A self-hosted household ledger.
quantum DESIGN.md
6.4 kB
Markdown
at main

Design #

Visual system for Quantum. Register: product. Personality: calm precision — ink on good paper, composed and legible. All colors OKLCH.

Theme #

Scene: a couple reviews the month on a laptop at the kitchen table under evening lamplight; each also glances at balances on a phone in daylight. Both themes are first-class: light is the canonical "paper statement" reading, dark is the evening companion. Default follows prefers-color-scheme, with a data-theme override on <html> persisted per user.

Identity holds across themes: warm ink on warm paper (light), warm chalk on warm charcoal (dark). Never pure black or white.

Color #

Strategy: Restrained. Tinted warm neutrals carry the interface; one spruce accent under 10% of any surface; semantic and chart colors appear only where data demands them.

Neutrals (warm, hue ≈ 85) #

Token Light Dark
--bg oklch(0.975 0.004 85) oklch(0.19 0.006 85)
--bg-raised oklch(0.99 0.003 85) oklch(0.225 0.007 85)
--bg-sunken oklch(0.955 0.005 85) oklch(0.165 0.006 85)
--text oklch(0.245 0.012 85) oklch(0.92 0.008 85)
--text-muted oklch(0.49 0.012 85) oklch(0.70 0.01 85)
--text-faint oklch(0.62 0.01 85) oklch(0.55 0.01 85)
--border oklch(0.90 0.006 85) oklch(0.30 0.008 85)
--border-strong oklch(0.82 0.008 85) oklch(0.38 0.01 85)

Accent — spruce (hue ≈ 190, deliberately not money-green, not fintech navy) #

Token Light Dark
--accent oklch(0.46 0.07 190) oklch(0.80 0.08 185)
--accent-hover oklch(0.40 0.075 190) oklch(0.85 0.075 185)
--accent-bg oklch(0.94 0.02 190) oklch(0.27 0.03 190)
--on-accent oklch(0.98 0.005 190) oklch(0.20 0.02 190)

Semantic (paired with sign/shape, never hue alone) #

Token Light Dark Use
--pos oklch(0.52 0.10 175) oklch(0.78 0.11 170) income, inflow
--neg oklch(0.53 0.13 30) oklch(0.74 0.13 30) expense, outflow, destructive
--warn oklch(0.60 0.11 75) oklch(0.80 0.11 80) staleness, needs-attention
--warn-bg oklch(0.95 0.03 85) oklch(0.26 0.03 80) banner fills

Red (--neg) appears in chrome only when money or data is at risk; an expense figure in a table is plain ink, not red.

Chart colors #

  • --chart-1 oklch(0.55 0.13 180) light / oklch(0.65 0.12 190) dark — the single-series mark color (net worth line). Validated ≥0.1 chroma and 3:1 contrast per surface. (--chart-2…7 remain as spare series tokens.)
  • Per-category palette --cat-0 … --cat-11: each category owns a stable palette index (categories.color_index, assigned at creation, never recomputed from position). Generated with poline via scripts/generate-category-palette.ts — a closed-loop hue journey anchored at spruce/amber/plum, normalized into OKLCH L 0.54–0.62, C ≥ 0.10 so one set passes the lightness band, chroma floor, and 3:1 contrast on BOTH surfaces (no dark override). Neighboring slots are interleaved to maximize hue distance; charts additionally separate fills with 2px surface gaps and always carry a legend, so identity never rests on hue alone. Neutral (--text-faint) is reserved for "Other" and "Uncategorized" — never a category.

Typography #

  • UI + numerals: Inter Variable (bundled via @fontsource-variable/inter, self-hosted, no CDN). Fallback system-ui.
  • Monospace (DIDs, rule patterns, tokens): ui-monospace, 'Cascadia Mono', Consolas, monospace.
  • Tabular figures are law: any column of money or dates sets font-variant-numeric: tabular-nums.
  • Scale (1.25 ratio): 13 / 14 (body) / 16 / 20 / 25 / 31 px as --text-xs/-sm/-md/-lg/-xl/-2xl. Weights: 400 body, 500 emphasis & nav, 600 headings & figures. Line height 1.5 body, 1.2 headings.
  • Body measure ≤ 70ch. Money aligns right; descriptions align left.

Spacing, shape, elevation #

  • 4px base: --space-1..8 = 4, 8, 12, 16, 24, 32, 48, 64.
  • Radius: --radius-sm 5px (inputs, chips), --radius-md 8px (surfaces, menus), full for provenance badges.
  • Elevation by border first: 1px --border + --bg-raised. Shadows only on overlays (menus, dialogs): 0 4px 16px oklch(0 0 0 / 0.10) light, / 0.35 dark. No decorative shadows on static content.

Motion #

120ms (micro: hover, check, chip) and 180ms (panels, menus), both cubic-bezier(0.22, 1, 0.36, 1). Nothing bounces, nothing floats, no layout property animation. prefers-reduced-motion: reduce collapses all to 0ms.

App shell #

  • Desktop (≥880px): fixed left sidebar, 216px. Wordmark "Quantum" top; nav: Dashboard, Ledger, Reports, Accounts, Settings; footer shows sync status line ("Synced 6:02 AM") and the signed-in user's handle. Content area scrolls independently; reports/forms cap at 1040px, the ledger runs fluid.
  • Mobile (<880px): bottom tab bar with the same five destinations; page title in a slim top bar. No hamburger menus.
  • Login is shell-less: a centered column on --bg, wordmark, one input, one button.

Component conventions #

  • Provenance badge: pill, --bg-sunken fill, 12px text: ⚙ rule / person's handle / ↻ carried. Click opens event history.
  • Banners (connection health): full-width strip under the top of content, --warn-bg fill, 1px --warn border (all four sides), plain sentence + one link out. No icons larger than the text.
  • Tables: row hover --bg-sunken, 1px hairline row separators, header 13px/500/--text-muted, uppercase avoided.
  • Empty states: one sentence + one action, set in --text-muted. No illustrations.
  • Focus: 2px --accent outline, 2px offset, on every interactive element.