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
+42
View File
@@ -0,0 +1,42 @@
export interface HomeArtist {
id: string;
name: string;
}
const FILLER_ARTISTS = [
'Nova Reyes',
'The Paper Lanterns',
'Kairo',
'Lucía Marés',
'Midnight Transit',
'Sundown Kids',
'Aria Volkov',
'El Bosque Rojo',
'Halcyon',
'Marea Baja'
];
export function pickTopArtists(tracks: { artist?: string | null }[], limit: number): HomeArtist[] {
const seen = new Set<string>();
const artists: HomeArtist[] = [];
for (const track of tracks) {
for (const name of (track.artist ?? '').split(',').map((part) => part.trim())) {
const key = name.toLowerCase();
if (!name || seen.has(key)) continue;
seen.add(key);
artists.push({ id: name, name });
if (artists.length >= limit) return artists;
}
}
for (const name of FILLER_ARTISTS) {
const key = name.toLowerCase();
if (seen.has(key)) continue;
seen.add(key);
artists.push({ id: name, name });
if (artists.length >= limit) break;
}
return artists;
}
@@ -0,0 +1,24 @@
export interface DirectoryUser {
id: string;
name: string;
handle: string;
}
const DIRECTORY: DirectoryUser[] = [
{ id: 'u1', name: 'Alex Rivera', handle: 'alexrivera' },
{ id: 'u2', name: 'Marina Cordero', handle: 'marinac' },
{ id: 'u3', name: 'Diego Fuentes', handle: 'dfuentes' },
{ id: 'u4', name: 'Lucía Marés', handle: 'luciamares' },
{ id: 'u5', name: 'Noah Bennett', handle: 'noahb' },
{ id: 'u6', name: 'Sara Ibáñez', handle: 'saraib' },
{ id: 'u7', name: 'Kairo', handle: 'kairo' },
{ id: 'u8', name: 'Valentina Ortiz', handle: 'valen' }
];
export function searchUsers(query: string, limit = 12): DirectoryUser[] {
const term = query.trim().toLowerCase();
if (!term) return [];
return DIRECTORY.filter(
(user) => user.name.toLowerCase().includes(term) || user.handle.toLowerCase().includes(term)
).slice(0, limit);
}