Files
nvrl 62b28bd30e Redesign the bar, sheets and cards, and add likes queue, history and sleep timer
Feed modes move into tabs in the bar; likes, history, filters and settings,
and account open as one kind of sheet: a drawer on wide screens, a bottom
sheet with a drag handle on phones, with a scrim, Esc, a focus trap and
focus restore.

Cards get an artist avatar, follow and radio pills, a "new" chip, counts
under round action buttons, an up-next pill and a redrawn waveform with
rounded bars, a reflection and the buffered range. The start overlay names
the track behind a big round play button.

New: undo on dislike, a searchable likes list that plays as a queue
(in order or shuffled), a local history of what played, deep links to a
track (?t=handle/slug), a sleep timer, adjustable crossfade, bass boost,
wheel up (w or a long press on the drop) and more keys.
2026-11-01 19:33:49 +01:00

36 lines
1.0 KiB
TypeScript

// What this browser played lately, newest first, for the history sheet. Kept on
// the device only; the server has its own record of plays for the feed.
import type { Track } from "./api";
const KEY = "pl8inf.history";
const MAX = 100;
export interface Heard {
track: Track;
/// ms since the epoch.
at: number;
}
export function load(): Heard[] {
try {
const list = JSON.parse(localStorage.getItem(KEY) || "[]");
return Array.isArray(list) ? list.filter((h) => h?.track?.id && typeof h.at === "number") : [];
} catch {
return [];
}
}
/// Note a track as heard; a track heard again moves to the top.
export function add(track: Track): void {
// Why it was in a radio says nothing once it is replayed from here.
const list = [{ track: { ...track, why: undefined }, at: Date.now() }, ...load().filter((h) => h.track.id !== track.id)].slice(0, MAX);
try {
localStorage.setItem(KEY, JSON.stringify(list));
} catch { /* storage full: history is a nicety */ }
}
export function clear(): void {
localStorage.removeItem(KEY);
}