diff --git a/docs/superpowers/specs/2026-06-27-desktop-visual-refresh-design.md b/docs/superpowers/specs/2026-06-27-desktop-visual-refresh-design.md new file mode 100644 index 0000000..51384bd --- /dev/null +++ b/docs/superpowers/specs/2026-06-27-desktop-visual-refresh-design.md @@ -0,0 +1,161 @@ +# Desktop Visual Refresh + Theme Pack + +**Date:** 2026-06-27 +**Status:** Approved (design phase) +**Scope:** Desktop (egui) UI only. Web UI is out of scope. + +## Goal + +Lift the desktop UI's out-of-box experience and expand its identity options. Two +prongs: (1) add 12 new themes, (2) refresh the whole look โ€” default themes +included โ€” so the app no longer reads as stock egui. + +## Current state (findings from code survey) + +- `theme.rs` ships **7 themes**: Dark, Light, Dracula, Trans, Emo:Nocturnal, + Emo:Coffin, Emo:Scene Queen. Dark and Light are **stock + `egui::Visuals::dark()`/`light()` with zero customization**; the other five + are fully hand-tuned. +- Video list is **flat horizontal rows** (thumb left, text right) โ€” no card + background, no rounded corners. Reads as a spreadsheet, not a media library. +- **Accent colors are hardcoded** throughout `app.rs`, ignoring the active theme: + - selected ring: `Color32::from_rgb(120, 170, 230)` (`app.rs:4225`) + - playing ring: `Color32::from_rgb(110, 200, 110)` (`app.rs:4232`) + - bulk-checked ring: `Color32::from_rgb(180, 130, 240)` (`app.rs:4239`) + So even in a hand-tuned theme, selection always glows stock blue. +- Placeholder thumbnails are inconsistent: channel cards use `๐Ÿ“บ` on + `from_gray(30)` (`app.rs:3972`); video cards use `โ–ถ` on `from_gray(38)` + (`app.rs:4210`). +- Density is very high (~12px text, ~10px padding) with no typographic + hierarchy โ€” section headers read the same weight/size as body text. + +## Design + +### 1. Theme pack โ€” 12 new themes + +Add twelve fully-tuned `Visuals` to `theme.rs`, each defining panel fills, +per-state widget strokes (noninteractive/inactive/hovered/active/open), +selection bg+stroke, hyperlink color, warn/error colors โ€” the full set the +existing five themed variants already tune. Catalog grows from 7 to **19**. + +| # | Category | Theme key | Identity | +|---|----------|-----------|----------| +| 1 | Neon | `cyberpunk` | Magenta + electric cyan on black. Hacker HUD. | +| 2 | Neon | `synthwave` | Sunset gradient (hot pink โ†’ orange โ†’ purple) on deep indigo. | +| 3 | Neon | `vaporwave` | Pastel pink + cyan + lavender on deep plum. Aesthetic. | +| 4 | Goth | `cemetery-moss` | Weathered stone + mossy green + bone. Organic, ancient. | +| 5 | Goth | `vampire` | Deep wine burgundy + antique gold + black. Regal. | +| 6 | Goth | `witching-hour` | Midnight indigo + moonlight silver + arcane violet. Mystical. | +| 7 | Dev | `nord` | Arctic blues & greys. Cold, legible. | +| 8 | Dev | `gruvbox` | Warm earthy retro groove. Contrast-focused. | +| 9 | Dev | `tokyo-night` | Tokyo city lights. Blue/purple, clean. | +| 10 | Cozy | `paper` | Aged paper + sepia ink. Quiet reading-room. | +| 11 | Cozy | `honey` | Warm amber + gold + cream. Golden-hour. | +| 12 | Cozy | `candlelight` | Dim warm glow + toasted brown. Evening, intimate. | + +Deliberate distinctness: Vampire is kept visually separate from the existing +Emo:Coffin (burgundy+gold vs blood-red+black); Witching Hour separate from +Emo:Nocturnal (silver+violet vs hot-pink). Dracula is not re-added (already +shipped). + +Each theme **must** export three semantic accent colors (see ยง2) in addition to +the standard `Visuals` fields. + +### 2. Theme-aware semantic accents (bug fix) + +Today's hardcoded rings ignore the theme. Replace with named semantic accents +that each theme provides: + +- `accent` โ€” selection / focus (replaces the `120,170,230` blue) +- `success` โ€” playing / watched (replaces `110,200,110` green) +- `warning` โ€” bulk-selection highlight (replaces `180,130,240` purple) + +**Mechanism:** egui `Visuals` has no field for arbitrary semantic accents, so +expose them via a `ThemeAccents { accent: Color32, success: Color32, warning: +Color32 }` struct in `theme.rs`, with a `pub fn accents_for(name: &str) -> +ThemeAccents` lookup. `App` holds a `theme_accents: ThemeAccents` field, +recomputed at every site that already calls `theme::apply(ctx, name)` (App +construction + the Settings theme-picker callback). All paint code that +currently hardcodes a color reads from `self.theme_accents.accent` / +`.success` / `.warning` instead. This makes the new themes look correct and +also makes the existing Dracula/Trans/Emo themes' selection rings match their +palettes. + +### 3. View-mode toggle: List / Card / Grid + +Three render paths for the video list, switchable live via a toolbar toggle. + +- **List** โ€” current horizontal rows, polished (theme-aware rings, unified + placeholder, refined spacing). The lowest-disruption default. +- **Card** โ€” same horizontal layout, but each row is a rounded card on the + faint-bg fill with a hover lift. Media-app feel without sacrificing density. +- **Grid** โ€” YouTube/Plex-style vertical cards (thumb on top, title + meta + below), responsive column count derived from window width. Most visual. + +**Persistence model (global default + per-view override):** + +- **Global default** stored in `config.toml` (new + `[ui] default_view_mode = "list|card|grid"`) and seeded onto `App` at + construction + on settings-save. Mirrors the existing `card_density` setting's + five-touchpoint shape (config โ†’ settings UI in `app.rs` โ†’ seeded on `App`). +- **Per-view override** stored in an `App` field: `HashMap`. The toolbar toggle writes to this map for the current + `SidebarView`. A view with no entry falls back to the global default. +- Toolbar control is a 3-segment toggle (โ˜ฐ List / โ–ข Card / โ—ซ Grid) in the video + list header. + +### 4. Default Dark / Light polish + +Promote Dark and Light from stock `egui::Visuals::dark()/light()` to fully +hand-tuned palettes, matching the level of care in the themed variants: + +- **Dark** โ€” true near-black panel fill, cool accent (e.g. soft steel-blue), + tuned widget strokes per state. +- **Light** โ€” warm off-white panel fill (not pure white), slate accent, tuned + widget strokes per state. + +### 5. Typography & spacing refresh + +Modest adjustments via egui's built-in proportional font (no new font files): + +- Base body text: ~12px โ†’ ~13px. +- Introduce hierarchy: distinct sizes/weights for heading / section / card + title / metadata. +- Standardize card internal padding and inter-row spacing. +- Tighten the metadata line (channel ยท id ยท date ยท duration ยท size) into a + consistent rhythm. + +### 6. Unify placeholder thumbnails + +Replace the two divergent placeholder styles (๐Ÿ“บ on gray-30 for channels, โ–ถ on +gray-38 for videos) with **one consistent style**: a theme-tinted gradient +background (derived from the theme's faint/noninteractive bg) plus a single +subtle glyph, scaled by `card_density`. Used by both channel and video cards. + +## Architecture impact + +- `theme.rs` โ€” gains 12 theme fns + a `ThemeAccents` struct + a + `pub fn accents_for(name: &str) -> ThemeAccents` helper. +- `app.rs` โ€” gains `view_mode`/`view_mode_overrides` fields on `App`; the video + list render path branches on `ViewMode::List/Card/Grid`; all hardcoded accent + colors read from `self.theme_accents.*`; placeholder paint unified. +- `config.rs` โ€” gains `[ui] default_view_mode` field + `Default` + + `default_with_dir()`. +- Settings screen (`app.rs`) โ€” gains view-mode selector + theme picker entry + for the 12 new names. + +Follows the documented five-touchpoint settings shape for the new config field. + +## Out of scope + +- The "Newest" web sort bug โ€” separate work item. +- Web UI visual changes โ€” web has its own CSS and is not touched here. +- New font files / custom typefaces โ€” deferred; uses egui's built-in + proportional at adjusted sizes. + +## Open questions for implementation + +None โ€” all design decisions approved during brainstorming: +- 12 themes (3 neon + 3 goth + 3 dev + 3 cozy): approved. +- List/Card/Grid toggle with global default + per-view override: approved. +- Desktop-only scope: approved.