Files
resolutionflow/frontend/src/index.css
chihlasm 5095b0d8df refactor: adopt shared Input/Textarea components (#101)
* refactor: adopt shared Input/Textarea components across 15 files

Replace 42 raw <input>/<textarea> elements with <Input>/<Textarea>
from components/ui/. Consistent focus states, error handling, and
styling across all form fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: replace hardcoded rgba/hex colors with Tailwind tokens

- rgba(255,255,255,0.xx) → bg-white/[0.xx], border-white/[0.xx]
- rgba(6,182,212,0.3) → border-primary/30 (focus states)
- #0a0a0a → bg-background
- Inline style hex colors → var(--color-primary), var(--color-brand-gradient-to)
- 28 files updated, zero hardcoded rgba() patterns remaining

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add PageMeta to 16 pages for SEO and proper browser tab titles

Public pages (Login, Register, Forgot/Reset Password, Verify Email,
Survey Thank You) get descriptions for SEO. Authenticated pages
(Dashboard, Flow Library, My Flows, Session History, AI Assistant,
Account Settings, Step Library, My Shares, Feedback, Guides) get
proper tab titles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add page transitions and staggered list animations

- ViewTransitionOutlet: wraps Outlet with fade-in-up animation keyed
  to route path. Sidebar/topbar stay still, only content area animates.
- StaggerList: reusable component that cascades children with
  incremental delay (50ms default). Pure CSS via @utility stagger-item.
- Applied stagger to TreeGridView, MyTreesPage cards, SessionHistoryPage.
- New stagger-fade-in keyframe in @theme block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: ViewTransitionOutlet needs h-full for React Flow canvas

The wrapper div broke the height chain needed by TreeEditorPage's
h-full layout, causing React Flow canvas to collapse to zero height.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: main-content flex layout for tree editor + scrollable pages

Main content area is now flex-col so the ViewTransitionOutlet wrapper
gets an explicit computed height via flex-1 min-h-0. This makes h-full
resolve correctly in the tree editor (React Flow canvas) while still
allowing overflow-y-auto scrolling for normal pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve ESLint errors in Button and Skeleton components

- Button: suppress react-refresh/only-export-components for buttonVariants re-export
- Skeleton: replace empty interface with type alias, replace Math.random() with static widths array

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add PageMeta, animation classes, and layout fixes to remaining pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:12:21 -04:00

450 lines
14 KiB
CSS

@import 'tailwindcss';
@custom-variant dark (&:where(.dark, .dark *));
/* React Flow — imported here (not in component JS) so @tailwindcss/vite
doesn't wrap it in a cascade layer, which would lower its specificity
below Tailwind's own styles and hide nodes/edges. */
@import '@xyflow/react/dist/style.css';
@theme {
/* ── Brand tokens ─────────────────────────────────── */
--color-brand-gradient-from: oklch(0.65 0.13 195); /* #06b6d4 cyan-500 */
--color-brand-gradient-to: oklch(0.74 0.12 195); /* #22d3ee cyan-400 */
--color-brand-dark: oklch(0.145 0.013 264); /* #101114 */
--color-brand-dark-card: oklch(0.17 0.01 264); /* #14161a */
--color-brand-dark-surface: oklch(0.17 0.01 264); /* #14161a */
--color-brand-text-primary: oklch(0.98 0.005 264); /* #f8fafc */
--color-brand-text-secondary: oklch(0.63 0.02 260); /* #8891a0 */
--color-brand-text-muted: oklch(0.45 0.015 260); /* #5a6170 */
--color-brand-border: oklch(0.35 0 0 / 0.06);
/* ── Semantic color tokens (OKLCH, direct values) ── */
--color-background: oklch(0.145 0.013 264); /* dark charcoal #101114 */
--color-foreground: oklch(0.98 0.005 264); /* near-white #f8fafc */
--color-card: oklch(0.178 0.008 264); /* #17191d */
--color-card-foreground: oklch(0.98 0.005 264);
--color-popover: oklch(0.178 0.008 264);
--color-popover-foreground: oklch(0.98 0.005 264);
--color-primary: oklch(0.65 0.13 195); /* cyan #1ea8c4 */
--color-primary-foreground: oklch(0.145 0.013 264); /* dark bg for contrast */
--color-secondary: oklch(0.22 0.008 264); /* #212329 */
--color-secondary-foreground: oklch(0.98 0.005 264);
--color-muted: oklch(0.22 0.008 264);
--color-muted-foreground: oklch(0.63 0.02 260); /* #8891a0 */
--color-accent: oklch(0.22 0.008 264);
--color-accent-foreground: oklch(0.98 0.005 264);
--color-destructive: oklch(0.55 0.22 15); /* rose #e63359 */
--color-destructive-foreground: oklch(0.98 0.005 264);
--color-border: oklch(0.22 0.008 264);
--color-input: oklch(0.22 0.008 264);
--color-ring: oklch(0.65 0.13 195); /* cyan, matches primary */
/* ── Radii ───────────────────────────────────────── */
--radius-sm: 0.5rem;
--radius-md: 0.625rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
/* ── Fonts ───────────────────────────────────────── */
--font-sans:
'IBM Plex Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, sans-serif;
--font-heading: 'Bricolage Grotesque', system-ui, sans-serif;
--font-label: 'JetBrains Mono', monospace;
/* ── Gradients ───────────────────────────────────── */
--background-image-gradient-brand: linear-gradient(
135deg,
oklch(0.65 0.13 195) 0%,
oklch(0.74 0.12 195) 100%
);
--background-image-gradient-brand-hover: linear-gradient(
135deg,
oklch(0.58 0.12 195) 0%,
oklch(0.65 0.13 195) 100%
);
/* ── Animations (keyframes inside @theme) ────────── */
--animate-fade-in: fade-in 200ms ease-out;
--animate-fade-in-up: fade-in-up 200ms ease-out;
--animate-slide-in-left: slide-in-from-left 200ms ease-out;
--animate-slide-in-bottom: slide-in-from-bottom 200ms ease-out;
--animate-scale-in: scale-in 150ms ease-out;
--animate-slide-down: slideDown 400ms ease-out;
--animate-slide-in-right: slideInRight 300ms ease-out;
--animate-fade-in-right: fadeInRight 400ms ease-out;
--animate-breathe-glow: breatheGlow 3s ease-in-out infinite alternate;
--animate-bell-wobble: bellWobble 500ms ease-in-out;
--animate-fade: fadeIn 300ms ease forwards;
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slide-in-from-left {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
@keyframes slide-in-from-bottom {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scale-in {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes slideDown {
from { transform: translateY(-100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes slideInRight {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes fadeInRight {
from { transform: translateX(30px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes breatheGlow {
from {
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
0 0 20px oklch(0.65 0.13 195 / 0.04);
}
to {
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
0 0 30px oklch(0.65 0.13 195 / 0.12);
}
}
@keyframes bellWobble {
0% { transform: rotate(0deg); }
20% { transform: rotate(8deg); }
40% { transform: rotate(-6deg); }
60% { transform: rotate(4deg); }
80% { transform: rotate(-2deg); }
100% { transform: rotate(0deg); }
}
@keyframes stagger-fade-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
}
/* ── Root CSS variables (non-theme: glass, shadows, layout) ── */
:root {
/* App Shell tokens */
--sidebar-w: 260px;
--sidebar-bg: oklch(0.13 0.013 264);
--sidebar-hover: var(--color-secondary);
--sidebar-active: oklch(0.65 0.13 195 / 0.10);
--text-dimmed: oklch(0.42 0.015 260);
/* Glass system */
--glass-bg: oklch(0.16 0.01 264 / 0.55);
--glass-bg-hover: oklch(0.16 0.01 264 / 0.7);
--glass-border: oklch(1 0 0 / 0.06);
--glass-border-hover: oklch(1 0 0 / 0.12);
--glass-blur: blur(16px);
--glass-blur-strong: blur(20px);
--glass-blur-light: blur(12px);
/* Shadow system */
--shadow-float: 0 8px 32px rgba(0, 0, 0, 0.3);
--shadow-float-hover: 0 12px 40px rgba(0, 0, 0, 0.4), 0 0 0 1px oklch(1 0 0 / 0.08);
--shadow-cyan-glow: 0 8px 32px oklch(0.65 0.13 195 / 0.08);
/* Easing */
--ease-out-smooth: cubic-bezier(0.4, 0, 0.2, 1);
}
/* ── Base styles ─────────────────────────────────────── */
@layer base {
* {
@apply border-border;
scrollbar-width: thin;
scrollbar-color: var(--color-border) transparent;
}
*::-webkit-scrollbar {
width: 6px;
height: 6px;
}
*::-webkit-scrollbar-track {
background: transparent;
}
*::-webkit-scrollbar-thumb {
background-color: var(--color-border);
border-radius: 9999px;
}
*::-webkit-scrollbar-thumb:hover {
background-color: var(--color-muted-foreground);
}
body {
@apply bg-background text-foreground font-sans;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading);
font-weight: 700;
letter-spacing: -0.02em;
}
}
/* ── Custom utilities ────────────────────────────────── */
@utility btn-press {
@apply active:scale-[0.98] transition-transform;
}
@utility text-gradient-brand {
@apply bg-gradient-brand bg-clip-text text-transparent;
}
@utility glass-card {
background: var(--glass-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--glass-border);
border-radius: 16px;
box-shadow: var(--shadow-float);
transition:
transform 200ms var(--ease-out-smooth),
border-color 200ms var(--ease-out-smooth),
box-shadow 200ms var(--ease-out-smooth);
&:hover {
transform: scale(1.02);
border-color: var(--glass-border-hover);
box-shadow: var(--shadow-float-hover);
}
}
@utility glass-card-static {
background: var(--glass-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--glass-border);
border-radius: 16px;
box-shadow: var(--shadow-float);
}
@utility active-glow {
animation: breatheGlow 3s ease-in-out infinite alternate;
}
@utility stagger-item {
opacity: 0;
animation: stagger-fade-in 350ms var(--ease-out-smooth) forwards;
animation-delay: calc(var(--stagger-index, 0) * 50ms);
}
@utility rdp-custom {
@apply text-foreground;
& .rdp-month { @apply w-full; }
& .rdp-caption { @apply flex justify-center items-center mb-4; }
& .rdp-caption_label { @apply text-sm font-medium; }
& .rdp-nav { @apply flex gap-1; }
& .rdp-nav_button { @apply h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100; }
& .rdp-table { @apply w-full border-collapse; }
& .rdp-head_cell { @apply text-muted-foreground font-normal text-xs; }
& .rdp-cell { @apply text-center text-sm p-0; }
& .rdp-day { @apply h-9 w-9 p-0 font-normal hover:bg-accent rounded-md transition-colors; }
& .rdp-day_selected { @apply bg-primary text-primary-foreground hover:bg-primary/90; }
& .rdp-day_today { @apply bg-accent text-accent-foreground; }
& .rdp-day_outside { @apply text-muted-foreground opacity-50; }
& .rdp-day_disabled { @apply text-muted-foreground opacity-50; }
& .rdp-day_range_middle { @apply bg-accent text-accent-foreground; }
& .rdp-day_hidden { @apply invisible; }
}
/* ── Layout utilities ────────────────────────────────── */
@layer utilities {
.app-shell {
display: grid;
grid-template-columns: var(--sidebar-w) 1fr;
grid-template-rows: 56px 1fr;
height: 100vh;
overflow: hidden;
transition: grid-template-columns 200ms ease;
}
.app-shell--collapsed {
grid-template-columns: 56px 1fr;
}
.topbar {
grid-column: 1 / -1;
}
.sidebar {
min-height: 0;
overflow-y: auto;
}
.main-content {
min-height: 0;
min-width: 0;
/* Each page handles its own scrolling — full-height pages use
overflow-hidden, scrollable pages use overflow-y-auto */
}
@media (max-width: 767px) {
.app-shell {
grid-template-columns: 1fr;
}
}
/* Staggered fade-in helper */
.fade-in {
animation: fadeIn 0.3s ease forwards;
}
}
/* ── Sonner toast overrides ──────────────────────────── */
[data-sonner-toast] {
background-color: var(--color-card) !important;
color: var(--color-card-foreground) !important;
border: 1px solid var(--color-border) !important;
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3) !important;
border-radius: 0.75rem;
font-family: var(--font-sans);
}
[data-sonner-toast] [data-title] {
font-family: var(--font-sans);
font-weight: 600;
}
[data-sonner-toast][data-type='success'] {
border-color: oklch(0.76 0.15 163 / 0.3) !important;
}
[data-sonner-toast][data-type='success'] [data-icon] {
color: oklch(0.76 0.15 163);
}
[data-sonner-toast][data-type='error'] {
border-color: oklch(0.7 0.16 22 / 0.3) !important;
}
[data-sonner-toast][data-type='error'] [data-icon] {
color: oklch(0.7 0.16 22);
}
[data-sonner-toast][data-type='info'] {
border-color: var(--color-border) !important;
}
[data-sonner-toast][data-type='info'] [data-icon] {
color: var(--color-muted-foreground);
}
[data-sonner-toast][data-type='warning'] {
border-color: oklch(0.82 0.16 85 / 0.3) !important;
}
[data-sonner-toast][data-type='warning'] [data-icon] {
color: oklch(0.82 0.16 85);
}
[data-sonner-toast] [data-close-button] {
color: var(--color-muted-foreground);
border-radius: 0.375rem;
transition: color 150ms, background-color 150ms;
}
[data-sonner-toast] [data-close-button]:hover {
background-color: var(--color-accent);
color: var(--color-accent-foreground);
}
/* ── React Flow dark theme overrides ─────────────────── */
/* React Flow v12 uses --xy-* CSS custom properties for theming.
Override the defaults to match our Slate & Ice design system. */
.react-flow.dark {
--xy-background-color-default: transparent;
--xy-edge-stroke-default: var(--color-border);
--xy-edge-stroke-selected-default: var(--color-primary);
--xy-edge-label-color-default: var(--color-muted-foreground);
--xy-edge-label-background-color-default: var(--color-card);
--xy-node-background-color-default: var(--color-card);
--xy-node-color-default: var(--color-foreground);
--xy-node-border-default: 1px solid var(--color-border);
--xy-handle-background-color-default: var(--color-border);
--xy-handle-border-color-default: var(--color-card);
--xy-minimap-background-color-default: var(--color-card);
--xy-minimap-mask-background-color-default: oklch(0.22 0.008 264 / 0.6);
--xy-controls-button-background-color-default: var(--color-card);
--xy-controls-button-background-color-hover-default: var(--color-accent);
--xy-controls-button-color-default: var(--color-muted-foreground);
--xy-controls-button-color-hover-default: var(--color-foreground);
--xy-controls-button-border-color-default: var(--color-border);
}
.react-flow__controls {
border-radius: 0.75rem !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3) !important;
overflow: hidden;
}
.react-flow__minimap {
border-radius: 0.75rem !important;
}
.react-flow__attribution {
display: none;
}
/* ── Glow edge animations ────────────────────────────── */
@keyframes glow-flow-downstream {
from { stroke-dashoffset: 40; }
to { stroke-dashoffset: 0; }
}
@keyframes glow-flow-upstream {
from { stroke-dashoffset: 0; }
to { stroke-dashoffset: 40; }
}
.glow-edge-flow-downstream {
animation: glow-flow-downstream 1s linear infinite;
}
.glow-edge-flow-upstream {
animation: glow-flow-upstream 1s linear infinite;
}
/* ── Accessibility: Reduce motion ────────────────────── */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}