docs: Pressing Plant frontend design system spec (Phase 1)

Design language + Phase 1 plan (design system + app shell + home page).
Comp signed off. Liner Notes / record-label identity, chrome-only
metaphor, manila+spruce palette, Fraunces/mono, state-as-form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan
2026-07-12 13:53:52 +02:00
parent 8bbc1db32f
commit 4655d815ee
@@ -0,0 +1,128 @@
# Lyra frontend: the "Pressing Plant" design system
**Date:** 2026-07-12
**Status:** approved (design comp signed off), implementing
**Comp:** https://claude.ai/code/artifact/af6f9654-619d-4245-b3dd-602a6427934a
## Intent
Give Lyra a real visual identity instead of unstyled raw HTML. Direction:
**Liner Notes / "Pressing Plant"** — the app presented as *a record label's internal
catalog for an audience of one*. The acquisition pipeline reads as a pressing plant.
The metaphor lives in the **chrome only** (masthead, section names, catalog/matrix
numbers, deadwax etch styling); **functional labels stay literal and legible**
(Downloading, Verifying, Needs attention) so nothing is obscured. Single-user homelab
tool → license to be bold.
This is a **tool, not a document**: information design first (summary before detail,
state encoded as form — severity stripe + chip — not just prose), editorial typography
second.
## Design language (the reusable reference)
### Color — deliberately off the AI-editorial cliché (no cream, no vermilion)
Two-color-print feel: manila/kraft paper + warm ink + one bold **deep spruce** accent.
Tokens are CSS custom properties on `:root`; both themes get equal care. Semantic status
colors are **separate from the accent**.
| Token | Day (light) | Night (dark) | Use |
|---|---|---|---|
| `--paper` | `#E7DFCC` | `#14130F` | ground |
| `--paper-2` | `#DED5BE` | `#1D1B16` | recessed panel |
| `--ink` | `#1B1A17` | `#E9E1CD` | primary text |
| `--graphite` | `#726D5F` | `#938C7B` | secondary text / mono meta |
| `--rule` | `rgba(ink,.16)` | `rgba(ink,.16)` | hairlines |
| `--rule-2` | `rgba(ink,.30)` | `rgba(ink,.28)` | stronger rules |
| `--accent` | `#1F5138` | `#5BB487` | the one bold move; links, active nav, "pressed" |
| `--accent-ink` | `#F3EEDF` | `#10120F` | text on accent |
| `--live` (semantic) | `#9A6B1E` amber | `#D2A24E` | work in progress |
| `--alert` (semantic) | `#99392B` clay | `#D8695A` | needs attention (errors only) |
Theme mechanics: define tokens on `:root`; redefine under
`@media (prefers-color-scheme: dark)`; **also** redefine under
`:root[data-theme="light"]` and `:root[data-theme="dark"]` so an explicit in-app toggle
wins in both directions. Style components through tokens only, never inside the media
query. Faint paper grain via a tiled radial-gradient. **Sharp corners** (radius 02px) —
letterpress/label feel, and it dodges the `rounded-lg` cliché.
### Type — serif/mono tension is the concept
- **Display + editorial:** **Fraunces** (OFL variable serif; soft, optical, warm),
self-hosted via `next/font/local` (woff2 vendored under `web/src/fonts/`, subset to
latin). The comp used a Palatino system stack as a stand-in; the real app ships Fraunces.
- **Machine voice (all metadata, labels, data, progress):** a system **mono** stack
(`ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace`) — zero weight,
reliable. Uppercase mono labels get `letter-spacing: 0.120.22em`.
- Scale (rem): masthead `clamp(2.6, 6vw, 3.7)`, job title `1.42`, tile value `2.5`,
body `1.0`, section head `0.78` mono, meta/labels `0.620.72` mono. `text-wrap: balance`
on headings; running text ~65ch; `font-variant-numeric: tabular-nums` on all aligned digits.
### Motifs
Masthead wordmark + tagline + live worker dot + "Pressing № NNNN" catalog mark;
contents-bar nav (hairline top/bottom, active item underlined in accent); summary tiles
(hairline grid); dept section headers (mono small-caps + rule fill + count); tracklist
job rows with a left **severity stripe**; mono **status chip**; thin ruled **progress bar**
(indeterminate variant for search); **deadwax matrix** etch line for pressed items
(`Lyra·A0430 — FLAC 24/48`); colophon footer.
### Motion (restrained)
Live-dot pulse; indeterminate progress slide; hover underlines. All gated by
`@media (prefers-reduced-motion: reduce)`. No scattered effects.
## Architecture
Hand-rolled CSS, no framework (matches the lightweight Next 15 / React 19 stack).
- **`web/src/app/globals.css`** (new) — token definitions (both themes), base/reset,
paper grain, focus-visible, reduced-motion. Imported once in the root layout.
- **`web/src/app/design.css`** (new, or co-located module) — the component classes
(masthead, contents nav, tiles, dept header, job row, chip, bar, pressed list, buttons,
inputs). Plain global classes keep it simple and shared across pages.
- **Fonts:** `web/src/fonts/` vendored Fraunces woff2 + `next/font/local` wiring, exposed
as a CSS variable on `<body>`.
- **App shell:** `web/src/app/layout.tsx` gains the shared chrome — a `<Masthead/>` +
`<ContentsNav/>` server components wrapping `{children}` in the editorial column, plus a
small client **theme toggle** that stamps `data-theme` on `<html>` and persists to
`localStorage`. Individual page files stop repeating the ad-hoc nav line.
- **Components** live under `web/src/app/_ui/` (e.g. `masthead.tsx`, `contents-nav.tsx`,
`theme-toggle.tsx`, `stat-tiles.tsx`, `job-row.tsx`, `status-chip.tsx`, `progress-bar.tsx`,
`section-header.tsx`). Each: one clear job, styled via the shared classes, driven by props.
## Rollout (this spec covers Phase 1; later phases are their own plans)
**Phase 1 — system + shell + flagship (this plan):**
1. Fonts + `globals.css` tokens (both themes) + base reset, wired into the root layout.
2. Component library (`_ui/`) + `design.css` classes: masthead, contents-nav, theme-toggle,
stat-tiles, section-header, job-row, status-chip, progress-bar, pressed-list.
3. App shell in `layout.tsx` (masthead + nav + column + toggle).
4. Rebuild the **home / "The Floor"** page (`app/page.tsx` + `queue.tsx`) on the real
queue data: summary tiles, on-the-press job list (state → stripe+chip+bar, real pipeline
stages mapped to literal labels: resolving/searching/downloading/verifying/tagging;
needs-attention shows recovery actions), recently-pressed list, empty state.
**Phase 2 (later):** restyle Artists, Artist detail, Discover, Discover/artist preview,
Wanted onto the same system.
**Phase 3 (later): Settings redesign** — the paths + auth pain point. Editorial form
treatment for credentials (Qobuz, slskd) with clear "set/unset" affordances; a **Library**
section surfacing the configured music path. NOTE/constraint: `MUSIC_DIR` is a container
mount, not runtime-editable from the UI (changing a string can't remount a Docker volume),
so paths are **shown with explanation** (and any genuinely runtime-config paths made
editable); credentials and `discover.*`/`monitor.*`/`scan.chunkSize` become first-class,
well-labelled controls. Scoped in its own spec when we reach it.
## Testing & verification
- The existing web suite (vitest, 71 tests) tests **routes/logic**, not visuals; a restyle
must keep it green. **Preserve accessible names / test hooks** — existing `aria-label`s,
button text ("Save", "Search"), and `aria-label="scan result"` etc. Update a test only
where copy/behavior intentionally changes, and in the same commit.
- New interactive component (theme toggle) gets a small test (persists + toggles
`data-theme`).
- **Visual verification:** drive the real app (playwright) and eyeball the home page in
**both themes** + a mobile width before calling Phase 1 done — screenshots, not just
"it compiles".
- Accessibility: visible `:focus-visible`, `prefers-reduced-motion` honored, contrast
legible on both grounds, keyboard-navigable nav + toggle.
## Out of scope (Phase 1)
Phases 23 pages; any backend/data changes (home reads existing queue/config data as-is);
new fonts beyond Fraunces + system mono; charts.