Checkpoint web-player design-system refactor and backend track genre tagging
Advances the ongoing token/componentization cleanup (anchored player dock, border-based panels instead of floating shadows, corrected light-mode accent/gradient tokens) alongside in-progress backend support for track genre tags.
This commit is contained in:
Vendored
+2
@@ -743,6 +743,8 @@ export interface components {
|
||||
artist: null | string;
|
||||
/** Format: double */
|
||||
durationSeconds: number | string;
|
||||
/** Format: int32 */
|
||||
listensCount: number | string;
|
||||
};
|
||||
PaginatedResponseOfAlbumApplicationResponse: {
|
||||
items: components["schemas"]["AlbumApplicationResponse"][];
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
onclick={onPlayNext}
|
||||
class="flex w-full items-center gap-3 rounded-control px-2 py-2.5 text-left text-sm font-medium text-fg-2 transition hover:bg-hover"
|
||||
>
|
||||
<ListPlus class="h-[18px] w-[18px] shrink-0" strokeWidth={2} />
|
||||
<ListPlus class="h-4.5 w-4.5 shrink-0" strokeWidth={2} />
|
||||
<span>Reproducir a continuación</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -63,7 +63,7 @@
|
||||
onclick={onAddToQueue}
|
||||
class="flex w-full items-center gap-3 rounded-control px-2 py-2.5 text-left text-sm font-medium text-fg-2 transition hover:bg-hover"
|
||||
>
|
||||
<ListEnd class="h-[18px] w-[18px] shrink-0" strokeWidth={2} />
|
||||
<ListEnd class="h-4.5 w-4.5 shrink-0" strokeWidth={2} />
|
||||
<span>Añadir a la cola</span>
|
||||
</button>
|
||||
{#if playlists.length > 0}
|
||||
@@ -80,7 +80,7 @@
|
||||
: 'left-full pl-1.5'}"
|
||||
>
|
||||
<div
|
||||
class="w-64 overflow-hidden rounded-panel border border-line bg-elevated p-2 shadow-menu max-sm:w-full"
|
||||
class="w-64 overflow-hidden rounded-panel border border-line bg-elevated p-2 max-sm:w-full"
|
||||
>
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
{#each playlists as playlist (playlist.id)}
|
||||
|
||||
@@ -21,9 +21,7 @@
|
||||
href="/"
|
||||
class="flex items-center gap-2 font-display text-lg font-semibold tracking-[-0.02em] text-fg"
|
||||
>
|
||||
<span
|
||||
class="h-5.5 w-5.5 rounded-[7px] bg-[image:var(--mf-logo-grad)] shadow-[var(--mf-logo-glow)]"
|
||||
></span>
|
||||
<span class="h-5.5 w-5.5 rounded-lg bg-[image:var(--mf-logo-grad)]"></span>
|
||||
Musify
|
||||
</a>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
? [
|
||||
{ href: '/', label: 'Inicio', icon: Home },
|
||||
{ href: '/explore', label: 'Descubrir', icon: Compass },
|
||||
{ href: '/playlists', label: 'Playlists', icon: ListMusic },
|
||||
{ href: '/playlists', label: 'Playlists', icon: ListMusic, count: playlists.length },
|
||||
{ href: '/liked', label: 'Me gusta', icon: Heart, count: liked.count },
|
||||
{ href: '/albums', label: 'Mis álbumes', icon: Disc },
|
||||
{ href: '/library', label: 'Canciones subidas', icon: Folder },
|
||||
@@ -48,24 +48,22 @@
|
||||
</script>
|
||||
|
||||
<aside
|
||||
class="hidden shrink-0 flex-col border-r border-hairline bg-[image:var(--mf-sidebar-bg)] pt-5.5 pr-3 pl-5 transition-[background] duration-[600ms] ease-out lg:flex"
|
||||
class="hidden shrink-0 flex-col border-r border-hairline bg-[image:var(--mf-sidebar-bg)] p-5 transition-[background] duration-500 ease-out lg:flex"
|
||||
style="width:var(--mf-sidebar-w)"
|
||||
>
|
||||
<a href="/" class="flex items-center gap-2.5 px-1.5 pb-6.5">
|
||||
<span class="font-display text-lg font-semibold tracking-[-0.02em] text-fg">Musify</span>
|
||||
</a>
|
||||
<a href="/" class="px-3 pb-3 font-display text-lg font-semibold tracking-tight text-fg">Musify</a>
|
||||
|
||||
<nav class="flex flex-col gap-0.5">
|
||||
<nav class="flex flex-col gap-1">
|
||||
{#each navLinks as link (link.href)}
|
||||
{@const active = isActive(link.href)}
|
||||
<a
|
||||
href={link.href}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
class="flex items-center gap-3 rounded-[10px] px-3 py-2.25 text-sm font-medium transition-colors duration-150 {active
|
||||
class="flex items-center gap-3 rounded-control px-3 py-2 text-sm transition-colors duration-150 {active
|
||||
? 'bg-surface-hover text-fg'
|
||||
: 'text-fg-2 hover:bg-hover hover:text-fg'}"
|
||||
>
|
||||
<link.icon class="h-[17px] w-[17px] shrink-0" strokeWidth={1.6} />
|
||||
<link.icon class="h-4.5 w-4.5 shrink-0" strokeWidth={1.5} />
|
||||
<span class="flex-1 truncate">{link.label}</span>
|
||||
{#if link.count}
|
||||
<span class="text-xs text-muted tabular-nums">{link.count}</span>
|
||||
@@ -77,8 +75,7 @@
|
||||
{#if user}
|
||||
<div class="mx-3 mt-6 mb-3.5 h-px bg-line"></div>
|
||||
<div class="flex items-center justify-between px-3 pb-1.5">
|
||||
<span class="text-xs font-semibold tracking-[0.13em] text-muted uppercase">Tus playlists</span
|
||||
>
|
||||
<span class="text-xs font-medium tracking-widest text-muted uppercase">Tus playlists</span>
|
||||
{#if playlists.length > 0}
|
||||
<button
|
||||
type="button"
|
||||
@@ -86,7 +83,7 @@
|
||||
aria-label="Crear playlist"
|
||||
class="text-base text-muted transition-colors hover:text-fg"
|
||||
>
|
||||
+
|
||||
<Plus class="h-3.5 w-3.5" strokeWidth={1.25} />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -96,7 +93,7 @@
|
||||
{@const active = page.url.pathname === `/playlists/${playlist.id}`}
|
||||
<a
|
||||
href="/playlists/{playlist.id}"
|
||||
class="flex items-center gap-2.75 rounded-[10px] px-3 py-1.75 transition-colors duration-150 {active
|
||||
class="flex items-center gap-2.75 rounded-control px-3 py-1.75 transition-colors duration-150 {active
|
||||
? 'bg-surface-hover'
|
||||
: 'hover:bg-hover'}"
|
||||
>
|
||||
@@ -117,7 +114,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => createPlaylistModal.show()}
|
||||
class="flex items-center gap-2 rounded-[10px] px-3 py-2 text-xs text-muted transition-colors hover:bg-hover hover:text-fg-2"
|
||||
class="flex items-center gap-2 rounded-control px-3 py-2 text-xs text-muted transition-colors hover:bg-hover hover:text-fg-2"
|
||||
>
|
||||
<Plus class="h-3.5 w-3.5" strokeWidth={1.8} />
|
||||
Crear tu primera lista
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</script>
|
||||
|
||||
<nav
|
||||
class="pointer-events-auto flex items-center justify-around rounded-[18px] border border-hairline bg-[image:var(--mf-bar-bg)] p-1.75 backdrop-blur-[22px] transition-[background] duration-[600ms] lg:hidden {klass}"
|
||||
class="pointer-events-auto flex items-center justify-around rounded-panel border border-hairline bg-[image:var(--mf-bar-bg)] p-1.75 backdrop-blur-xl transition-[background] duration-500 lg:hidden {klass}"
|
||||
style="padding-bottom:calc(0.4375rem + var(--mf-safe-b))"
|
||||
>
|
||||
{#each links as link (link.href)}
|
||||
@@ -43,7 +43,7 @@
|
||||
? 'text-fg'
|
||||
: 'text-fg-2'}"
|
||||
>
|
||||
<link.icon class="h-[17px] w-[17px]" strokeWidth={1.6} />
|
||||
<link.icon class="h-4.25 w-4.25" strokeWidth={1.6} />
|
||||
<span class="text-xs font-medium">{link.label}</span>
|
||||
</a>
|
||||
{/each}
|
||||
|
||||
@@ -62,21 +62,21 @@
|
||||
aria-label="Atrás"
|
||||
class="grid h-7.5 w-7.5 place-items-center rounded-full bg-surface-2 text-fg-2 transition-colors hover:bg-surface-hover hover:text-fg"
|
||||
>
|
||||
<ChevronLeft class="h-[15px] w-[15px]" strokeWidth={1.9} />
|
||||
<ChevronLeft class="h-3.75 w-3.75" strokeWidth={1.9} />
|
||||
</button>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="grid h-7.5 w-7.5 place-items-center rounded-full bg-surface-2 text-muted"
|
||||
>
|
||||
<ChevronRight class="h-[15px] w-[15px]" strokeWidth={1.9} />
|
||||
<ChevronRight class="h-3.75 w-3.75" strokeWidth={1.9} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form onsubmit={onSubmit} class="max-w-[480px] min-w-0 flex-1">
|
||||
<form onsubmit={onSubmit} class="max-w-120 min-w-0 flex-1">
|
||||
<label
|
||||
class="flex h-9.5 w-full items-center gap-2.5 rounded-[11px] bg-surface-2 px-3.5 transition focus-within:bg-surface-hover"
|
||||
class="flex h-9.5 w-full items-center gap-2.5 rounded-control bg-surface-2 px-3.5 transition focus-within:bg-surface-hover"
|
||||
>
|
||||
<Search class="h-[15px] w-[15px] shrink-0 text-fg-3" strokeWidth={1.8} />
|
||||
<Search class="h-3.75 w-3.75 shrink-0 text-fg-3" strokeWidth={1.8} />
|
||||
<input
|
||||
type="text"
|
||||
bind:this={searchInput}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
onclick={onAddToQueue}
|
||||
class="flex w-full items-center gap-3 rounded-control px-2 py-2.5 text-left text-sm font-medium text-fg-2 transition hover:bg-hover"
|
||||
>
|
||||
<ListPlus class="h-[18px] w-[18px] shrink-0" strokeWidth={2} />
|
||||
<ListPlus class="h-4.5 w-4.5 shrink-0" strokeWidth={2} />
|
||||
<span>Reproducir a continuación</span>
|
||||
</button>
|
||||
{#if playlists.length > 0}
|
||||
@@ -72,7 +72,7 @@
|
||||
: 'left-full pl-1.5'}"
|
||||
>
|
||||
<div
|
||||
class="w-64 overflow-hidden rounded-panel border border-line bg-elevated p-2 shadow-menu max-sm:w-full"
|
||||
class="w-64 overflow-hidden rounded-panel border border-line bg-elevated p-2 max-sm:w-full"
|
||||
>
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
{#each playlists as playlist (playlist.id)}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="animate-enter pointer-events-auto flex items-center gap-4.5 rounded-[18px] border border-hairline bg-[image:var(--mf-bar-bg)] px-4 py-2.75 shadow-player backdrop-blur-[22px] transition-[background] duration-[600ms] sm:hidden"
|
||||
class="animate-enter pointer-events-auto flex items-center gap-4.5 border-t border-hairline bg-[image:var(--mf-bar-bg)] px-4 py-2.75 backdrop-blur-xl transition-[background] duration-500 sm:hidden"
|
||||
>
|
||||
<div class="min-w-0 flex-1">
|
||||
<TrackInfo compact />
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="animate-enter pointer-events-auto hidden items-center gap-5 rounded-[18px] border border-hairline bg-[image:var(--mf-bar-bg)] px-4 py-2.75 shadow-player backdrop-blur-[22px] transition-[background] duration-[600ms] sm:flex"
|
||||
class="animate-enter pointer-events-auto hidden items-center gap-5 border-t border-hairline bg-[image:var(--mf-bar-bg)] px-4 py-2.75 backdrop-blur-xl transition-[background] duration-500 sm:flex"
|
||||
>
|
||||
<div class="w-64 min-w-0 shrink-0">
|
||||
<TrackInfo />
|
||||
|
||||
@@ -15,15 +15,17 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="pointer-events-none fixed inset-x-0 bottom-0 z-40 flex flex-col gap-2.5 px-4 pb-4 lg:left-[var(--mf-sidebar-w)]"
|
||||
class="pointer-events-none fixed inset-x-0 bottom-0 z-40 flex flex-col lg:left-[var(--mf-sidebar-w)]"
|
||||
>
|
||||
{#if player.error}
|
||||
<div class="pointer-events-auto">
|
||||
<div class="pointer-events-auto px-4 pb-2.5">
|
||||
<Alert tone="danger">{player.error}</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
{#if hasTrack}
|
||||
<PlayerBar />
|
||||
{/if}
|
||||
<TabsBar {user} />
|
||||
<div class="px-4 pt-2.5 pb-4 lg:hidden">
|
||||
<TabsBar {user} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<div class="hidden w-64 shrink-0 items-center justify-end gap-3.5 sm:flex">
|
||||
<span aria-hidden="true" class="text-fg-3">
|
||||
<AlignJustify class="h-[15px] w-[15px]" strokeWidth={1.8} />
|
||||
<AlignJustify class="h-3.75 w-3.75" strokeWidth={1.8} />
|
||||
</span>
|
||||
<VolumeControl />
|
||||
<button
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
{#if queuePanel.open}
|
||||
<aside
|
||||
transition:fly={{ x: 24, duration: 220, easing: cubicOut }}
|
||||
class="hidden shrink-0 flex-col border-l border-hairline bg-[image:var(--mf-panel-bg)] px-4 pt-5 transition-[background] duration-[600ms] sm:flex"
|
||||
class="hidden shrink-0 flex-col border-l border-hairline bg-[image:var(--mf-panel-bg)] px-4 pt-5 transition-[background] duration-500 sm:flex"
|
||||
style="width:var(--mf-queue-w); padding-bottom:calc(var(--mf-player-h) + 2rem)"
|
||||
>
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<h2 class="font-display text-base font-semibold tracking-[-0.02em] text-fg">En cola</h2>
|
||||
<h2 class="font-display text-base font-medium tracking-tight text-fg">En cola</h2>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => queuePanel.close()}
|
||||
@@ -33,24 +33,22 @@
|
||||
</div>
|
||||
|
||||
{#if player.current.id}
|
||||
<p class="mb-2.75 text-xs font-semibold tracking-[0.12em] text-muted uppercase">
|
||||
Reproduciendo
|
||||
</p>
|
||||
<p class="mb-2.75 text-xs font-medium tracking-widest text-muted uppercase">Reproduciendo</p>
|
||||
<div class="mb-6 flex items-center gap-3 rounded-xl bg-accent-tint p-2.5">
|
||||
<Cover
|
||||
trackId={player.current.id}
|
||||
size="small"
|
||||
alt={player.current.title}
|
||||
class="h-10 w-10 shrink-0 rounded-[9px]"
|
||||
class="h-10 w-10 shrink-0 rounded-lg"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<div class="truncate text-sm font-semibold text-accent-soft">
|
||||
<div class="truncate text-sm text-accent-soft">
|
||||
{player.current.title}
|
||||
</div>
|
||||
<ArtistLink
|
||||
name={player.current.artist || '—'}
|
||||
ownerUserId={player.current.ownerUserId}
|
||||
class="mt-0.75 text-xs text-fg-3"
|
||||
class="mt-0.5 text-xs text-fg-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,7 +56,7 @@
|
||||
|
||||
<div class="mb-2.75 flex items-center justify-between gap-3">
|
||||
<span
|
||||
class="min-w-0 flex-1 truncate text-xs font-semibold tracking-[0.12em] text-muted uppercase"
|
||||
class="min-w-0 flex-1 truncate text-xs font-medium tracking-widest text-muted uppercase"
|
||||
>
|
||||
A continuación
|
||||
</span>
|
||||
@@ -85,16 +83,16 @@
|
||||
e.preventDefault();
|
||||
player.playQueueIndex(index);
|
||||
}}
|
||||
class="flex items-center gap-3 rounded-[11px] p-2.25 text-left hover:bg-hover"
|
||||
class="flex items-center gap-3 rounded-control p-2.25 text-left hover:bg-hover"
|
||||
>
|
||||
<Cover
|
||||
trackId={track.id}
|
||||
size="small"
|
||||
alt={track.title}
|
||||
class="h-9 w-9 shrink-0 rounded-lg opacity-90"
|
||||
class="h-10 w-10 shrink-0 rounded-lg opacity-80"
|
||||
/>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm font-medium text-fg">{track.title}</div>
|
||||
<div class="truncate text-xs text-fg">{track.title}</div>
|
||||
<ArtistLink
|
||||
name={track.artist || '—'}
|
||||
ownerUserId={track.ownerUserId}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
aria-label="Barra de progreso"
|
||||
class="group relative h-3.5 flex-1 cursor-pointer"
|
||||
>
|
||||
<div class="h-[3.5px] w-full overflow-hidden rounded-full bg-track">
|
||||
<div class="h-0.875 w-full overflow-hidden rounded-full bg-track">
|
||||
<div class="h-full rounded-full bg-accent" style="width:{player.progressPercent}%"></div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
trackId={player.current.id}
|
||||
size="small"
|
||||
alt={player.current.title}
|
||||
class="h-11 w-11 shrink-0 rounded-[11px] shadow-cover-xs md:h-12 md:w-12"
|
||||
class="h-11 w-11 shrink-0 rounded-xl md:h-12 md:w-12"
|
||||
/>
|
||||
{:else}
|
||||
<div class="h-11 w-11 shrink-0 rounded-[11px] bg-surface shadow-cover-xs md:h-12 md:w-12"></div>
|
||||
<div class="h-11 w-11 shrink-0 rounded-xl bg-surface md:h-12 md:w-12"></div>
|
||||
{/if}
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex min-w-0 items-center gap-1.5">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
aria-label="Aleatorio"
|
||||
class="transition-colors hover:text-fg {player.shuffle ? 'text-accent' : 'text-fg-3'}"
|
||||
>
|
||||
<Shuffle class="h-[15px] w-[15px]" strokeWidth={1.8} />
|
||||
<Shuffle class="h-3.75 w-3.75" strokeWidth={1.8} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
aria-label="Repetir"
|
||||
class="transition-colors hover:text-fg {player.repeat ? 'text-accent' : 'text-fg-3'}"
|
||||
>
|
||||
<Repeat class="h-[15px] w-[15px]" strokeWidth={1.8} />
|
||||
<Repeat class="h-3.75 w-3.75" strokeWidth={1.8} />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-2.25">
|
||||
<Volume2 class="h-[15px] w-[15px] shrink-0 text-fg-3" strokeWidth={1.8} />
|
||||
<Volume2 class="h-3.75 w-3.75 shrink-0 text-fg-3" strokeWidth={1.8} />
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
required={requireCover}
|
||||
icon={Disc3}
|
||||
gradient
|
||||
class="h-36 w-36 rounded-[16px]"
|
||||
class="h-36 w-36 rounded-2xl"
|
||||
/>
|
||||
</Field>
|
||||
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<p
|
||||
class="rounded-control border px-4 py-3 text-sm shadow-menu backdrop-blur-md {tones[
|
||||
tone
|
||||
]} {klass}"
|
||||
>
|
||||
<p class="rounded-control border px-4 py-3 text-sm backdrop-blur-md {tones[tone]} {klass}">
|
||||
{@render children()}
|
||||
</p>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<div class="flex flex-col items-center gap-2.5 text-center" style="width:{size}px">
|
||||
<div
|
||||
class="relative grid shrink-0 place-items-center overflow-hidden rounded-full bg-surface shadow-art ring-1 ring-line transition duration-300 ease-out ring-inset group-hover/artist:shadow-art-lg"
|
||||
class="relative grid shrink-0 place-items-center overflow-hidden rounded-full bg-surface ring-1 ring-line transition duration-300 ease-out ring-inset"
|
||||
style="width:{size}px;height:{size}px"
|
||||
>
|
||||
{#if imageUrl}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
};
|
||||
|
||||
const classes = $derived(
|
||||
`inline-flex items-center justify-center gap-2 rounded-xl font-semibold transition focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg focus-visible:outline-none disabled:opacity-40 ${sizes[size]} ${variants[variant]} ${klass}`
|
||||
`inline-flex items-center justify-center gap-2 rounded-xl transition focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg focus-visible:outline-none disabled:opacity-40 ${sizes[size]} ${variants[variant]} ${klass}`
|
||||
);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,15 +15,15 @@
|
||||
|
||||
<div class="flex flex-col items-center rounded-panel px-6 py-16 text-center {klass}">
|
||||
{#if Icon}
|
||||
<Icon class="h-9 w-9 text-fg-2" strokeWidth={1.3} />
|
||||
<Icon class="h-9 w-9 text-fg-2" strokeWidth={1.25} />
|
||||
{/if}
|
||||
{#if title}
|
||||
<h3 class="mt-6 font-display text-xl font-semibold tracking-[-0.025em] text-fg">
|
||||
<h3 class="mt-6 font-display text-xl font-medium tracking-tight text-fg">
|
||||
{title}
|
||||
</h3>
|
||||
{/if}
|
||||
{#if description}
|
||||
<p class="mt-2.5 max-w-sm text-sm leading-[1.65] text-fg-2">{description}</p>
|
||||
<p class="mt-2.5 max-w-sm text-sm leading-relaxed text-fg-2">{description}</p>
|
||||
{/if}
|
||||
{#if actions}
|
||||
<div class="mt-6 flex flex-wrap items-center justify-center gap-2.5">{@render actions()}</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
const delays = [0, 0.3, 0.6];
|
||||
</script>
|
||||
|
||||
<div class="flex items-end justify-center gap-[2px] {klass}" style="height:{size}px">
|
||||
<div class="flex items-end justify-center gap-0.5 {klass}" style="height:{size}px">
|
||||
{#each delays as delay (delay)}
|
||||
<span
|
||||
class="rounded-full bg-accent"
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
let { class: klass = '', style = '', children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="glass-panel animate-pop rounded-panel p-1.5 shadow-menu {klass}" {style}>
|
||||
<div class="glass-panel animate-pop rounded-panel border border-hairline p-1.5 {klass}" {style}>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" module>
|
||||
export const mediaCardArt =
|
||||
'aspect-square w-full rounded-art shadow-art transition duration-300 ease-out group-hover:shadow-art-lg';
|
||||
'aspect-square w-full rounded-art transition duration-300 ease-out';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -25,7 +25,7 @@
|
||||
{oncontextmenu}
|
||||
>
|
||||
{@render art(mediaCardArt)}
|
||||
<div class="mt-2.5 truncate text-sm font-medium text-fg">{title}</div>
|
||||
<div class="mt-2.5 truncate text-fg">{title}</div>
|
||||
{#if subtitle}
|
||||
<div class="mt-0.5 truncate text-xs text-fg-3">{subtitle}</div>
|
||||
{/if}
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
{onclick}
|
||||
class={classes}
|
||||
>
|
||||
<Icon class="h-[18px] w-[18px] shrink-0" strokeWidth={1.8} />
|
||||
<Icon class="h-4.5 w-4.5 shrink-0" strokeWidth={1.8} />
|
||||
{label}
|
||||
</a>
|
||||
{:else}
|
||||
<button {type} {onclick} class={classes}>
|
||||
<Icon class="h-[18px] w-[18px] shrink-0" strokeWidth={1.8} />
|
||||
<Icon class="h-4.5 w-4.5 shrink-0" strokeWidth={1.8} />
|
||||
{label}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
class="group/tile animate-enter block cursor-pointer {klass}"
|
||||
style="animation-delay:{Math.min(index, 10) * 45}ms"
|
||||
>
|
||||
<div class="relative aspect-square overflow-hidden rounded-art shadow-art">
|
||||
<div class="relative aspect-square overflow-hidden rounded-art">
|
||||
<MixArt
|
||||
items={mix.items}
|
||||
class="h-full w-full scale-100 transition duration-300 group-hover/tile:scale-105"
|
||||
@@ -37,7 +37,7 @@
|
||||
type="button"
|
||||
onclick={play}
|
||||
aria-label="Reproducir {mix.title}"
|
||||
class="absolute right-2.5 bottom-2.5 grid h-9.5 w-9.5 place-items-center rounded-full border border-on-art/12 bg-ink/76 text-fg opacity-0 backdrop-blur-[8px] transition-opacity duration-150 group-hover/tile:opacity-100"
|
||||
class="absolute right-2.5 bottom-2.5 grid h-9.5 w-9.5 place-items-center rounded-full border border-on-art/12 bg-ink/76 text-fg opacity-0 backdrop-blur transition-opacity duration-150 group-hover/tile:opacity-100"
|
||||
>
|
||||
<Play class="h-3.5 w-3.5" fill="currentColor" />
|
||||
</button>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
>
|
||||
<div
|
||||
bind:this={panel}
|
||||
class="animate-pop max-h-[85dvh] w-full overflow-y-auto {maxWidth} rounded-panel p-5 shadow-2xl backdrop-blur-[22px] transition-[background] duration-[600ms] outline-none sm:p-6 {panelClass}"
|
||||
class="animate-pop max-h-[85dvh] w-full overflow-y-auto {maxWidth} rounded-panel border border-hairline p-5 backdrop-blur-xl transition-[background] duration-500 outline-none sm:p-6 {panelClass}"
|
||||
style="background-color:var(--mf-panel-bg); background-image:var(--mf-modal-glow)"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
@@ -87,7 +87,7 @@
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
{#if eyebrow}
|
||||
<p class="mb-2.5 text-xs font-semibold tracking-[0.1em] text-muted uppercase">
|
||||
<p class="mb-2.5 text-xs font-semibold tracking-widest text-muted uppercase">
|
||||
{eyebrow}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
const delays = [0, 0.3, 0.6];
|
||||
</script>
|
||||
|
||||
<div class="absolute inset-0 flex items-end justify-center gap-[3px] bg-scrim/45 pb-2.5">
|
||||
<div class="absolute inset-0 flex items-end justify-center gap-0.75 bg-scrim/45 pb-2.5">
|
||||
{#each delays as delay (delay)}
|
||||
<span
|
||||
class="w-[3px] origin-bottom rounded-full bg-on-art"
|
||||
style="height:14px; animation:equalize .9s ease-in-out {delay}s infinite; animation-play-state:{paused
|
||||
class="h-3.5 w-0.75 origin-bottom rounded-full bg-on-art"
|
||||
style="animation:equalize .9s ease-in-out {delay}s infinite; animation-play-state:{paused
|
||||
? 'paused'
|
||||
: 'running'}"
|
||||
></span>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
let { id, name, trackCount, trackIds, updatedAt, index = 0 }: Props = $props();
|
||||
|
||||
const subtitle = $derived(`${trackCount} ${trackCount === 1 ? 'canción' : 'canciones'}`);
|
||||
const subtitle = $derived(`${trackCount} ${trackCount === 1 ? 'Canción' : 'Canciones'}`);
|
||||
</script>
|
||||
|
||||
<MediaCard href="/playlists/{id}" title={name} {subtitle} {index}>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
fallbackUrl={coverFallbackUrl}
|
||||
icon={Music}
|
||||
gradient
|
||||
class="h-36 w-36 rounded-[16px]"
|
||||
class="h-36 w-36 rounded-2xl"
|
||||
/>
|
||||
</Field>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4 rounded-[14px] bg-surface px-4 py-3.5">
|
||||
<div class="flex items-center justify-between gap-4 rounded-art bg-surface px-4 py-3.5">
|
||||
<div class="min-w-0">
|
||||
<div class="text-sm font-medium text-fg-2">Visibilidad</div>
|
||||
<div class="mt-0.5 text-xs text-muted">
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
const RADIUS: Record<SearchResultKind, string> = {
|
||||
track: 'rounded-control',
|
||||
album: 'rounded-[6px]',
|
||||
playlist: 'rounded-[13px]',
|
||||
album: 'rounded-md',
|
||||
playlist: 'rounded-art',
|
||||
user: 'rounded-full'
|
||||
};
|
||||
|
||||
@@ -37,9 +37,7 @@
|
||||
art
|
||||
}: Props = $props();
|
||||
|
||||
const artClass = $derived(
|
||||
`relative h-13 w-13 shrink-0 overflow-hidden shadow-art ${RADIUS[kind]}`
|
||||
);
|
||||
const artClass = $derived(`relative h-13 w-13 shrink-0 overflow-hidden ${RADIUS[kind]}`);
|
||||
</script>
|
||||
|
||||
{#snippet content()}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</script>
|
||||
|
||||
{#snippet heading()}
|
||||
<h2 class="font-display text-lg font-medium tracking-[-0.025em] text-fg">
|
||||
<h2 class="font-display text-lg font-medium tracking-tight text-fg">
|
||||
{title}
|
||||
</h2>
|
||||
{#if subtitle}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Hash } from '@lucide/svelte';
|
||||
|
||||
import { setContext } from 'svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
@@ -31,7 +33,7 @@
|
||||
index ? '32px' : '',
|
||||
'minmax(180px,1fr)',
|
||||
...meta.map((column) => column.width),
|
||||
'64px',
|
||||
'104px',
|
||||
action ? '36px' : ''
|
||||
]
|
||||
.filter((width) => width !== '')
|
||||
@@ -56,17 +58,17 @@
|
||||
|
||||
<div class="overflow-x-auto {klass}">
|
||||
<div
|
||||
class="grid track-grid items-center gap-3 border-b border-line pr-4 pb-3 pl-2 text-xs font-semibold tracking-[0.12em] text-muted uppercase sm:gap-8 sm:pr-6 sm:pl-3"
|
||||
class="grid track-grid items-center gap-3 border-b border-line pr-4 pb-3 pl-2 text-sm text-fg-3 sm:gap-8 sm:pr-6 sm:pl-3"
|
||||
style="--mf-track-cols:{columns}; --mf-track-cols-mobile:{columnsMobile}"
|
||||
>
|
||||
{#if index}
|
||||
<span class="text-center">#</span>
|
||||
<Hash size={16} strokeWidth={1.5} />
|
||||
{/if}
|
||||
<span>Título</span>
|
||||
<span class="transition-colors">Título</span>
|
||||
{#each meta as column (column.label)}
|
||||
<span class="hidden text-center sm:block">{column.label}</span>
|
||||
<span class="hidden text-center transition-colors sm:block">{column.label}</span>
|
||||
{/each}
|
||||
<span class="hidden text-center sm:block">Duración</span>
|
||||
<span class="hidden text-center transition-colors sm:block">Duración</span>
|
||||
{#if action}
|
||||
<span></span>
|
||||
{/if}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
artist,
|
||||
ownerUserId,
|
||||
coverSrc,
|
||||
coverSize = 'h-[42px] w-[42px]',
|
||||
coverSize = 'h-10.5 w-10.5',
|
||||
explicit = false,
|
||||
active = false,
|
||||
titleClass = '',
|
||||
@@ -39,12 +39,17 @@
|
||||
event.preventDefault();
|
||||
onClick();
|
||||
}
|
||||
|
||||
function onclick(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
onClick();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={onClick}
|
||||
{onclick}
|
||||
onkeydown={onKeydown}
|
||||
class="flex min-w-0 items-center gap-3.5 text-left"
|
||||
>
|
||||
@@ -62,10 +67,8 @@
|
||||
{#if explicit}
|
||||
<ExplicitBadge />
|
||||
{/if}
|
||||
<span
|
||||
class="min-w-0 truncate text-sm font-medium {active
|
||||
? 'text-accent-soft'
|
||||
: 'text-fg'} {titleClass}">{title}</span
|
||||
<span class="min-w-0 truncate text-sm {active ? 'text-accent-soft' : 'text-fg'} {titleClass}"
|
||||
>{title}</span
|
||||
>
|
||||
{@render badge?.()}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ export const EXPLORE_ALBUMS_LIMIT = 18;
|
||||
export const HOME_LATEST_PAGE_SIZE = 10;
|
||||
export const HOME_MIXES_LIMIT = 6;
|
||||
export const HOME_SHELF_LIMIT = 6;
|
||||
export const HOME_CONTINUE_LIMIT = 6;
|
||||
export const HOME_CONTINUE_LIMIT = 10;
|
||||
export const HOME_SPOTLIGHT_TRACKS_LIMIT = 5;
|
||||
export const HOME_POPULAR_MAX = 8;
|
||||
export const PLAYLIST_PICKER_PAGE_SIZE = 50;
|
||||
|
||||
@@ -9,7 +9,7 @@ export function fmtDurationLong(seconds: number): string {
|
||||
const total = Math.max(0, Math.floor(seconds || 0));
|
||||
const hours = Math.floor(total / 3600);
|
||||
const minutes = Math.floor((total % 3600) / 60);
|
||||
return hours > 0 ? `${hours} h ${minutes} min` : `${minutes} min`;
|
||||
return hours > 0 ? `${hours} H ${minutes} Min` : `${minutes} Min`;
|
||||
}
|
||||
|
||||
const dateFormatter = new Intl.DateTimeFormat('es', { dateStyle: 'medium' });
|
||||
@@ -17,3 +17,8 @@ const dateFormatter = new Intl.DateTimeFormat('es', { dateStyle: 'medium' });
|
||||
export function fmtDate(value: string | number | Date): string {
|
||||
return dateFormatter.format(new Date(value));
|
||||
}
|
||||
|
||||
export function fmtPlays(count: number | string | undefined): string {
|
||||
const value = Math.max(0, Math.floor(Number(count ?? 0)));
|
||||
return `${value} ${value === 1 ? 'Reproducción' : 'Reproducciones'}`;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ export function mixItemTarget(item: MixItem): TrackTarget {
|
||||
track: {
|
||||
id: item.trackId,
|
||||
title: item.title,
|
||||
artist: item.artist
|
||||
artist: item.artist,
|
||||
listensCount: item.listensCount
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface LikedTrack {
|
||||
artist?: string;
|
||||
explicit?: boolean;
|
||||
ownerUserId?: string | number | null;
|
||||
listensCount?: number | string;
|
||||
likedAt: number;
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ class LikedStore {
|
||||
artist: item.artist,
|
||||
explicit: item.explicit,
|
||||
ownerUserId: item.ownerUserId,
|
||||
listensCount: item.listensCount,
|
||||
likedAt: new Date(tracks[i].createdAt).getTime()
|
||||
};
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface PlayerTrack {
|
||||
duration: number;
|
||||
explicit?: boolean;
|
||||
ownerUserId?: string | number | null;
|
||||
listensCount?: number | string;
|
||||
}
|
||||
|
||||
export interface Playlist {
|
||||
@@ -23,6 +24,7 @@ export interface QueueItem {
|
||||
artist?: string;
|
||||
explicit?: boolean;
|
||||
ownerUserId?: string | number | null;
|
||||
listensCount?: number | string;
|
||||
}
|
||||
|
||||
export interface ApiTrackLike {
|
||||
@@ -31,6 +33,7 @@ export interface ApiTrackLike {
|
||||
artist?: string | null;
|
||||
isExplicit?: boolean;
|
||||
ownerUserId?: string | number | null;
|
||||
listensCount?: number | string;
|
||||
}
|
||||
|
||||
export function toQueueItems(tracks: ApiTrackLike[]): QueueItem[] {
|
||||
@@ -39,7 +42,8 @@ export function toQueueItems(tracks: ApiTrackLike[]): QueueItem[] {
|
||||
title: track.title,
|
||||
artist: track.artist ?? undefined,
|
||||
explicit: track.isExplicit,
|
||||
ownerUserId: track.ownerUserId
|
||||
ownerUserId: track.ownerUserId,
|
||||
listensCount: track.listensCount
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -68,7 +72,8 @@ function toTrack(item: QueueItem): PlayerTrack {
|
||||
artist: item.artist ?? '',
|
||||
duration: 0,
|
||||
explicit: item.explicit,
|
||||
ownerUserId: item.ownerUserId
|
||||
ownerUserId: item.ownerUserId,
|
||||
listensCount: item.listensCount
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ export function mergeRecentlyPlayed(history: ApiTrackLike[], limit: number): Que
|
||||
title: t.title,
|
||||
artist: t.artist,
|
||||
explicit: t.explicit,
|
||||
ownerUserId: t.ownerUserId
|
||||
ownerUserId: t.ownerUserId,
|
||||
listensCount: t.listensCount
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,7 +31,8 @@ export function mergeRecentlyPlayed(history: ApiTrackLike[], limit: number): Que
|
||||
title: item.title,
|
||||
artist: item.artist,
|
||||
explicit: item.explicit,
|
||||
ownerUserId: item.ownerUserId
|
||||
ownerUserId: item.ownerUserId,
|
||||
listensCount: item.listensCount
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -33,10 +33,12 @@
|
||||
rgba(6, 6, 9, 0) 70%
|
||||
);
|
||||
--mf-logo-grad: linear-gradient(140deg, oklch(0.78 0.15 145), oklch(0.62 0.13 185) 92%);
|
||||
--mf-logo-glow: 0 0 20px oklch(0.74 0.145 145 / 0.3);
|
||||
--mf-cover-grad: linear-gradient(150deg, oklch(0.5 0.13 145), oklch(0.16 0.035 145));
|
||||
--mf-liked-grad: linear-gradient(150deg, oklch(0.4 0.09 330), oklch(0.18 0.04 10));
|
||||
--mf-tile-shade: rgba(9, 9, 12, 0.95);
|
||||
--mf-tile-l: 0.34;
|
||||
--mf-tile-c: 0.13;
|
||||
--mf-tile-shade-l: 0.14;
|
||||
--mf-tile-shade-c: 0.05;
|
||||
|
||||
--mf-accent: oklch(0.74 0.145 145);
|
||||
--mf-accent-title: oklch(0.87 0.07 145);
|
||||
@@ -65,6 +67,7 @@
|
||||
|
||||
--mf-scrim: #000000;
|
||||
--mf-on-art: #fefdfb;
|
||||
--mf-on-art-2: #cac8c3;
|
||||
--mf-ink: #0a0a0d;
|
||||
--mf-cta-strong-bg: #ffffff;
|
||||
|
||||
@@ -125,28 +128,32 @@
|
||||
--mf-ink: #ffffff;
|
||||
--mf-cta-strong-bg: #17171a;
|
||||
|
||||
--mf-ambient:
|
||||
radial-gradient(52% 38% at 14% 0%, oklch(0.9 0.06 145 / 0.26), transparent 54%),
|
||||
radial-gradient(46% 34% at 78% 0%, oklch(0.9121 0.0497 145 / 0.2), transparent 56%);
|
||||
--mf-tile-l: 0.58;
|
||||
--mf-tile-c: 0.15;
|
||||
--mf-tile-shade-l: 0.42;
|
||||
--mf-tile-shade-c: 0.12;
|
||||
--mf-liked-grad: linear-gradient(150deg, oklch(0.62 0.12 330), oklch(0.46 0.08 10));
|
||||
|
||||
--mf-ambient: radial-gradient(46% 34% at 78% 0%, oklch(0.7476 0.121 145 / 0.2), transparent 56%);
|
||||
--mf-hero-bg: linear-gradient(
|
||||
105deg,
|
||||
oklch(0.9145 0.0476 145 / 0.34) 0%,
|
||||
oklch(0.9483 0.0186 145 / 0.11) 37%,
|
||||
oklch(0.97 0 145 / 0.03) 65%,
|
||||
oklch(0.7531 0.1172 145 / 0.34) 0%,
|
||||
oklch(0.8303 0.0641 145 / 0.11) 37%,
|
||||
oklch(0.88 0.03 145 / 0.03) 65%,
|
||||
rgba(255, 255, 255, 0) 92%
|
||||
);
|
||||
--mf-spotlight-bg: linear-gradient(
|
||||
100deg,
|
||||
oklch(0.9193 0.0434 145 / 0.28) 0%,
|
||||
oklch(0.9531 0.0145 145 / 0.1) 40%,
|
||||
oklch(0.97 0 145 / 0.03) 69%,
|
||||
oklch(0.7641 0.1097 145 / 0.28) 0%,
|
||||
oklch(0.8414 0.0566 145 / 0.1) 40%,
|
||||
oklch(0.88 0.03 145 / 0.03) 69%,
|
||||
rgba(255, 255, 255, 0) 96%
|
||||
);
|
||||
--mf-modal-glow: linear-gradient(
|
||||
135deg,
|
||||
oklch(0.9145 0.0476 145 / 0.34) 0%,
|
||||
oklch(0.9483 0.0186 145 / 0.11) 28%,
|
||||
oklch(0.97 0 145 / 0.03) 50%,
|
||||
oklch(0.7531 0.1172 145 / 0.34) 0%,
|
||||
oklch(0.8303 0.0641 145 / 0.11) 28%,
|
||||
oklch(0.88 0.03 145 / 0.03) 50%,
|
||||
rgba(255, 255, 255, 0) 70%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,13 +13,14 @@ function tc(alpha: number, l: number, hue: number): string {
|
||||
return `oklch(${r(lightness)} ${r(chroma)} ${r(hue, 1)} / ${alpha})`;
|
||||
}
|
||||
|
||||
// Light-mode counterpart of tc(): fades toward the light canvas (~0.97
|
||||
// lightness, 0 chroma at l=88) instead of toward black, so the "glow"
|
||||
// gradients read as a soft tint instead of a dark smudge on a light bg.
|
||||
// Light-mode counterpart of tc(): fades toward a soft tinted glass (~0.88
|
||||
// lightness) instead of toward black, so the "glow" gradients read as a
|
||||
// visible colored tint instead of a dark smudge — or, at the old lightness
|
||||
// floor of 0.97, an almost invisible wash next to the near-white canvas.
|
||||
function tcLight(alpha: number, l: number, hue: number): string {
|
||||
const depth = (88 - l) / 58;
|
||||
const lightness = 0.97 - 0.07 * depth;
|
||||
const chroma = Math.max(0, 0.06 * depth);
|
||||
const lightness = 0.88 - 0.16 * depth;
|
||||
const chroma = 0.03 + 0.11 * depth;
|
||||
return `oklch(${r(lightness)} ${r(chroma)} ${r(hue, 1)} / ${alpha})`;
|
||||
}
|
||||
|
||||
@@ -43,15 +44,27 @@ export function buildThemeTokens(hue: number, mode: 'dark' | 'light' = 'dark'):
|
||||
const H2 = r(h2, 1);
|
||||
const light = mode === 'light';
|
||||
|
||||
// Dark mode wants a bright, light accent (pops on a near-black canvas).
|
||||
// Light mode needs the opposite: the same hue pulled down to a dark,
|
||||
// saturated shade so it still reads against a near-white canvas — using
|
||||
// the dark-mode lightness here is what made accent text/icons/buttons
|
||||
// nearly invisible in light mode.
|
||||
const accentL = light ? 0.52 : L;
|
||||
const accentC = light ? 0.16 : C;
|
||||
const titleL = light ? 0.4 : 0.87;
|
||||
const titleC = light ? 0.13 : 0.07;
|
||||
const mutedL = light ? 0.48 : 0.79;
|
||||
const mutedC = light ? 0.06 : 0.035;
|
||||
|
||||
return {
|
||||
'--mf-accent': `oklch(${r(L)} ${r(C)} ${H})`,
|
||||
'--mf-accent-title': `oklch(0.87 0.07 ${H})`,
|
||||
'--mf-accent-muted': `oklch(0.79 0.035 ${H})`,
|
||||
'--mf-accent-soft-bg': `oklch(${r(L)} ${r(C)} ${H} / 0.08)`,
|
||||
'--mf-accent-line': `oklch(${r(L)} ${r(C)} ${H} / 0.2)`,
|
||||
'--mf-accent-hair': `oklch(${r(L)} ${r(C)} ${H} / 0.09)`,
|
||||
'--mf-accent-btn-bg': `oklch(${r(L)} ${r(C)} ${H} / 0.15)`,
|
||||
'--mf-accent-btn-bg-hover': `oklch(${r(L)} ${r(C)} ${H} / 0.24)`,
|
||||
'--mf-accent': `oklch(${r(accentL)} ${r(accentC)} ${H})`,
|
||||
'--mf-accent-title': `oklch(${r(titleL)} ${r(titleC)} ${H})`,
|
||||
'--mf-accent-muted': `oklch(${r(mutedL)} ${r(mutedC)} ${H})`,
|
||||
'--mf-accent-soft-bg': `oklch(${r(accentL)} ${r(accentC)} ${H} / 0.08)`,
|
||||
'--mf-accent-line': `oklch(${r(accentL)} ${r(accentC)} ${H} / 0.2)`,
|
||||
'--mf-accent-hair': `oklch(${r(accentL)} ${r(accentC)} ${H} / 0.09)`,
|
||||
'--mf-accent-btn-bg': `oklch(${r(accentL)} ${r(accentC)} ${H} / 0.15)`,
|
||||
'--mf-accent-btn-bg-hover': `oklch(${r(accentL)} ${r(accentC)} ${H} / 0.24)`,
|
||||
'--mf-bg': light ? `oklch(0.97 0.006 ${H})` : `oklch(0.068 0.008 ${H})`,
|
||||
'--mf-elevated': light ? `oklch(0.99 0.003 ${H})` : `oklch(0.11 0.012 ${H})`,
|
||||
'--mf-sidebar-bg': light
|
||||
@@ -61,7 +74,7 @@ export function buildThemeTokens(hue: number, mode: 'dark' | 'light' = 'dark'):
|
||||
'--mf-bar-bg': light ? `oklch(0.97 0.006 ${H} / 0.9)` : `oklch(0.105 0.012 ${H} / 0.9)`,
|
||||
'--mf-hairline': light ? `oklch(0.3 0.02 ${H} / 0.1)` : `oklch(0.62 0.03 ${H} / 0.08)`,
|
||||
'--mf-ambient': light
|
||||
? `radial-gradient(52% 38% at 14% 0%, ${tcLight(0.26, 30, h)}, transparent 54%), radial-gradient(46% 34% at 78% 0%, ${tcLight(0.2, 40, h)}, transparent 56%)`
|
||||
? `radial-gradient(46% 34% at 78% 0%, ${tcLight(0.2, 40, h)}, transparent 56%)`
|
||||
: `radial-gradient(52% 38% at 14% 0%, ${tc(0.26, 30, h)}, transparent 54%), radial-gradient(46% 34% at 78% 0%, ${tc(0.2, 40, h)}, transparent 56%)`,
|
||||
'--mf-hero-bg': light
|
||||
? `linear-gradient(105deg, ${tcLight(0.34, 42, h)} 0%, ${tcLight(0.11, 70, h)} 37%, ${tcLight(0.03, 88, h)} 65%, rgba(255,255,255,0) 92%)`
|
||||
@@ -72,9 +85,12 @@ export function buildThemeTokens(hue: number, mode: 'dark' | 'light' = 'dark'):
|
||||
'--mf-spotlight-bg': light
|
||||
? `linear-gradient(100deg, ${tcLight(0.28, 46, h)} 0%, ${tcLight(0.1, 74, h)} 40%, ${tcLight(0.03, 88, h)} 69%, rgba(255,255,255,0) 96%)`
|
||||
: `linear-gradient(100deg, ${tc(0.28, 46, h)} 0%, ${tc(0.1, 74, h)} 40%, ${tc(0.03, 88, h)} 69%, rgba(6,6,9,0) 96%)`,
|
||||
'--mf-logo-grad': `linear-gradient(140deg, oklch(0.78 0.15 ${H}), oklch(0.62 0.13 ${H2}) 92%)`,
|
||||
'--mf-logo-glow': `0 0 20px oklch(${r(L)} ${r(C)} ${H} / 0.3)`,
|
||||
'--mf-cover-grad': `linear-gradient(150deg, oklch(0.5 0.13 ${H}), oklch(0.16 0.035 ${H}))`
|
||||
'--mf-logo-grad': light
|
||||
? `linear-gradient(140deg, oklch(0.68 0.16 ${H}), oklch(0.5 0.14 ${H2}) 92%)`
|
||||
: `linear-gradient(140deg, oklch(0.78 0.15 ${H}), oklch(0.62 0.13 ${H2}) 92%)`,
|
||||
'--mf-cover-grad': light
|
||||
? `linear-gradient(150deg, oklch(0.62 0.14 ${H}), oklch(0.44 0.08 ${H}))`
|
||||
: `linear-gradient(150deg, oklch(0.5 0.13 ${H}), oklch(0.16 0.035 ${H}))`
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
player,
|
||||
toQueueItems,
|
||||
type ApiTrackLike,
|
||||
type QueueItem
|
||||
} from '$lib/player/player.svelte';
|
||||
import { player, toQueueItems, type ApiTrackLike, type QueueItem } from '$lib/player/player.svelte';
|
||||
|
||||
export type TrackTarget = { track: ApiTrackLike };
|
||||
|
||||
@@ -27,6 +22,10 @@ export function targetExplicit(target: TrackTarget): boolean {
|
||||
return target.track.isExplicit ?? false;
|
||||
}
|
||||
|
||||
export function targetListensCount(target: TrackTarget): number | string | undefined {
|
||||
return target.track.listensCount;
|
||||
}
|
||||
|
||||
export function isTargetCurrent(target: TrackTarget): boolean {
|
||||
return player.current.id === target.track.id;
|
||||
}
|
||||
@@ -42,7 +41,8 @@ export function targetForQueueItem(item: QueueItem): TrackTarget {
|
||||
title: item.title,
|
||||
artist: item.artist,
|
||||
isExplicit: item.explicit,
|
||||
ownerUserId: item.ownerUserId
|
||||
ownerUserId: item.ownerUserId,
|
||||
listensCount: item.listensCount
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user