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
@@ -0,0 +1,50 @@
<script lang="ts">
import { page } from '$app/state';
import { isSectionActive } from '$lib/navigation.svelte';
import type { SessionUser } from '$lib/types';
import Home from '@lucide/svelte/icons/house';
import Compass from '@lucide/svelte/icons/compass';
import ListMusic from '@lucide/svelte/icons/list-music';
import Heart from '@lucide/svelte/icons/heart';
interface Props {
user: SessionUser | null;
class?: string;
}
let { user, class: klass = '' }: Props = $props();
const links = $derived(
user
? [
{ href: '/', label: 'Inicio', icon: Home },
{ href: '/explore', label: 'Descubrir', icon: Compass },
{ href: '/playlists', label: 'Playlists', icon: ListMusic },
{ href: '/liked', label: 'Me gusta', icon: Heart }
]
: [{ href: '/explore', label: 'Descubrir', icon: Compass }]
);
function isActive(href: string) {
return isSectionActive(href, page.url.pathname, page.data.section);
}
</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}"
style="padding-bottom:calc(0.4375rem + var(--mf-safe-b))"
>
{#each links as link (link.href)}
{@const active = isActive(link.href)}
<a
href={link.href}
aria-current={active ? 'page' : undefined}
class="flex flex-col items-center gap-1.25 rounded-xl px-2.5 py-1.5 {active
? 'text-fg'
: 'text-fg-2'}"
>
<link.icon class="h-[17px] w-[17px]" strokeWidth={1.6} />
<span class="text-[10px] font-medium">{link.label}</span>
</a>
{/each}
</nav>