# 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 0–2px) — 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.12–0.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.62–0.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 ``. - **App shell:** `web/src/app/layout.tsx` gains the shared chrome — a `` + `` server components wrapping `{children}` in the editorial column, plus a small client **theme toggle** that stamps `data-theme` on `` 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 2–3 pages; any backend/data changes (home reads existing queue/config data as-is); new fonts beyond Fraunces + system mono; charts.