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.
This commit is contained in:
nvrl committed 2026-11-01 19:33:49 +01:00
1 parent 53ac54b178
commit 62b28bd30e
10 files changed
+1312 -339

No files matched your search

+6 -1
View File
@@ -81,7 +81,7 @@ export type Mode =
| { mode: "classic" | "riddim" | "fresh" | "deep" }
| { mode: "artist"; artist: string; name: string; img?: string | null };
export type EventKind = "play" | "like" | "unlike" | "skip" | "complete" | "dislike";
export type EventKind = "play" | "like" | "unlike" | "skip" | "complete" | "dislike" | "undislike";
export class HttpError extends Error {
constructor(message: string, readonly status: number) {
@@ -125,12 +125,17 @@ export function feed(filters: Filters, mode: Mode) {
export const artist = (handle: string) => getJSON<ArtistInfo>("/local/artist?" + new URLSearchParams({ handle }));
export const track = (handle: string, slug: string) =>
getJSON<{ track: Track }>("/local/track?" + new URLSearchParams({ handle, slug }));
export const likes = () => getJSON<{ tracks: Track[] }>("/local/likes");
export const me = () => getJSON<Me>("/local/me");
export const PL8 = "https://pl8list.com";
export const trackUrl = (t: Track) => `${PL8}/${t.handle}/${t.slug}`;
/// A link that opens this app on the track.
export const appTrackUrl = (t: Track) => `${location.origin}/?t=${encodeURIComponent(t.handle)}/${encodeURIComponent(t.slug)}`;
export const artistUrl = (t: Track) => `${PL8}/${t.handle}`;
// Images come through our server, so pl8list never sees the visitor's address.
export const imgUrl = (id: string | null) => (id ? `/img/${encodeURIComponent(id)}` : null);