From 04992846797896ab34c4f2fa9622fe73564feb2c Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Sun, 22 Mar 2026 00:19:21 -0400 Subject: [PATCH] refactor: rewrite CSS foundation for Design System v4 New flat dark color tokens, remove glass/gradient utilities, add compatibility shims for phased migration. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/index.css | 325 ++++++++++++++++++----------------------- 1 file changed, 143 insertions(+), 182 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index b40e3f7c..045ce347 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -8,186 +8,104 @@ @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); + /* ── Surface colors ────────────────────────────── */ + --color-bg-page: #0c0d10; + --color-bg-sidebar: #0f1118; + --color-bg-card: #14161d; + --color-bg-card-hover: #191c25; + --color-bg-input: #191c25; + --color-bg-code: #0e1017; + --color-bg-elevated: #1c1f2a; - /* ── 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 */ + /* ── Text colors ───────────────────────────────── */ + --color-text-heading: #f0f2f5; + --color-text-primary: #e2e5eb; + --color-text-secondary: #848b9b; + --color-text-muted: #4f5666; + --color-text-rail-label: #6b7280; - --color-card: oklch(0.178 0.008 264); /* #17191d */ - --color-card-foreground: oklch(0.98 0.005 264); + /* ── Border colors ─────────────────────────────── */ + --color-border-default: #1e2130; + --color-border-hover: #2a2f3d; - --color-popover: oklch(0.178 0.008 264); - --color-popover-foreground: oklch(0.98 0.005 264); + /* ── Accent (cyan) ─────────────────────────────── */ + --color-accent: #22d3ee; + --color-accent-hover: #06b6d4; + --color-accent-dim: rgba(34,211,238,0.10); + --color-accent-text: #67e8f9; - --color-primary: oklch(0.65 0.13 195); /* cyan #1ea8c4 */ - --color-primary-foreground: oklch(0.145 0.013 264); /* dark bg for contrast */ + /* ── Semantic colors ───────────────────────────── */ + --color-success: #34d399; + --color-success-dim: rgba(52,211,153,0.10); + --color-warning: #fbbf24; + --color-warning-dim: rgba(251,191,36,0.10); + --color-danger: #f87171; + --color-danger-dim: rgba(248,113,113,0.10); - --color-secondary: oklch(0.22 0.008 264); /* #212329 */ - --color-secondary-foreground: oklch(0.98 0.005 264); + /* ── Tailwind semantic mappings ─────────────────── */ + --color-background: #0c0d10; + --color-foreground: #e2e5eb; + --color-card: #14161d; + --color-card-foreground: #e2e5eb; + --color-popover: #14161d; + --color-popover-foreground: #e2e5eb; + --color-primary: #22d3ee; + --color-primary-foreground: #ffffff; + --color-secondary: #1c1f2a; + --color-secondary-foreground: #e2e5eb; + --color-muted: #1c1f2a; + --color-muted-foreground: #848b9b; + --color-accent-tw: #1c1f2a; + --color-accent-foreground: #e2e5eb; + --color-destructive: #f87171; + --color-destructive-foreground: #ffffff; + --color-border: #1e2130; + --color-input: #191c25; + --color-ring: #22d3ee; - --color-muted: oklch(0.22 0.008 264); - --color-muted-foreground: oklch(0.63 0.02 260); /* #8891a0 */ + /* ── Radii ─────────────────────────────────────── */ + --radius-sm: 5px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; - --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; + /* ── Fonts ─────────────────────────────────────── */ + --font-sans: 'IBM Plex Sans', system-ui, -apple-system, sans-serif; --font-heading: 'Bricolage Grotesque', system-ui, sans-serif; - --font-label: 'JetBrains Mono', monospace; + --font-mono: 'JetBrains Mono', monospace; + --font-label: 'JetBrains Mono', monospace; /* deprecated alias — remove in Phase 5 */ - /* ── 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) ────────── */ + /* ── Animations ────────────────────────────────── */ --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; } + from { opacity: 0; } to { opacity: 1; } } - @keyframes fade-in-up { - from { opacity: 0; transform: translateY(4px); } - to { opacity: 1; transform: translateY(0); } + 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); } + 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); } + 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); } + 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 pulse-dot { - 0%, 100% { box-shadow: 0 0 4px rgba(52,211,153,0.4); } - 50% { box-shadow: 0 0 8px rgba(52,211,153,0.7); } - } - - @keyframes stagger-fade-in { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } + from { opacity: 0; transform: translateY(6px); } 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 */ + --sidebar-w: 72px; + --sidebar-w-pinned: 260px; --ease-out-smooth: cubic-bezier(0.4, 0, 0.2, 1); } @@ -224,52 +142,91 @@ } } -/* ── 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; -} - +/* ── Deprecated shims — remove after Phase 3 sweep ── */ @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); + background: var(--color-bg-card); + border: 1px solid var(--color-border-default); + border-radius: 8px; + transition: border-color 200ms ease; &:hover { - transform: scale(1.02); - border-color: var(--glass-border-hover); - box-shadow: var(--shadow-float-hover); + border-color: var(--color-border-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); + background: var(--color-bg-card); + border: 1px solid var(--color-border-default); + border-radius: 8px; +} + +@utility text-gradient-brand { + color: var(--color-accent-text); } @utility active-glow { - animation: breatheGlow 3s ease-in-out infinite alternate; + /* glow removed in v4 — kept as no-op shim */ + --_active-glow: 1; +} + +@utility bg-gradient-brand-hover { + &:hover { filter: brightness(1.1); } } @utility stagger-item { opacity: 0; - animation: stagger-fade-in 350ms var(--ease-out-smooth) forwards; + animation: fadeIn 350ms var(--ease-out-smooth) forwards; animation-delay: calc(var(--stagger-index, 0) * 50ms); } +@utility btn-press { + @apply active:scale-[0.98] transition-transform; +} + +/* ── New component classes (Design System v4) ───── */ +@utility card-flat { + background: var(--color-bg-card); + border: 1px solid var(--color-border-default); + border-radius: 8px; +} + +@utility card-interactive { + background: var(--color-bg-card); + border: 1px solid var(--color-border-default); + border-radius: 8px; + transition: border-color 200ms ease; + &:hover { + border-color: var(--color-border-hover); + } +} + +@utility btn-primary-v4 { + background: var(--color-accent); + color: #ffffff; + font-weight: 550; + border-radius: 5px; + padding: 9px 16px; + font-size: 13px; + transition: filter 150ms ease; + &:hover { + filter: brightness(1.1); + } +} + +@utility btn-ghost-v4 { + background: transparent; + color: var(--color-text-secondary); + border: 1px solid var(--color-border-default); + border-radius: 5px; + padding: 9px 16px; + font-size: 13px; + transition: background 150ms ease, color 150ms ease, border-color 150ms ease; + &:hover { + background: var(--color-bg-elevated); + color: var(--color-text-primary); + border-color: var(--color-border-hover); + } +} + @utility rdp-custom { @apply text-foreground; & .rdp-month { @apply w-full; } @@ -294,12 +251,16 @@ .app-shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; - grid-template-rows: 56px 1fr; + grid-template-rows: auto 1fr; height: 100vh; overflow: hidden; transition: grid-template-columns 200ms ease; } + .app-shell--pinned { + --sidebar-w: 260px; + } + .app-shell--collapsed { grid-template-columns: 56px 1fr; } @@ -387,7 +348,7 @@ /* ── 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. */ + Override the defaults to match our design system. */ .react-flow.dark { --xy-background-color-default: transparent; --xy-edge-stroke-default: var(--color-border); @@ -400,7 +361,7 @@ --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-minimap-mask-background-color-default: rgba(28, 31, 42, 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);