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>
7.5 KiB
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 underweb/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 getletter-spacing: 0.12–0.22em. - Scale (rem): masthead
clamp(2.6, 6vw, 3.7), job title1.42, tile value2.5, body1.0, section head0.78mono, meta/labels0.62–0.72mono.text-wrap: balanceon headings; running text ~65ch;font-variant-numeric: tabular-numson 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/localwiring, exposed as a CSS variable on<body>. - App shell:
web/src/app/layout.tsxgains the shared chrome — a<Masthead/>+<ContentsNav/>server components wrapping{children}in the editorial column, plus a small client theme toggle that stampsdata-themeon<html>and persists tolocalStorage. 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):
- Fonts +
globals.csstokens (both themes) + base reset, wired into the root layout. - Component library (
_ui/) +design.cssclasses: masthead, contents-nav, theme-toggle, stat-tiles, section-header, job-row, status-chip, progress-bar, pressed-list. - App shell in
layout.tsx(masthead + nav + column + toggle). - 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-labels, button text ("Save", "Search"), andaria-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-motionhonored, 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.