# Theme — Design System v2 Complete design system overhaul. Replaces the achromatic shadcn/ui defaults with an indigo-branded, status-aware, dark-mode-first token system. ## Current State (v1 Problems) ``` :root (light) .dark --secondary: 0 0% 96.1% --secondary: 0 0% 14.9% --muted: 0 0% 96.1% <-- SAME --muted: 0 0% 14.9% <-- SAME --accent: 0 0% 96.1% <-- SAME --accent: 0 0% 14.9% <-- SAME --primary: 0 0% 9% <-- black --primary: 0 0% 98% <-- white --ring: 0 0% 3.9% <-- black --ring: 0 0% 83.1% <-- grey --radius: 0.5rem (8px) ``` Problems: - Zero brand identity (achromatic everywhere) - `secondary`, `muted`, and `accent` all resolve to the **same** gray value - No status color tokens (active/success/warning/error) - No terminal tokens (AgentOutputViewer always-dark surface) - No diff tokens (review components) - No dark mode toggle - Ring color matches foreground instead of brand color - 8px radius is visually heavy --- ## 1. Color System — Indigo Brand (#6366F1) ### Light Mode ```css :root { --background: 0 0% 99%; /* #FCFCFC — slight warmth, not pure white */ --foreground: 240 6% 10%; /* #18181B — zinc-900 equivalent */ --card: 0 0% 100%; /* #FFFFFF */ --card-foreground: 240 6% 10%; --popover: 0 0% 100%; /* #FFFFFF */ --popover-foreground: 240 6% 10%; --primary: 239 84% 67%; /* #6366F1 — indigo-500 */ --primary-foreground: 0 0% 100%; /* white on indigo */ --secondary: 240 5% 96%; /* #F4F4F5 — zinc-100 */ --secondary-foreground: 240 4% 16%;/* #27272A — zinc-800 */ --muted: 240 5% 93%; /* #ECECEE — distinguishable from secondary */ --muted-foreground: 240 4% 46%; /* #71717A — zinc-500 */ --accent: 226 100% 97%; /* #EEF0FF — light indigo tint */ --accent-foreground: 239 84% 67%; /* indigo on tint */ --destructive: 0 84% 60%; /* #EF4444 — red-500 */ --destructive-foreground: 0 0% 100%; --border: 240 6% 90%; /* #E4E4E7 — zinc-200 */ --input: 240 6% 90%; --ring: 239 84% 67%; /* indigo focus ring */ --radius: 0.375rem; /* 6px — down from 8px */ } ``` ### Dark Mode ```css .dark { --background: 240 6% 7%; /* #111114 */ --foreground: 240 5% 96%; /* #F4F4F5 */ --card: 240 5% 10%; /* #19191D — surface level 1 */ --card-foreground: 240 5% 96%; --popover: 240 5% 13%; /* #202025 — surface level 2 */ --popover-foreground: 240 5% 96%; --primary: 239 84% 67%; /* #6366F1 — same indigo */ --primary-foreground: 0 0% 100%; --secondary: 240 4% 16%; /* #27272A */ --secondary-foreground: 240 5% 96%; --muted: 240 4% 16%; /* #27272A */ --muted-foreground: 240 5% 65%; /* #A1A1AA — zinc-400 */ --accent: 239 40% 16%; /* dark indigo tint */ --accent-foreground: 239 84% 75%; /* lighter indigo on dark */ --destructive: 0 63% 31%; --destructive-foreground: 0 0% 100%; --border: 240 4% 16%; --input: 240 4% 16%; --ring: 239 84% 67%; } ``` ### Surface Hierarchy (Dark Mode) Three-tier elevation model using lightness alone (no box shadows in dark mode): ``` Level 0 --background 240 6% 7% #111114 Page background Level 1 --card 240 5% 10% #19191D Cards, panels, sidebars Level 2 --popover 240 5% 13% #202025 Dropdowns, tooltips, command palette ``` Visual reference: ``` +============================================================================+ | Level 0 (7%) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | | | | +------------------------------------------------------------------+ | | | Level 1 (10%) ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ | | | | | | | | +----------------------------------------------+ | | | | | Level 2 (13%) ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ | | | | | +----------------------------------------------+ | | | | | | | +------------------------------------------------------------------+ | | | +============================================================================+ ``` --- ## 2. Status Tokens Six semantic status sets with bg/fg/border/dot variants for both light and dark modes. ### Light Mode | Status | Use Case | bg | fg | border | dot | |--------|----------|----|----|--------|-----| | `active` | Running agents, in-progress tasks | `210 100% 95%` | `210 100% 40%` | `210 100% 80%` | `210 100% 50%` | | `success` | Completed tasks, healthy services | `142 72% 94%` | `142 72% 29%` | `142 72% 80%` | `142 72% 45%` | | `warning` | Pending approval, exhausted accounts | `38 92% 95%` | `38 92% 30%` | `38 92% 80%` | `38 92% 50%` | | `error` | Failed tasks, crashed agents | `0 84% 95%` | `0 84% 40%` | `0 84% 80%` | `0 84% 50%` | | `neutral` | Exited agents, idle states | `240 5% 96%` | `240 4% 46%` | `240 6% 90%` | `240 4% 46%` | | `urgent` | Blocked tasks, attention needed | `270 91% 95%` | `270 91% 40%` | `270 91% 80%` | `270 91% 55%` | ### Dark Mode | Status | bg | fg | border | dot | |--------|----|----|--------|-----| | `active` | `210 100% 12%` | `210 100% 70%` | `210 100% 25%` | `210 100% 50%` | | `success` | `142 72% 10%` | `142 72% 65%` | `142 72% 22%` | `142 72% 45%` | | `warning` | `38 92% 10%` | `38 92% 65%` | `38 92% 22%` | `38 92% 50%` | | `error` | `0 84% 12%` | `0 84% 65%` | `0 84% 25%` | `0 84% 50%` | | `neutral` | `240 4% 16%` | `240 5% 65%` | `240 4% 22%` | `240 5% 50%` | | `urgent` | `270 91% 12%` | `270 91% 70%` | `270 91% 25%` | `270 91% 55%` | ### CSS Custom Properties ```css :root { /* active */ --status-active-bg: 210 100% 95%; --status-active-fg: 210 100% 40%; --status-active-border: 210 100% 80%; --status-active-dot: 210 100% 50%; /* success */ --status-success-bg: 142 72% 94%; --status-success-fg: 142 72% 29%; --status-success-border: 142 72% 80%; --status-success-dot: 142 72% 45%; /* warning */ --status-warning-bg: 38 92% 95%; --status-warning-fg: 38 92% 30%; --status-warning-border: 38 92% 80%; --status-warning-dot: 38 92% 50%; /* error */ --status-error-bg: 0 84% 95%; --status-error-fg: 0 84% 40%; --status-error-border: 0 84% 80%; --status-error-dot: 0 84% 50%; /* neutral */ --status-neutral-bg: 240 5% 96%; --status-neutral-fg: 240 4% 46%; --status-neutral-border: 240 6% 90%; --status-neutral-dot: 240 4% 46%; /* urgent */ --status-urgent-bg: 270 91% 95%; --status-urgent-fg: 270 91% 40%; --status-urgent-border: 270 91% 80%; --status-urgent-dot: 270 91% 55%; } .dark { --status-active-bg: 210 100% 12%; --status-active-fg: 210 100% 70%; --status-active-border: 210 100% 25%; --status-active-dot: 210 100% 50%; --status-success-bg: 142 72% 10%; --status-success-fg: 142 72% 65%; --status-success-border: 142 72% 22%; --status-success-dot: 142 72% 45%; --status-warning-bg: 38 92% 10%; --status-warning-fg: 38 92% 65%; --status-warning-border: 38 92% 22%; --status-warning-dot: 38 92% 50%; --status-error-bg: 0 84% 12%; --status-error-fg: 0 84% 65%; --status-error-border: 0 84% 25%; --status-error-dot: 0 84% 50%; --status-neutral-bg: 240 4% 16%; --status-neutral-fg: 240 5% 65%; --status-neutral-border: 240 4% 22%; --status-neutral-dot: 240 5% 50%; --status-urgent-bg: 270 91% 12%; --status-urgent-fg: 270 91% 70%; --status-urgent-border: 270 91% 25%; --status-urgent-dot: 270 91% 55%; } ``` ### Tailwind Utility Class Mapping Extend `tailwind.config.ts` to expose status tokens as utilities: ```ts // tailwind.config.ts theme: { extend: { colors: { status: { 'active-bg': 'hsl(var(--status-active-bg))', 'active-fg': 'hsl(var(--status-active-fg))', 'active-border': 'hsl(var(--status-active-border))', 'active-dot': 'hsl(var(--status-active-dot))', 'success-bg': 'hsl(var(--status-success-bg))', 'success-fg': 'hsl(var(--status-success-fg))', 'success-border': 'hsl(var(--status-success-border))', 'success-dot': 'hsl(var(--status-success-dot))', 'warning-bg': 'hsl(var(--status-warning-bg))', 'warning-fg': 'hsl(var(--status-warning-fg))', 'warning-border': 'hsl(var(--status-warning-border))', 'warning-dot': 'hsl(var(--status-warning-dot))', 'error-bg': 'hsl(var(--status-error-bg))', 'error-fg': 'hsl(var(--status-error-fg))', 'error-border': 'hsl(var(--status-error-border))', 'error-dot': 'hsl(var(--status-error-dot))', 'neutral-bg': 'hsl(var(--status-neutral-bg))', 'neutral-fg': 'hsl(var(--status-neutral-fg))', 'neutral-border': 'hsl(var(--status-neutral-border))', 'neutral-dot': 'hsl(var(--status-neutral-dot))', 'urgent-bg': 'hsl(var(--status-urgent-bg))', 'urgent-fg': 'hsl(var(--status-urgent-fg))', 'urgent-border': 'hsl(var(--status-urgent-border))', 'urgent-dot': 'hsl(var(--status-urgent-dot))', }, }, }, } ``` Usage in components: ```tsx [RUNNING] {/* green dot */} ``` ### Status-to-Entity Mapping | Entity State | Status Token | |--------------|-------------| | Agent: running | `active` | | Agent: waiting_for_input | `warning` | | Agent: exited / completed | `neutral` | | Agent: crashed | `error` | | Task: in_progress | `active` | | Task: completed | `success` | | Task: pending | `neutral` | | Task: pending_approval | `warning` | | Task: blocked | `urgent` | | Task: failed | `error` | | Account: active | `success` | | Account: exhausted | `warning` | | Preview: building | `active` | | Preview: running | `success` | | Preview: failed | `error` | | Preview: stopped | `neutral` | | Server health: connected | `success` | | Server health: disconnected | `error` | --- ## 3. Terminal Tokens For `AgentOutputViewer` — always-dark surface even in light mode (terminal aesthetic), card-level in dark mode. ```css :root { --terminal-bg: 240 6% 7%; /* always dark — #111114 */ --terminal-fg: 120 100% 80%; /* green-on-dark */ --terminal-muted: 240 5% 55%; /* dimmed text */ --terminal-border: 240 4% 16%; --terminal-selection: 239 84% 67%; /* indigo selection, use with /0.2 alpha */ --terminal-system: 240 5% 55%; /* [System] badge text */ --terminal-tool: 217 91% 60%; /* [Tool Call] blue accent */ --terminal-result: 142 72% 45%; /* [Result] green accent */ --terminal-error: 0 84% 60%; /* [Error] red accent */ } .dark { --terminal-bg: 240 5% 10%; /* matches card surface */ --terminal-fg: 120 100% 80%; --terminal-muted: 240 5% 55%; --terminal-border: 240 4% 16%; --terminal-selection: 239 84% 67%; --terminal-system: 240 5% 55%; --terminal-tool: 217 91% 60%; --terminal-result: 142 72% 45%; --terminal-error: 0 84% 60%; } ``` Tailwind extension: ```ts terminal: { DEFAULT: 'hsl(var(--terminal-bg))', fg: 'hsl(var(--terminal-fg))', muted: 'hsl(var(--terminal-muted))', border: 'hsl(var(--terminal-border))', system: 'hsl(var(--terminal-system))', tool: 'hsl(var(--terminal-tool))', result: 'hsl(var(--terminal-result))', error: 'hsl(var(--terminal-error))', }, ``` Visual reference (AgentOutputViewer): ``` +------------------------------------------------------------------------+ | bg: --terminal-bg | | | | [System] Starting task... <-- text: --terminal-system | | | | > I'll examine the existing code. <-- text: --terminal-fg | | | | | [Tool Call] Read <-- border/text: --terminal-tool | | file_path: src/auth/index.ts <-- text: --terminal-muted | | | | | [Result] <-- border/text: --terminal-result | | import { Router } from 'express'; <-- text: --terminal-muted | | | | | [Error] <-- border/text: --terminal-error | | Permission denied: /etc/hosts | | | +------------------------------------------------------------------------+ ``` --- ## 4. Diff Tokens For review tab components — file diffs, proposal diffs, merge previews. ```css :root { --diff-add-bg: 142 72% 94%; /* light green background */ --diff-add-fg: 142 72% 29%; /* dark green text */ --diff-add-border: 142 72% 80%; --diff-remove-bg: 0 84% 95%; /* light red background */ --diff-remove-fg: 0 84% 40%; /* dark red text */ --diff-remove-border: 0 84% 80%; --diff-hunk-bg: 226 100% 97%; /* indigo-tinted hunk header */ } .dark { --diff-add-bg: 142 72% 10%; --diff-add-fg: 142 72% 65%; --diff-add-border: 142 72% 22%; --diff-remove-bg: 0 84% 12%; --diff-remove-fg: 0 84% 65%; --diff-remove-border: 0 84% 25%; --diff-hunk-bg: 239 40% 16%; } ``` Tailwind extension: ```ts diff: { 'add-bg': 'hsl(var(--diff-add-bg))', 'add-fg': 'hsl(var(--diff-add-fg))', 'add-border': 'hsl(var(--diff-add-border))', 'remove-bg': 'hsl(var(--diff-remove-bg))', 'remove-fg': 'hsl(var(--diff-remove-fg))', 'remove-border':'hsl(var(--diff-remove-border))', 'hunk-bg': 'hsl(var(--diff-hunk-bg))', }, ``` Visual reference: ``` +------------------------------------------------------------------------+ | @@ -12,6 +12,8 @@ function setup() bg: --diff-hunk-bg | | const router = Router(); | | router.get('/health', ...); | | + router.get('/oauth/callback', ...); bg: --diff-add-bg | | + router.get('/oauth/login', ...); fg: --diff-add-fg | | - router.get('/legacy', ...); bg: --diff-remove-bg | | export default router; fg: --diff-remove-fg | +------------------------------------------------------------------------+ ``` --- ## 5. Typography — Geist Sans + Geist Mono ### Installation ```bash cd packages/web npm install geist ``` ### CSS Import Add to `packages/web/src/index.css` before `@tailwind` directives: ```css @import 'geist/font/sans.css'; @import 'geist/font/mono.css'; ``` ### Tailwind Config ```ts // packages/web/tailwind.config.ts import defaultTheme from 'tailwindcss/defaultTheme'; export default { theme: { extend: { fontFamily: { sans: ['Geist Sans', ...defaultTheme.fontFamily.sans], mono: ['Geist Mono', ...defaultTheme.fontFamily.mono], }, }, }, }; ``` ### Usage - Body text: `font-sans` (Geist Sans) — applied via Tailwind base - Code blocks, terminal output: `font-mono` (Geist Mono) - Agent names: `font-mono text-sm` - Timestamps, metadata: `font-mono text-xs text-muted-foreground` No font size scale changes; keep Tailwind defaults (`text-xs` through `text-4xl`). --- ## 6. Radius — 6px Base ```css :root { --radius: 0.375rem; /* 6px, down from 0.5rem/8px */ } ``` Tailwind border-radius mapping (shadcn/ui convention): ```ts borderRadius: { lg: 'var(--radius)', // 6px — cards, dialogs md: 'calc(var(--radius) - 2px)', // 4px — buttons, inputs sm: 'calc(var(--radius) - 4px)', // 2px — badges, small elements }, ``` v1 vs v2: | Token | v1 | v2 | |-------|----|----| | `--radius` | `0.5rem` (8px) | `0.375rem` (6px) | | `rounded-lg` | 8px | 6px | | `rounded-md` | 6px | 4px | | `rounded-sm` | 4px | 2px | --- ## 7. Shadows — Layered System ### Light Mode ```css :root { --shadow-xs: 0 1px 2px hsl(0 0% 0% / 0.04); --shadow-sm: 0 1px 3px hsl(0 0% 0% / 0.06), 0 1px 2px hsl(0 0% 0% / 0.04); --shadow-md: 0 4px 6px hsl(0 0% 0% / 0.06), 0 2px 4px hsl(0 0% 0% / 0.04); --shadow-lg: 0 10px 15px hsl(0 0% 0% / 0.06), 0 4px 6px hsl(0 0% 0% / 0.04); --shadow-xl: 0 20px 25px hsl(0 0% 0% / 0.08), 0 8px 10px hsl(0 0% 0% / 0.04); } ``` ### Dark Mode ```css .dark { --shadow-xs: none; --shadow-sm: none; --shadow-md: none; --shadow-lg: none; --shadow-xl: none; } ``` Dark mode uses **borders + surface lightness hierarchy** instead of box shadows. This avoids the muddy appearance of shadows on dark backgrounds. Tailwind extension: ```ts boxShadow: { xs: 'var(--shadow-xs)', sm: 'var(--shadow-sm)', md: 'var(--shadow-md)', lg: 'var(--shadow-lg)', xl: 'var(--shadow-xl)', }, ``` Usage guidance: | Component | Shadow Level | |-----------|-------------| | Buttons (hover) | `xs` | | Cards, panels | `sm` | | Dropdowns, popovers | `md` | | Dialogs, modals | `lg` | | Command palette | `xl` | --- ## 8. Dark Mode Implementation ### Default Behavior - First load: detect `prefers-color-scheme` media query - Persist choice in `localStorage` key: `cw-theme` - Values: `light`, `dark`, `system` - Default (no stored value): `system` ### Class Toggling Add/remove `.dark` class on the `` element: ```ts function applyTheme(theme: 'light' | 'dark' | 'system') { const root = document.documentElement; const isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); root.classList.toggle('dark', isDark); localStorage.setItem('cw-theme', theme); } ``` ### Inline Script (prevent flash) Add to `` in `index.html` before any stylesheets: ```html ``` ### Toggle Component 3-state toggle in the header bar, right-aligned: ``` +-------------------------------------------+ | [logo] Codewalk District [Sun|Monitor|Moon] | +-------------------------------------------+ States: [*Sun*] Monitor Moon = light mode forced Sun [*Monitor*] Moon = system preference Sun Monitor [*Moon*] = dark mode forced ``` - Icons: `Sun` (Lucide `Sun`), `Monitor` (Lucide `Monitor`), `Moon` (Lucide `Moon`) - Active state: `bg-accent` highlight - Location: right side of header bar, same row as logo ### React Context ```ts // packages/web/src/lib/theme.tsx type Theme = 'light' | 'dark' | 'system'; const ThemeContext = createContext<{ theme: Theme; setTheme: (t: Theme) => void; isDark: boolean; }>(null!); function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setThemeState] = useState( () => (localStorage.getItem('cw-theme') as Theme) || 'system' ); const isDark = useMemo(() => { if (theme === 'system') { return window.matchMedia('(prefers-color-scheme: dark)').matches; } return theme === 'dark'; }, [theme]); const setTheme = useCallback((t: Theme) => { setThemeState(t); applyTheme(t); }, []); // Listen for system theme changes when in 'system' mode useEffect(() => { const mq = window.matchMedia('(prefers-color-scheme: dark)'); const handler = () => { if (theme === 'system') applyTheme('system'); }; mq.addEventListener('change', handler); return () => mq.removeEventListener('change', handler); }, [theme]); return ( {children} ); } ``` --- ## 9. Migration Notes ### Token Changes: v1 to v2 #### Light Mode (`:root`) | Token | v1 | v2 | Notes | |-------|----|----|-------| | `--background` | `0 0% 100%` | `0 0% 99%` | Slight warmth | | `--foreground` | `0 0% 3.9%` | `240 6% 10%` | Zinc-tinted, not pure black | | `--card` | `0 0% 100%` | `0 0% 100%` | Unchanged | | `--card-foreground` | `0 0% 3.9%` | `240 6% 10%` | Matches foreground | | `--popover` | `0 0% 100%` | `0 0% 100%` | Unchanged | | `--popover-foreground` | `0 0% 3.9%` | `240 6% 10%` | Matches foreground | | `--primary` | `0 0% 9%` | `239 84% 67%` | **Black to indigo** | | `--primary-foreground` | `0 0% 98%` | `0 0% 100%` | Near-white to white | | `--secondary` | `0 0% 96.1%` | `240 5% 96%` | Zinc-tinted | | `--secondary-foreground` | `0 0% 9%` | `240 4% 16%` | Zinc-tinted | | `--muted` | `0 0% 96.1%` | `240 5% 93%` | **Now 93%, not 96.1%** | | `--muted-foreground` | `0 0% 45.1%` | `240 4% 46%` | Zinc-tinted | | `--accent` | `0 0% 96.1%` | `226 100% 97%` | **Grey to indigo tint** | | `--accent-foreground` | `0 0% 9%` | `239 84% 67%` | **Black to indigo** | | `--destructive` | `0 84.2% 60.2%` | `0 84% 60%` | Rounded values | | `--destructive-foreground` | `0 0% 98%` | `0 0% 100%` | Near-white to white | | `--border` | `0 0% 89.8%` | `240 6% 90%` | Zinc-tinted | | `--input` | `0 0% 89.8%` | `240 6% 90%` | Zinc-tinted | | `--ring` | `0 0% 3.9%` | `239 84% 67%` | **Black to indigo** | | `--radius` | `0.5rem` | `0.375rem` | **8px to 6px** | #### Dark Mode (`.dark`) | Token | v1 | v2 | Notes | |-------|----|----|-------| | `--background` | `0 0% 3.9%` | `240 6% 7%` | Lighter, zinc-tinted | | `--foreground` | `0 0% 98%` | `240 5% 96%` | Zinc-tinted | | `--card` | `0 0% 3.9%` | `240 5% 10%` | **Elevated above bg** | | `--card-foreground` | `0 0% 98%` | `240 5% 96%` | Zinc-tinted | | `--popover` | `0 0% 3.9%` | `240 5% 13%` | **Elevated above card** | | `--popover-foreground` | `0 0% 98%` | `240 5% 96%` | Zinc-tinted | | `--primary` | `0 0% 98%` | `239 84% 67%` | **White to indigo** | | `--primary-foreground` | `0 0% 9%` | `0 0% 100%` | Black to white | | `--secondary` | `0 0% 14.9%` | `240 4% 16%` | Zinc-tinted | | `--secondary-foreground` | `0 0% 98%` | `240 5% 96%` | Zinc-tinted | | `--muted` | `0 0% 14.9%` | `240 4% 16%` | Zinc-tinted | | `--muted-foreground` | `0 0% 63.9%` | `240 5% 65%` | Zinc-tinted | | `--accent` | `0 0% 14.9%` | `239 40% 16%` | **Grey to dark indigo** | | `--accent-foreground` | `0 0% 98%` | `239 84% 75%` | **White to light indigo** | | `--destructive` | `0 62.8% 30.6%` | `0 63% 31%` | Rounded values | | `--destructive-foreground` | `0 0% 98%` | `0 0% 100%` | Near-white to white | | `--border` | `0 0% 14.9%` | `240 4% 16%` | Zinc-tinted | | `--input` | `0 0% 14.9%` | `240 4% 16%` | Zinc-tinted | | `--ring` | `0 0% 83.1%` | `239 84% 67%` | **Grey to indigo** | ### Key Breaking Changes 1. **`--primary` is no longer achromatic.** Any component using `bg-primary` will render indigo instead of black/white. This is intentional — buttons, links, and focus rings gain brand color. 2. **`--secondary`, `--muted`, `--accent` are now distinct values.** Components relying on them being identical need review: - `--secondary` = neutral surface (96% light) - `--muted` = recessed surface (93% light) — **3% darker than secondary** - `--accent` = indigo-tinted surface (97% light, blue hue) — **colored, not grey** 3. **`--ring` is now indigo**, not foreground-colored. All focus outlines will be indigo. 4. **`--radius` shrinks from 8px to 6px.** All rounded corners tighten slightly. 5. **Dark mode card/popover surfaces are elevated.** v1 had card = background (both 3.9%). v2 separates them: background 7% < card 10% < popover 13%. ### New Token Categories These are entirely new in v2 — no v1 equivalents exist: - **Status tokens** (24 light + 24 dark = 48 new properties) - **Terminal tokens** (10 new properties) - **Diff tokens** (14 new properties) - **Shadow tokens** (5 new properties, `none` in dark) ### File Changes Required | File | Changes | |------|---------| | `packages/web/src/index.css` | Replace all `:root` and `.dark` token values; add status, terminal, diff, shadow tokens; add Geist font imports | | `packages/web/tailwind.config.ts` | Add `fontFamily`, `colors.status`, `colors.terminal`, `colors.diff`, `boxShadow`, `borderRadius` extensions | | `packages/web/package.json` | Add `geist` dependency | | `packages/web/index.html` | Add dark mode inline script in `` | | `packages/web/src/lib/theme.tsx` | New file: ThemeProvider context | | `packages/web/src/layouts/AppLayout.tsx` | Add ThemeToggle to header | | `packages/web/src/App.tsx` (or root) | Wrap with ThemeProvider | --- ## Source - `packages/web/src/index.css` (current theme) - `packages/web/tailwind.config.ts` (current Tailwind config)