Add likes, follows and playlist visibility; redesign web-player UI

Backend:
- New Likes feature (toggle/get liked tracks) and endpoints
- User follow/unfollow, listening stats, public user profile
- Playlist visibility (public/private) support
- Various auth and DTO/response adjustments to support the above

Frontend (web-player):
- New design system pass: theme tokens/mode (light/dark), glass menu,
  tabs bar, player dock/extras/queue components, mix tiles, artist link
- New routes: Liked, public user profile (/u/[id]), likes API proxy
- Search refactor: shared SearchBox component used by TopBar and
  MobileHeader (search now reachable on mobile), live results as you
  type instead of requiring Enter, dropped the "Buscando…" quick-result
  preview
- Removed now-unused components (MobileNav, CoverBadge, MixBentoTile,
  PlayPauseOverlay, SuggestionsTable, userDirectory)
- Design handoff reference docs added under web-player/design_handoff_musify_player
  and web-player/docs/design-system-refactor-plan.md
This commit is contained in:
2026-09-14 23:42:45 +02:00
parent 389b3b9244
commit dc1ba89a6d
166 changed files with 14271 additions and 1982 deletions
@@ -1,5 +1,7 @@
<script lang="ts">
import { player } from '$lib/player/player.svelte';
import Shuffle from '@lucide/svelte/icons/shuffle';
import Repeat from '@lucide/svelte/icons/repeat';
interface Props {
compact?: boolean;
@@ -10,11 +12,23 @@
const sideClass = $derived(
compact
? 'grid h-10 w-10 place-items-center text-fg opacity-70 transition active:scale-90'
: 'flex text-fg opacity-70 transition hover:opacity-100 active:scale-90'
: 'flex text-fg-2 transition-colors hover:text-fg active:scale-90'
);
</script>
<div class="flex shrink-0 items-center {compact ? 'gap-0.5' : 'gap-5'}">
<div class="flex shrink-0 items-center {compact ? 'gap-0.5' : 'gap-3.5'}">
{#if !compact}
<button
type="button"
onclick={() => player.toggleShuffle()}
aria-pressed={player.shuffle}
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} />
</button>
{/if}
<button type="button" onclick={() => player.previous()} aria-label="Anterior" class={sideClass}>
<svg width="16" height="14" viewBox="0 0 16 14" fill="currentColor">
<rect x="0" y="0" width="2.5" height="14" rx="1" />
@@ -26,16 +40,16 @@
type="button"
onclick={() => player.toggle()}
aria-label={player.playing ? 'Pausar' : 'Reproducir'}
class="grid h-[36px] w-[36px] shrink-0 place-items-center rounded-full bg-fg text-bg shadow-lg transition duration-100 active:scale-95"
class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-cta-strong text-ink transition duration-150 hover:brightness-95"
>
{#if player.playing}
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
<rect x="1.5" y="0" width="3.5" height="12" rx="1" />
<rect x="7" y="0" width="3.5" height="12" rx="1" />
<svg width="15" height="15" viewBox="0 0 15 15" fill="currentColor">
<rect x="3" y="1" width="3" height="13" rx="1" />
<rect x="9" y="1" width="3" height="13" rx="1" />
</svg>
{:else}
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
<path d="M2.5 0 12 6l-9.5 6z" />
<svg width="15" height="15" viewBox="0 0 15 15" fill="currentColor">
<path d="M3 1.5v12l10-6z" />
</svg>
{/if}
</button>
@@ -46,4 +60,16 @@
<rect x="13.5" y="0" width="2.5" height="14" rx="1" />
</svg>
</button>
{#if !compact}
<button
type="button"
onclick={() => player.toggleRepeat()}
aria-pressed={player.repeat}
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} />
</button>
{/if}
</div>