Art direction for the Document Intelligence Console. This file is the critique contract. Token
values live only in app/globals.css. A review that wants a different look (landing-page chrome,
Awwwards motion, a new palette) is out of scope unless this file changes first.
Calm, dense, operator-facing. Dark is the primary art direction; light is a first-class peer that uses the same token names. One accent, used sparingly for primary actions and the active state. Everything else is a neutral grey ladder so document content and extracted data stand out.
This is a job console (upload → stream → review), not a marketing site. Do not add decorative illustration, hero copy, or animation that competes with status and fields.
html[data-theme="dark"|"light"] is the only theme switch. Components must not branch on theme.
| Preference | Behavior |
|---|---|
system (default) |
Follows prefers-color-scheme |
light / dark |
Locked; stored in localStorage key doc-intel-theme |
- Default rendered theme before JS:
dark(data-theme="dark"on<html>). - A blocking script in
ThemeScriptapplies the stored / system choice before paint. color-schemeon:root/[data-theme]must match so native controls stay consistent.--paperis always a light page (PDF canvas). It does not invert with the chrome.
Color (both themes must define every name): --bg, --surface, --surface-2, --border,
--text, --text-muted, --accent, --accent-contrast, --accent-soft, --scrim, --shadow,
--paper, --status-queued, --status-processing, --status-partial, --status-completed,
--status-failed.
Spacing: --space-1..6 on a 4px base.
Radius: --radius (8px), --radius-sm (4px).
Type: --font-sans (system stack), --text-xs..2xl.
- Tokens only. No raw hex,
rgb(), orpxin components except: (a) layout math that cannot be a token (virtualizer offsets), (b)0/none. New colors go inglobals.cssfirst. - Both themes. A change that looks correct in dark and broken in light is incomplete. Check
--texton--surface,--text-mutedon--bg, and--accentas a link/button. - Status is never color alone. Use
StatusBadge(dot + label). Overlay boxes may use status color only if they also have an accessible name (aria-label/title). - Focus. Every interactive control has a visible
:focus-visiblering (--accent). - Contrast. Target WCAG AA in both themes for text, badges, and the theme toggle.
- No second design system. Do not introduce a new card style, font, or radius to “match a
mock” unless
design.mdand spec 0003 are updated in the same change. - Density over decoration. Prefer one primary button, token borders, and the existing
Panel. Do not add drop shadows exceptvar(--shadow)on floating chrome (walkthrough, tour pill). - Motion. Transitions stay under ~200ms; honor
prefers-reduced-motionfor looping motion (walkthrough pulse already does). - Labels are sentence case in the body face. Panel headers, field keys, and review group
labels are plain text, never tracked capitals or small caps. No
text-transform: uppercaseorletter-spacingon a label. - No capability pills on working surfaces. A panel does not badge itself with its implementation ("Resumable", "Live SSE", "Virtualized", "Observability"). The behavior speaks; the header only names the content. (Walkthrough step badges are tour copy, not surface chrome.)
- State is words, not decorative glyphs. Connection and job state read as words
("Live" / "Reconnecting"), never a bare
●/○. No arrow glued to link text (Back, not← Back).StatusBadge's dot is allowed because it is paired with a text label (rule 3).
Button, Card, Panel, StatusBadge, ProgressBar, FieldRow, SourceChip. Typed, imported
via @/ui, styled in src/ui/ui.module.css. Theme chrome: @/theme/ThemeToggle + ThemeScript.
SourceChip names a field's origin (document + page) and selects it to highlight the viewer box.