# music — frontend spec

React (vite). Dark theme by default, blue accent. Web UI is v1; backend is API-first so a mobile app can reuse the same endpoints later.

## Theme
- **Dark by default.** Blue accent (`--accent: #2D7FF9` or similar; pick one and define tints like kdrive's accent system).
- Reuse the design-token approach from kdrive (CSS vars, Geist font). This is a distinct app with its own palette but shared visual language.
- Animations: tasteful — now-playing bar slide-up, queue/lyrics panel slide-in, album art crossfade on track change, subtle hover/press states, progress bar smoothing, shuffle/loop button state transitions.

## Layout
- **Left sidebar**: nav (Artists / Albums / Songs / Favorites / Recently played / Vibe), profile/avatar (opens settings modal).
- **Main**: list/grid views per section.
- **Bottom now-playing bar** (persistent): cover thumb, title, artist + feats, prev / play-pause / next, volume slider, shuffle toggle, loop toggle (off → all → one), progress/seek bar, lyrics button, queue button.

## Views
### Artists
- List/grid of artists (image if enriched, else monogram). Click → artist page: header (image, name, genres), albums, all tracks.

### Albums
- Grid of album covers (sort: name / year / artist). Click → album page: cover, title, artist, year, ordered tracklist, play / shuffle album.

### Songs
- Virtualized list (library can be large): title, artist, album, duration, play count, favorite toggle. Sort by title/artist/added/plays. Fuzzy search box.

### Favorites / Recently played
- Favorites: from `/api/favorites`. Recently played: from `/api/history`.

### Vibe
- "Start a vibe" from current track / an artist / a genre. Calls `/api/vibe`. Shows the mixed queue; recommendation candidates are visually tagged subtly (small dot/"suggested") but interleaved, not grouped.

## Player controls (now-playing bar)
- **Global shuffle button** (prominent, maybe in topbar or sidebar too): one tap → `GET /api/shuffle/all` → loads entire library shuffled as queue, starts playing. The headline feature.
- **Prev / Play-Pause / Next**.
- **Loop toggle**: cycles off → loop-all (current queue/album) → loop-one (single track). Distinct icons per state.
- **Volume slider**.
- **Seek bar**: draggable, shows elapsed/total.
- **Cover + title + artist + feats**: feats rendered subtly after artist (e.g. "Artist · feat. X, Y").
- **Lyrics button**: opens lyrics panel.
- **Queue button**: opens queue panel.

## Playback engine (client)
- Web Audio / `<audio>` with `/api/stream/{id}` (range-enabled).
- Playback modes from prefs:
  - **basic**: sequential.
  - **gapless**: preload next track, start without silence (dual audio elements or Web Audio buffering).
  - **crossfade**: fade out current / fade in next over `crossfade_ms`.
  - **interstitial**: play the configured interstitial track between every song (the gag mode).
- Log plays to `POST /api/history` with `completed` once threshold reached (≥50% or final 10s).

## Lyrics panel
- Slide-in panel. Calls `/api/lyrics/{track_id}`.
- **Synced** (LRC): auto-scroll, highlight active line, tap a line to seek.
- Falls back to plain text or "no lyrics found".
- Provider chain handled server-side (Musixmatch → LRCLIB), frontend just renders.

## Queue panel
- Slide-in. Shows **prev tracks** (history within session) and **next tracks** (upcoming).
- Reorder (drag), remove, jump-to.
- Queue resets on session end (no persistence — per the decision).

## Favorites / dislike
- Heart toggle on tracks/now-playing → `POST /api/favorite/{id}`.
- **Dislike** control (e.g. thumbs-down in now-playing context menu) → `POST /api/dislike/{id}`. Track hides immediately. A toast confirms.
- When a dislike reminder fires (server sweep), the app surfaces a toast: *"You disliked '<track>' — deleting in 24h. Undo?"* with an Undo action calling `DELETE /api/dislike/{id}`. (Also delivered via ntfy out-of-app.)

## Settings modal (avatar click)
- **Appearance**: theme (dark default, allow light), blue accent + maybe a couple alt accents.
- **Playback**: mode (basic/gapless/crossfade/interstitial), crossfade ms (when crossfade), interstitial track picker (when interstitial).
- **Recommendations**: enable/disable vibe acquisition, max candidates (read-only display of the cap), Last.fm key field, MusicBrainz UA.
- **Library**: reindex button (`POST /api/library/reindex`), enrich button (`POST /api/enrich`).
- Persist via `/api/prefs`.

## Search
- Global fuzzy search (topbar) → `/api/search`, grouped results: Artists / Albums / Songs. Handles Cyrillic/mixed scripts (display original, match normalized).

## API-first note
- All state lives behind the documented endpoints so a future mobile client reuses them. No frontend-only business logic that the API can't reproduce. Keep auth simple for v1 (WG-gated, like the rest); leave room for token auth later for mobile over the tunnel.

## Deploy
- Same pattern as kdrive: vite build → served by the FastAPI app (or its own static mount). nginx vhost via the panel: `music.kvmx.ru` (replaces the swingmusic/navidrome entry) → `http://localhost:<port>`.
