From 964dfc809ff8d26bba36757bdba7603e3c2eed0d Mon Sep 17 00:00:00 2001 From: XShine555 Date: Mon, 21 Sep 2026 00:30:30 +0200 Subject: [PATCH] web-player: cards and grids (MediaCard) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MediaCard now builds its own art (src/trackIds via Artwork size="fill", or a custom art snippet) and its own play button (onPlay, with a scrim and a glass PlayButton on hover) instead of relying on each wrapper passing it a snippet. Replaces AlbumCard, PlaylistCard and MixTile, which are removed along with MediaGrid.svelte (their grids move to the grid-cards/grid-cards-lg/grid-tiles/grid-wide @utility recipes). format.ts gains albumMeta() and playlistMeta() (previously ad-hoc calculations inside each card). Explore's genre tile moves from an inline style= gradient to the genre-tile @utility recipe + --tile-hue. Along the way, fixes the "follower/s" pluralization on the user profile that had been missed. Delta documented: the art's hover-zoom and the scrim+PlayButton (previously exclusive to MixTile) become MediaCard behavior gated on the onPlay prop — they'll apply to any future card that uses it, for consistency. Verified with npm run check/build; no authenticated visual QA (Docker unavailable this session). --- web-player/docs/frontend-refactor-plan.md | 2 +- .../src/lib/components/ui/AlbumCard.svelte | 56 ------------------ .../src/lib/components/ui/MediaCard.svelte | 59 ++++++++++++++----- .../src/lib/components/ui/MediaGrid.svelte | 27 --------- .../src/lib/components/ui/MixTile.svelte | 53 ----------------- .../src/lib/components/ui/PlaylistCard.svelte | 30 ---------- web-player/src/lib/format.ts | 13 ++++ .../src/routes/(app)/albums/+page.svelte | 20 ++++--- .../src/routes/(app)/explore/+page.svelte | 7 +-- .../src/routes/(app)/playlists/+page.svelte | 21 +++---- .../src/routes/(app)/u/[id]/+page.svelte | 28 +++++---- web-player/src/routes/+page.svelte | 24 ++++++-- web-player/src/routes/layout.css | 7 +++ 13 files changed, 126 insertions(+), 221 deletions(-) delete mode 100644 web-player/src/lib/components/ui/AlbumCard.svelte delete mode 100644 web-player/src/lib/components/ui/MediaGrid.svelte delete mode 100644 web-player/src/lib/components/ui/MixTile.svelte delete mode 100644 web-player/src/lib/components/ui/PlaylistCard.svelte diff --git a/web-player/docs/frontend-refactor-plan.md b/web-player/docs/frontend-refactor-plan.md index ef080f5..9771690 100644 --- a/web-player/docs/frontend-refactor-plan.md +++ b/web-player/docs/frontend-refactor-plan.md @@ -781,7 +781,7 @@ Tareas: | P6 | ✅ | (pendiente) | z-index de AccountMenu unificado a z-(--z-menu) (antes z-20, ahora al mismo nivel que ContextMenu — Δ menor, mejora consistencia); sin QA visual autenticada (Docker no disponible) | | P7 | ✅ | (pendiente) | MediaIdentity queda "puro" (sin onClick) según spec; las 6 tablas de pista (P8) envuelven su propio `use:pressable` hasta que P8 las sustituya por TrackList. Deltas documentados: fila activa de Sidebar ahora usa bg-accent-tint/text-accent-soft (antes gris neutro, ahora consistente con el resto de filas activas); tarjeta "Tus playlists" de Inicio pierde la línea "por {tu nombre}" (redundante, siempre el propio usuario) y su radio pasa de rounded-panel a rounded-art (consistencia con la tarjeta "Continuar"); filas de Queue "A continuación" pierden el opacity-80 del arte (ListRow no expone esa personalización). Sin QA visual autenticada (Docker no disponible) | | P8 | ✅ | (pendiente) | Decisión tomada (recomendada por el propio plan, sin bloquear): se elimina la columna "Álbum" que siempre mostraba "—". TrackList unifica TrackTable+TrackRow+TrackIndexCell+TrackMeta; AlbumTrackTable/PlaylistTrackTable/LibraryPicker se inlinean en sus páginas y se borran. Sin QA visual autenticada (Docker no disponible) | -| P9 | ⬜ | | | +| P9 | ✅ | (pendiente) | MediaCard gana hover-zoom del arte y scrim+PlayButton condicionados a `onPlay` (antes exclusivos de MixTile); ahora se aplicarían a cualquier card futura con onPlay, consistencia intencional. Sin QA visual autenticada (Docker no disponible) | | P10 | ⬜ | | | | P11 | ⬜ | | | | P12 | ⬜ | | | diff --git a/web-player/src/lib/components/ui/AlbumCard.svelte b/web-player/src/lib/components/ui/AlbumCard.svelte deleted file mode 100644 index ca37ffa..0000000 --- a/web-player/src/lib/components/ui/AlbumCard.svelte +++ /dev/null @@ -1,56 +0,0 @@ - - - - {#snippet art(artClass)} - - {/snippet} - diff --git a/web-player/src/lib/components/ui/MediaCard.svelte b/web-player/src/lib/components/ui/MediaCard.svelte index 84b2fd4..3472bd2 100644 --- a/web-player/src/lib/components/ui/MediaCard.svelte +++ b/web-player/src/lib/components/ui/MediaCard.svelte @@ -1,30 +1,59 @@ - - - - {@render art(mediaCardArt)} + +
+ {#if art} + {@render art()} + {:else} + + {/if} + {#if onPlay} +
+ + {/if} +
{title}
{#if subtitle}
{subtitle}
diff --git a/web-player/src/lib/components/ui/MediaGrid.svelte b/web-player/src/lib/components/ui/MediaGrid.svelte deleted file mode 100644 index c979b83..0000000 --- a/web-player/src/lib/components/ui/MediaGrid.svelte +++ /dev/null @@ -1,27 +0,0 @@ - - - - {@render children()} - diff --git a/web-player/src/lib/components/ui/MixTile.svelte b/web-player/src/lib/components/ui/MixTile.svelte deleted file mode 100644 index e2c586d..0000000 --- a/web-player/src/lib/components/ui/MixTile.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - -
- -
- -
-
- {mix.title} -
- {#if mix.subtitle} -
{mix.subtitle}
- {/if} -
diff --git a/web-player/src/lib/components/ui/PlaylistCard.svelte b/web-player/src/lib/components/ui/PlaylistCard.svelte deleted file mode 100644 index 47d217c..0000000 --- a/web-player/src/lib/components/ui/PlaylistCard.svelte +++ /dev/null @@ -1,30 +0,0 @@ - - - - {#snippet art(artClass)} - - {/snippet} - diff --git a/web-player/src/lib/format.ts b/web-player/src/lib/format.ts index 477832a..81717aa 100644 --- a/web-player/src/lib/format.ts +++ b/web-player/src/lib/format.ts @@ -26,3 +26,16 @@ export function fmtPlays(count: number | string | undefined): string { const value = Math.max(0, Math.floor(Number(count ?? 0))); return plural(value, 'reproducción', 'reproducciones'); } + +export function albumMeta(releaseYear?: number, trackCount?: number): string { + return [ + releaseYear ? String(releaseYear) : undefined, + trackCount === undefined ? undefined : plural(trackCount, 'canción', 'canciones') + ] + .filter((part): part is string => !!part) + .join(' · '); +} + +export function playlistMeta(trackCount: number): string { + return plural(trackCount, 'canción', 'canciones'); +} diff --git a/web-player/src/routes/(app)/albums/+page.svelte b/web-player/src/routes/(app)/albums/+page.svelte index 345b7bc..05c86a4 100644 --- a/web-player/src/routes/(app)/albums/+page.svelte +++ b/web-player/src/routes/(app)/albums/+page.svelte @@ -3,12 +3,12 @@ import Disc from '@lucide/svelte/icons/disc-3'; import Page from '$lib/components/ui/Page.svelte'; import PageHeader from '$lib/components/ui/PageHeader.svelte'; - import MediaGrid from '$lib/components/ui/MediaGrid.svelte'; - import AlbumCard from '$lib/components/ui/AlbumCard.svelte'; + import MediaCard from '$lib/components/ui/MediaCard.svelte'; import AlbumForm from '$lib/components/ui/AlbumForm.svelte'; import Modal from '$lib/components/ui/Modal.svelte'; import EmptyState from '$lib/components/ui/EmptyState.svelte'; import Button from '$lib/components/ui/Button.svelte'; + import { albumMeta } from '$lib/format'; let { data, form } = $props(); @@ -35,19 +35,21 @@ {#if items.length > 0} - +
{#each items as album, i (album.id)} - {/each} - +
{:else} -
+
{#each genreTiles as genre, i (genre.query)}
{genre.label} diff --git a/web-player/src/routes/(app)/playlists/+page.svelte b/web-player/src/routes/(app)/playlists/+page.svelte index 275a01a..a79d558 100644 --- a/web-player/src/routes/(app)/playlists/+page.svelte +++ b/web-player/src/routes/(app)/playlists/+page.svelte @@ -2,12 +2,11 @@ import ListMusic from '@lucide/svelte/icons/list-music'; import Page from '$lib/components/ui/Page.svelte'; import PageHeader from '$lib/components/ui/PageHeader.svelte'; - import MediaGrid from '$lib/components/ui/MediaGrid.svelte'; - import PlaylistCard from '$lib/components/ui/PlaylistCard.svelte'; + import MediaCard from '$lib/components/ui/MediaCard.svelte'; import EmptyState from '$lib/components/ui/EmptyState.svelte'; import Button from '$lib/components/ui/Button.svelte'; import { createPlaylistModal } from '$lib/playlists.svelte'; - import { fmtDurationLong, plural } from '$lib/format'; + import { fmtDurationLong, plural, playlistMeta } from '$lib/format'; let { data } = $props(); @@ -36,18 +35,20 @@ {#if items.length > 0} - +
{#each items as playlist, i (playlist.id)} - {/each} - +
{:else} @@ -73,18 +75,20 @@ {#if playlists.length > 0} - +
{#each playlists as playlist, i (playlist.id)} - {/each} - +
{:else} queueItemForTarget(mixItemTarget(item))), + 0 + ); + } @@ -274,9 +283,16 @@
{#if mixes.length > 0} -
+
{#each mixes as mix, i (mix.id)} - + item.trackId)} + onPlay={(event) => playMix(mix, event)} + index={i} + /> {/each}
{:else} @@ -351,7 +367,7 @@ {/snippet} {#if playlists.length > 0} -
+
{#each playlists as playlist, i (playlist.id)} goto(`/playlists/${playlist.id}`)} diff --git a/web-player/src/routes/layout.css b/web-player/src/routes/layout.css index 0e7ac84..7dc4d29 100644 --- a/web-player/src/routes/layout.css +++ b/web-player/src/routes/layout.css @@ -196,6 +196,13 @@ --mf-dock-h: var(--mf-player-h); } } +@utility genre-tile { + background: linear-gradient( + 140deg, + oklch(var(--mf-tile-l) var(--mf-tile-c) var(--tile-hue)), + oklch(var(--mf-tile-shade-l) var(--mf-tile-shade-c) var(--tile-hue)) + ); +} @layer base { svg.lucide {