DESIGN.md · git:20260802.a2f786e · 2026-08-02 · sha256 e96f9336354de8aa
DESIGN.md git:20260802.a2f786eA
Immutable. This exact content is served forever at /api/v1/blob/e96f9336354de8aa.
# LangAlpha Web Design System — "Quiet Workspace" The visual language of the web app (`web/`): a quiet, charcoal-and-paper research workspace where **content is the interface** and color is reserved for meaning. Tokens are the single source of truth — every value below lives in `web/src/styles/tokens.css`, and every `var(--color-*)` referenced anywhere must be declared there (enforced by `web/src/styles/__tests__/tokenRefs.test.ts`). ## Identity - **Temperature**: dark charcoal layers / light warm paper. No decorative gradients anywhere (`--color-accent-gradient` is intentionally flat). - **Accent**: burnished amber — `#E9954A` (dark) / `#D07D33` (light). It is an **annotation color, not a brand fill** (see Accent discipline). - **Radius**: soft, `--radius: 0.5rem` everywhere; no per-component overrides. - **Depth**: borders first, shadows second (`--shadow-card` is the only card shadow); never glows. ## Palette (both themes live in tokens.css) | Role | Dark | Light | |---|---|---| | Page ground | `hsl(var(--background))` charcoal ~#191919 | white | | Canvas under card grids | = page | `#F5F4F1` warm paper | | Cards | `hsl(var(--card))` ~#202020 | white + `#E8E8E6` hairline | | Elevated (menus, tooltips) | `#2A2B2E` | `#F7F7F6` | | Primary text | `#E6E6E4` | `#1F1F1E` | | Accent (annotation) | `#E9954A` | `#D07D33` | | Primary button | `#ECECEA` bg / `#1A1B1D` text | `#1F1D1A` bg / `#FAF9F7` text | | Profit / loss | `#3FB950` / `#F85149` | `#1A7F37` / `#CF222E` | | Warning | `#D8B04C` | `#B45309` | Background roles (`bg-page` → `bg-canvas` → `bg-card`/`bg-tool-card` → `bg-elevated`, `bg-input`, `bg-popover`) are documented in the comment block above the background group in `tokens.css` — pick by surface role, never by matching a hex. Floating surfaces deliberately do not share one fill yet; don't converge one of them in isolation. ## Typography | Role | Face | How | |---|---|---| | Display / headings | **Sora** | `.title-font` (`--font-ui`, tracking −0.01em) | | UI + body | **Geist** (+ Noto Sans SC for CJK) | `--font-ui` / `--font-content` | | Data, numbers, kickers | **JetBrains Mono** | literal stack at call sites (`'JetBrains Mono', 'Menlo', monospace`) | Tabular numerals (`font-variant-numeric: tabular-nums`) wherever digits align. ## Accent discipline (the rule that makes the system) Amber is **annotation-only**: small icons, dots, chips, thin indicators, the ascii liveness glyph. Never: - a button or CTA fill, wash, ring, or glow; - a **one-sided accent bar** on a card, panel, or blockquote — liveness and state are carried by glyphs + text, and borders stay uniform neutral. The only sanctioned edge markers are **nav selection** (sidebar rail edge, bottom-bar underline, watchlist active row). Semantic color (profit/loss/warning/danger) is separate from the accent and never substitutes for it. ## Status & liveness vocabulary - **Live work** renders the shared ascii `Loader` glyph (amber) + text — never a spinning lucide icon. - **Completed** on dense surfaces (nav subagent rows) renders **no glyph**: absence of the liveness glyph is the done state. Roomier surfaces (status bar) may keep a celebratory check. - Exceptional terminal states keep informative glyphs (error = alert, cancelled = stop, spawned-idle = hollow circle). - Status/icon mappings live in one table per vocabulary (`web/src/pages/ChatAgent/components/taskStatusUi.tsx`); surfaces declare overrides there instead of restating ladders. ## Motion Entrance animations are shared utilities (`web/src/styles/animations.css`): staggered fade-up with `cubic-bezier(0.16, 1, 0.3, 1)`, ≤ 500ms. No ambient or looping decoration; motion signals state change only. Respect `prefers-reduced-motion`. ## Empty states Dot-grid texture is reserved for empty states only — never behind content. ## Working rules 1. New colors enter through `tokens.css` (both themes, plus the role comment) — never a hex literal in a component. Canvas painters that can't read CSS variables go through `web/src/lib/themeTokens.ts`. 2. Style both themes in the same change; the light theme is a first-class surface, not an inversion. 3. Buttons are quiet: `--color-btn-primary-*` for primary, neutral borders for secondary. `--color-text-on-accent` exists solely for text sitting on an amber accent fill (rare, annotation-scale). 4. Liveness = glyph + text, per the vocabulary above. 5. When in doubt, remove decoration rather than add it.