feat(web-player): rebuild Inicio and Buscar to the design handoff

Applies the handoff's layout and image shapes while keeping the current
Musify design system (Inter, colors, tokens) — no font or palette swap.

Inicio now stacks distinct shelves instead of repeated square grids:
recently played rows, a rail of recently listened albums with an "Álbum"
badge, a "Hecho para ti" bento of mixes, a rail of playlists with a
trailing dashed "Ver todas" tile, tall "Canciones nuevas" posters with a
"Nuevo" badge, a rail of circular top-artist avatars, and a recommended
rail. Buscar splits into Álbumes (square cards + badge), Usuarios (a
circular-avatar directory) and a flat Canciones list.

Top artists and the search user directory are filler data until the
backend exposes them. New shared pieces: Rail, CoverBadge, ArtistAvatar,
MixBentoTile. The orphaned TrackTile and MixCard are removed.
This commit is contained in:
2026-07-22 04:40:14 +02:00
parent 105681e1db
commit 9132e67371
14 changed files with 612 additions and 391 deletions
+12 -9
View File
@@ -1,15 +1,17 @@
import type { PageServerLoad, Actions } from './$types';
import { createApiClient, requireUser } from '$lib/server/api';
import { fetchYoutubeFiller } from '$lib/server/youtube';
import { shuffle } from '$lib/collections';
import { addTrackAction, addYouTubeToPlaylistAction } from '$lib/server/playlistActions';
import {
HOME_ALBUMS_LIMIT,
HOME_ARTISTS_LIMIT,
HOME_LATEST_PAGE_SIZE,
HOME_MIXES_BENTO,
HOME_SHELF_LIMIT,
YOUTUBE_FILLER_LIMIT
} from '$lib/config';
import { pickGreeting } from '$lib/server/greeting';
import { pickTopArtists } from '$lib/server/topArtists';
export const load: PageServerLoad = async ({ locals, url, fetch }) => {
const user = requireUser(locals, url);
@@ -40,23 +42,24 @@ export const load: PageServerLoad = async ({ locals, url, fetch }) => {
recentlyPlayedPromise
]);
const playlistItems = playlists.data?.items ?? [];
const latestItems = latest.data?.items ?? [];
return {
greeting: pickGreeting(new Date().getHours()),
latest: shuffle((latest.data?.items ?? []).map((track) => ({ kind: 'local' as const, track }))),
latestHasNext: Boolean(latest.data?.hasNextPage),
recentlyPlayed: recentlyPlayed.data ?? [],
albums: albums.data ?? [],
mixes: (mixes.data ?? []).slice(0, HOME_MIXES_BENTO),
playlists: playlistItems.slice(0, HOME_SHELF_LIMIT),
playlistsHasMore: playlistItems.length > HOME_SHELF_LIMIT,
newReleases: latestItems,
topArtists: pickTopArtists(latestItems, HOME_ARTISTS_LIMIT),
youtubeFiller: fetchYoutubeFiller(
api,
locals.accessToken,
YOUTUBE_FILLER_LIMIT,
undefined,
user.sub
),
playlists: playlistItems.slice(0, HOME_SHELF_LIMIT),
playlistsHasMore: playlistItems.length > HOME_SHELF_LIMIT,
albums: albums.data ?? [],
mixes: (mixes.data ?? []).slice(0, HOME_SHELF_LIMIT),
recentlyPlayed: recentlyPlayed.data ?? []
)
};
};