React + Vite + TypeScript SPA covering the full ABS feature set (library browsing, item detail, metadata/cover editing, podcasts, player with session sync, admin: users/libraries/scanner/server settings). Dev uses a dynamic CORS proxy; production is served by server/index.mjs (static + reverse proxy to ABS_URL). Includes systemd unit and installer under deploy/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.1 KiB
Shelfless — Design System (MASTER)
Single source of truth for visual + interaction design. Grounded in ui-ux-pro-max (Podcast Platform #27 + Music Streaming #45 + Book & Reading Tracker #135 → Dark Mode (OLED) + Minimalism with a literary-warm note) and the project brief.
When building any page: read this file first. A page-specific override under
design-system/pages/<page>.md (if present) takes precedence over these rules.
Principles
- Dark theme by default. One consistent accent color, never competing accents.
- Generous whitespace, clear type hierarchy, content (covers) first.
- Subtle micro-animations only (cover hover, player slide-in). No motion overload.
- NO AI-slop: no purple gradients, no glassmorphism overload, no card-in-card nesting.
- NO online badge, user counter, activity feed, or "welcome back" banner.
- Reference feel: Plex overview (but cleaner), Spotify player (but less cluttered).
Typography
Both verified as Google variable fonts. No Inter / Roboto / Space Grotesk.
- Headings / titles / section headers: Fraunces (serif, literary, optical sizing).
- Body / UI: Hanken Grotesk (humanist grotesque, highly readable).
- Numbers in player timers, durations, data tables:
font-variant-numeric: tabular-nums. - Type scale (px): 12 · 14 · 16 · 18 · 24 · 32 · 48. Body 16px, line-height 1.5–1.6.
- Weight hierarchy: headings 500–700, body 400, labels/nav-active 500.
- Loaded locally via
@fontsource-variable/fraunces+@fontsource-variable/hanken-grotesk(no hard CDN dependency);font-display: swap.
Color tokens (CSS custom properties on :root, dark default)
All foreground/background pairs verified ≥4.5:1 (text) / ≥3:1 (large/UI).
| Token | Value | Use |
|---|---|---|
--bg |
#0C0A09 |
app background (warm near-black) |
--surface |
#1C1917 |
cards, sidebar, panels |
--surface-2 |
#292524 |
raised surfaces, player bar, inputs |
--border |
#3F3B38 |
borders/dividers (visible in dark) |
--text |
#FAFAF9 |
primary text |
--text-muted |
#A8A29E |
secondary text (≥4.5:1 on --bg) |
--accent |
#F59E0B |
accent (default Amber) |
--on-accent |
#0C0A09 |
text/icon on accent fills |
--destructive |
#EF4444 |
delete / danger |
--success |
#22C55E |
progress / completed |
Accent is themeable via data-accent on <html>; options in Settings:
amber#F59E0B (default) ·teal#14B8A6 ·coral#FB7185 ·green#22C55E ·blue#3B82F6.
Tailwind maps bg, surface, surface-2, border, text, text-muted, accent,
on-accent, destructive, success to these variables (colors in tailwind.config).
Spacing, radius, elevation
- 4/8px spacing rhythm. Section spacing tiers 16 / 24 / 32 / 48.
- Radius ~10–12px on cards/buttons/inputs (
--radius: 0.625rem). 0px never; no huge pills. - Elevation: one consistent shadow scale; modals/player use a slightly stronger shadow. Avoid random shadow values.
Motion (respect prefers-reduced-motion)
- Durations 150–300ms; enter ease-out, exit ~60–70% shorter.
- Animate only
transform/opacity(never width/height/top/left). - Cover hover:
scale(1.0 → 1.03)+ subtle lift/shadow. - Player bar: slide-up + fade on first appearance.
- Skeleton shimmer while loading (>300ms). Visible 2px accent focus ring.
- Max 1–2 animated elements per view.
Component guidelines
- MediaCard: cover (book 2:3 / podcast 1:1), rounded, hover-lift. Progress strip at
bottom in
--accent, shown only when progress > 0%. Subtle check overlay when finished. - Sidebar:
--surfacebg; active item = accent indicator bar + medium weight; always icon + label (never icon-only); collapsible to icon mode; on mobile → bottom nav (≤5). - PlayerBar: fixed bottom,
--surface-2; tabular-nums times; draggable progress bar with accent only on the playhead/fill; touch targets ≥44px. - Grid: responsive
auto-fill minmax(...),gap-6. Virtualize only if a single page renders 50+ items (infinite scroll paginates, so usually fine). - Modal: scrim 40–60% black; animate from trigger (scale+fade); Esc/close affordance; focus trap + return focus; confirm before destructive/unsaved-dismiss.
- Table (admin users):
aria-sort, tabular-nums, row hover; destructive actions visually separated from normal ones. - Forms: visible labels (not placeholder-only), error below field, helper text, loading state on submit, password show/hide toggle, autofocus first invalid field.
Icons
Lucide React only. Consistent stroke width; one icon size scale (sm/md=24/lg). No emoji.
Anti-patterns (do not do)
Purple/indigo gradients · glassmorphism overload · card-in-card-in-card · emoji icons · online/user-count/activity-feed/welcome banners · icon-only nav · hover-only interactions · animating layout properties · gray-on-gray low contrast · raw hex in components (use tokens).