# Design Visual system for the Maestro web UI. Source of truth is `web/src/styles.css` (`:root` token block); this document mirrors it. Register: **product** — design serves the task. See [PRODUCT.md](PRODUCT.md) for strategy. ## Theme Dark is the default (terminal context); light is a full peer. Everything is driven by CSS custom-property tokens, so a theme is a token swap, never a restyle. Resolution order: 1. `:root[data-theme="light"|"dark"]` — explicit user pin (persisted in `localStorage` as `maestro-theme`), set by the header toggle. 2. `@media (prefers-color-scheme: light)` — followed when the user hasn't pinned a theme. 3. Otherwise the `:root` defaults (dark). The xterm terminal stays dark in both themes by convention; `--term-bg` is dark in light mode too. ## Color (OKLCH) Strategy: **Restrained** (product floor) with one committed signal hue — **green = "alive"**, carrying running status, primary action, focus, and selection. Neutrals are a cool slate (hue ~256). Green is the signal, never a costume: text stays neutral high-contrast, surfaces stay slate, so the result reads terminal-native without CRT novelty. | Role | Dark | Light | |---|---|---| | `--bg` | `oklch(0.178 0.012 256)` | `oklch(0.985 0.003 256)` | | `--surface` | `oklch(0.214 0.014 256)` | `oklch(1 0 0)` | | `--surface-2` | `oklch(0.252 0.016 256)` | `oklch(0.965 0.004 256)` | | `--surface-3` | `oklch(0.292 0.018 256)` | `oklch(0.93 0.006 256)` | | `--border` | `oklch(0.318 0.018 256)` | `oklch(0.892 0.008 256)` | | `--border-strong` | `oklch(0.4 0.022 256)` | `oklch(0.82 0.012 256)` | | `--ink` (body text) | `oklch(0.945 0.006 256)` | `oklch(0.26 0.014 256)` | | `--muted` (secondary) | `oklch(0.722 0.014 256)` | `oklch(0.47 0.016 256)` | | `--faint` (tertiary/decorative) | `oklch(0.58 0.014 256)` | `oklch(0.62 0.016 256)` | | `--accent` / `--live` | `oklch(0.83–0.84 0.16–0.17 152)` | `oklch(0.6 0.15–0.16 151)` | | `--accent-ink` (text on accent) | `oklch(0.2 0.03 256)` | `oklch(0.99 0.01 152)` | | `--danger` | `oklch(0.72 0.18 25)` | `oklch(0.55 0.21 27)` | | `--warn` | `oklch(0.82 0.14 85)` | `oklch(0.66 0.15 75)` | Body and secondary text meet WCAG AA (≥4.5:1) against their surface in both themes; `--faint` is reserved for decorative/large use only. Status never relies on color alone — a running dot pulses and is filled; a dead session is a hollow ring **and** carries an "exited" badge. Tints/glow derive from the accent at low alpha: `--accent-tint` (0.12), `--accent-ring` (focus, ~0.55), `--live-glow` (status halo). ## Typography **Monospace is the UI typeface** (brand identity), system-stack only (offline- safe, zero web-font dependency, embedded clean into the registry binary): - `--font-mono`: `ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", "Roboto Mono", Menlo, Consolas, monospace` — chrome, labels, data, ids, the wordmark. - `--font-sans`: `system-ui` stack — reserved for the few prose strings (empty-state sentences). Fixed rem scale (product register; not fluid/clamped): `--fs-11` 0.6875rem through `--fs-20` 1.25rem, in ~1.12–1.2 steps. Base body `--fs-15` (0.9375rem), line-height 1.45. No display sizes — the largest type is the 1.25rem wordmark. ## Spacing, radius, sizing - Spacing scale: `--sp-1`..`--sp-8` = 4 / 8 / 12 / 16 / 20 / 24 / 32 px. - Radius: `--r-1` 6 · `--r-2` 9 · `--r-3` 14 · `--r-pill` 999. - Touch/row sizing: `--row-h` 46, `--control-h` 38, `--tap` 40 (mobile-first touch targets). ## Motion State and feedback only; 120–240 ms, ease-out (`cubic-bezier(0.22,1,0.36,1)`), no bounce. Tokens `--dur-fast`/`--dur`/`--dur-slow`. - Living signals: brand dot + running-status ripple pulse (the "alive" cue); `connecting` state pill pulses. - Interaction: row hover/focus, button press (1px), input focus ring, chevron rotation, theme-toggle sun/moon swap. - Modal: bottom-sheet slide-up on mobile, scale-pop on ≥600px. - Every animation has a `prefers-reduced-motion: reduce` path (pulses off, transitions near-instant). ## Layout & structure Mobile-first, structural responsiveness (not fluid type). Single `≤520px` breakpoint stacks the header (full-width search, wrapped actions) and drops the daemon chips to their own wrapped line. `≥600px` switches the spawn modal from bottom-sheet to centered dialog. A faint slate grid texture sits on `--bg` as terminal atmosphere (very low contrast, not decoration). ## Components Three surfaces, all DOM built in TS, styled by class: - **Catalog** — sticky blurred header (pulsing wordmark, search, theme toggle, Spawn); status bar (`N sessions · M live` tally + daemon chips); session tree (collapsible folders with counts, leaf rows with status dot, name, `@daemon` + command meta, exited badge, reveal-on-hover kill). Empty states are distinct and teach: waiting-for-daemon, no-sessions, no-search-match. - **Attach** — header (back, `session `, live/closed/error state pill) over a framed dark xterm with a green cursor. - **Spawn modal** — bottom-sheet/dialog form; mono inputs with green focus ring; grouped worktree fieldset. Interactive elements ship default / hover / focus-visible / active / disabled states; the accent green is used only for action, selection, focus, and live status — never decoration. ## Dev review aids (DEV-only, stripped from production) `web/src/mock.ts` + query params seed a representative catalog without a running registry: `?mock` (seed sessions/daemons), `?mock&spawn` (open the spawn modal), `?theme=light|dark` (pin a theme). Guarded by `import.meta.env.DEV`.