feat(ui): rework every page for comfort, and make a seeded Vibe play its seed
A pass over the whole app against the Ethos laws, then a focused pass on Vibe with the operator reviewing each change. Across the app: - The player bar restores the last track it played, paused at zero, so a fresh tab opens on where the listener was instead of "nothing playing". - Track titles link to their album, matching the artist links beside them. Playback stays on the artwork tile; a title that played was the surprise. - The search field is bg-bg2. Tailwind cannot alpha-modify these var() colors, so bg-surface0/70 emitted no rule at all and the input fell back to the UA's white. - Row hover is light falling off to the right, not a flat slab. - The artwork placeholder can drop its note glyph, so TrackRow no longer layers a play icon on top of one. Vibe: - A seeded Vibe plays its seed first. The seed sits in front of the durable plan without being part of it, so the first advance consumes it locally and reports no plan feedback. - The queue drops a second recording of a song it already holds — same title, different track id, which id-based dedup let through. - Up next is read from the queue rather than the plan preview, since the seed is not a plan item. - The header carries the live profile (energy, discovery, goal) and both verbs. Keep is gone: letting a track finish already reports `completed`, which the director weighs the same. - The aura is one warm diffuse blob in the page background, warm-hued only and quieter on mobile. - Compact artwork is 32px. It was h-8 w-8, which this remapped spacing scale renders as 64px inside a 44px row, and that overflow was the "stacked" look. Verified by render at 1440x900 and 390x844, no horizontal overflow at either. 26 frontend and 122 backend tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,11 +6,16 @@
|
||||
* as the single source of truth.
|
||||
*/
|
||||
|
||||
/** Deterministic hash → hue (0..359) from an arbitrary string. */
|
||||
/**
|
||||
* Deterministic hash → hue from an arbitrary string, clamped to the warm band
|
||||
* (amber → rust → deep red, 8°..52°). Free-running 0..359 hues produced blue,
|
||||
* teal and violet placeholder tiles that fight the warm room Ethos asks for;
|
||||
* a 44° window keeps tiles distinguishable without leaving the palette.
|
||||
*/
|
||||
export function hueFromString(s: string): number {
|
||||
let h = 0;
|
||||
for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0;
|
||||
return Math.abs(h) % 360;
|
||||
return 8 + (Math.abs(h) % 45);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
export const PLAYBACK_PREF_KEYS = {
|
||||
prefetchNext: 'muzick.settings.prefetchNext',
|
||||
crossfadeMs: 'muzick.settings.crossfadeMs',
|
||||
lastTrack: 'muzick.playback.lastTrack',
|
||||
} as const;
|
||||
|
||||
/** Longest fade the UI offers. Also the longest early-advance lead. */
|
||||
@@ -53,3 +54,23 @@ export function storePrefetchNext(value: boolean): void {
|
||||
export function storeCrossfadeMs(value: number): void {
|
||||
try { localStorage.setItem(PLAYBACK_PREF_KEYS.crossfadeMs, String(value)); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
/**
|
||||
* The last track loaded into the player, so a fresh tab shows what was playing
|
||||
* rather than an empty bar. Restored paused — never autoplayed.
|
||||
* ponytail: stores the whole track object. It is one small row and it saves a
|
||||
* fetch on boot; if the shape drifts, the parse simply fails and the bar is empty.
|
||||
*/
|
||||
export function readStoredLastTrack<T>(): T | null {
|
||||
try {
|
||||
const stored = localStorage.getItem(PLAYBACK_PREF_KEYS.lastTrack);
|
||||
return stored ? (JSON.parse(stored) as T) : null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
export function storeLastTrack(track: unknown): void {
|
||||
try {
|
||||
if (track) localStorage.setItem(PLAYBACK_PREF_KEYS.lastTrack, JSON.stringify(track));
|
||||
else localStorage.removeItem(PLAYBACK_PREF_KEYS.lastTrack);
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user