Unify web-player design tokens and componentize collection headers

Migrates remaining one-off colors (bg-white/[N%], border-white/N, hardcoded
shadows/gradients) to the existing --mf-* tokens so light mode reacts
consistently everywhere, including a fix to --color-hover which was
hardcoded instead of pointing at --mf-surface-2. Extracts CollectionHeader
(album/playlist/mix/liked headers) and AccountMenu (TopBar/MobileHeader)
to remove five and two duplicated implementations respectively, and
normalizes inconsistent empty-state copy.
This commit is contained in:
2026-09-15 17:53:47 +02:00
parent 7861600081
commit f1756468bc
40 changed files with 463 additions and 448 deletions
@@ -6,6 +6,7 @@
import PlaylistArt from '$lib/components/ui/PlaylistArt.svelte';
import BackLink from '$lib/components/ui/BackLink.svelte';
import Button from '$lib/components/ui/Button.svelte';
import CollectionHeader from '$lib/components/ui/CollectionHeader.svelte';
interface Props {
title: string;
@@ -48,45 +49,34 @@
label={isOwner ? 'Volver a tus álbumes' : 'Volver al inicio'}
/>
<div class="mt-3 flex flex-col gap-5 sm:mt-4 sm:flex-row sm:items-end sm:gap-6">
<PlaylistArt
{trackIds}
{coverUrl}
size="large"
class="h-36 w-36 shrink-0 rounded-[18px] shadow-[0_24px_56px_rgba(0,0,0,.55)] sm:h-41 sm:w-41"
/>
<div class="min-w-0 flex-1">
<p class="text-[10.5px] font-semibold tracking-[0.16em] text-fg-2 uppercase">Álbum</p>
<h1
class="mt-1.5 font-display text-[28px] font-semibold tracking-[-0.035em] break-words text-fg sm:text-[33px]"
>
{title}
</h1>
{#if description}
<p class="mt-3 max-w-2xl text-sm text-fg-2 sm:text-base">{description}</p>
{/if}
<p class="mt-3 text-sm text-muted">{meta}</p>
</div>
</div>
<div class="mt-6 flex flex-wrap items-center gap-2.5 sm:mt-7">
<Button size="sm" onclick={onPlayAll} disabled={trackIds.length === 0}>
{#if playing}
<Pause class="h-4 w-4" strokeWidth={1.5} />
Pausar
{:else}
<Play class="h-4 w-4" strokeWidth={1.5} />
Reproducir
{/if}
</Button>
{#if isOwner}
<Button size="sm" variant="secondary" onclick={onEdit}>
<SquarePencil class="h-4 w-4" strokeWidth={1.5} />
Editar
<CollectionHeader eyebrow="Álbum" {title} {description} {meta}>
{#snippet cover()}
<PlaylistArt
{trackIds}
{coverUrl}
size="large"
class="h-36 w-36 shrink-0 rounded-[18px] shadow-cover-lg sm:h-41 sm:w-41"
/>
{/snippet}
{#snippet actions()}
<Button size="sm" onclick={onPlayAll} disabled={trackIds.length === 0}>
{#if playing}
<Pause class="h-4 w-4" strokeWidth={1.5} />
Pausar
{:else}
<Play class="h-4 w-4" strokeWidth={1.5} />
Reproducir
{/if}
</Button>
<Button size="sm" variant="secondary" onclick={onDelete}>
<Trash class="h-4 w-4" strokeWidth={1.5} />
Eliminar
</Button>
{/if}
</div>
{#if isOwner}
<Button size="sm" variant="secondary" onclick={onEdit}>
<SquarePencil class="h-4 w-4" strokeWidth={1.5} />
Editar
</Button>
<Button size="sm" variant="secondary" onclick={onDelete}>
<Trash class="h-4 w-4" strokeWidth={1.5} />
Eliminar
</Button>
{/if}
{/snippet}
</CollectionHeader>
@@ -8,6 +8,7 @@
import Button from '$lib/components/ui/Button.svelte';
import Cover from '$lib/components/ui/Cover.svelte';
import Alert from '$lib/components/ui/Alert.svelte';
import CollectionHeader from '$lib/components/ui/CollectionHeader.svelte';
import TrackTable from '$lib/components/ui/TrackTable.svelte';
import TrackRow from '$lib/components/ui/TrackRow.svelte';
import TrackMeta from '$lib/components/ui/TrackMeta.svelte';
@@ -79,48 +80,40 @@
<Page>
<BackLink href="/explore" label="Volver a explorar" />
<div class="mt-3 flex flex-col gap-5 sm:mt-4 sm:flex-row sm:items-end sm:gap-6">
<Cover
trackId={album.albumId}
src={album.thumbnailUrl}
size="large"
alt={album.title}
class="h-36 w-36 shrink-0 rounded-[18px] shadow-[0_24px_56px_rgba(0,0,0,.55)] sm:h-41 sm:w-41"
oncontextmenu={(e) => (albumMenu = albumContextMenuStateFor(e, 'youtube', album.albumId))}
/>
<div class="min-w-0 flex-1">
<p class="text-[10.5px] font-semibold tracking-[0.16em] text-fg-2 uppercase">
{album.isSingle ? 'Single' : album.isEp ? 'EP' : 'Álbum'}
</p>
<h1
class="mt-1.5 font-display text-[28px] font-semibold tracking-[-0.035em] break-words text-fg sm:text-[33px]"
>
{album.title}
</h1>
<p class="mt-3 text-base text-fg-2">{album.artist}</p>
<p class="mt-1 text-sm text-muted">
{[
album.releaseYear ? String(album.releaseYear) : undefined,
`${tracks.length} ${tracks.length === 1 ? 'canción' : 'canciones'}`,
fmtTime(Number(data.album.totalDurationSeconds))
]
.filter((part) => !!part)
.join(' · ')}
</p>
</div>
</div>
<div class="mt-6 flex flex-wrap items-center gap-2.5 sm:mt-7">
<Button size="sm" onclick={playAll} disabled={queue.length === 0}>
{#if isCurrentQueue && player.playing}
<Pause class="h-4 w-4" strokeWidth={1.5} />
Pausar
{:else}
<Play class="h-4 w-4" strokeWidth={1.5} />
Reproducir
{/if}
</Button>
</div>
<CollectionHeader
eyebrow={album.isSingle ? 'Single' : album.isEp ? 'EP' : 'Álbum'}
title={album.title}
description={album.artist}
meta={[
album.releaseYear ? String(album.releaseYear) : undefined,
`${tracks.length} ${tracks.length === 1 ? 'canción' : 'canciones'}`,
fmtTime(Number(data.album.totalDurationSeconds))
]
.filter((part) => !!part)
.join(' · ')}
>
{#snippet cover()}
<Cover
trackId={album.albumId}
src={album.thumbnailUrl}
size="large"
alt={album.title}
class="h-36 w-36 shrink-0 rounded-[18px] shadow-cover-lg sm:h-41 sm:w-41"
oncontextmenu={(e) => (albumMenu = albumContextMenuStateFor(e, 'youtube', album.albumId))}
/>
{/snippet}
{#snippet actions()}
<Button size="sm" onclick={playAll} disabled={queue.length === 0}>
{#if isCurrentQueue && player.playing}
<Pause class="h-4 w-4" strokeWidth={1.5} />
Pausar
{:else}
<Play class="h-4 w-4" strokeWidth={1.5} />
Reproducir
{/if}
</Button>
{/snippet}
</CollectionHeader>
{#if form?.message}
<Alert tone="danger" class="mt-4">{form.message}</Alert>