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:
@@ -1,24 +1,107 @@
|
||||
const GREETINGS_BY_HOUR: { until: number; pool: string[] }[] = [
|
||||
{
|
||||
until: 6,
|
||||
pool: ['¿Aún despierto?', 'La noche también tiene banda sonora', 'Un ratito más, con música']
|
||||
},
|
||||
{
|
||||
until: 12,
|
||||
pool: ['Buenos días', 'Que empiece bien la mañana', 'Un nuevo día, una nueva lista']
|
||||
},
|
||||
{ until: 14, pool: ['Buenos días', 'El mediodía sabe mejor con música'] },
|
||||
{
|
||||
until: 19,
|
||||
pool: ['Buenas tardes', 'La tarde pide su banda sonora', 'Un respiro, con algo de música']
|
||||
},
|
||||
{ until: 23, pool: ['Buenas noches', 'Hora de bajar el ritmo', 'Que la noche te acompañe'] },
|
||||
{ until: 24, pool: ['¿Aún despierto?', 'La noche también tiene banda sonora'] }
|
||||
interface SpecialDate {
|
||||
month: number;
|
||||
day: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
const SPECIAL_DATES: SpecialDate[] = [
|
||||
{ month: 1, day: 1, message: 'Nuevo año, nueva música.' },
|
||||
{ month: 2, day: 14, message: 'Hoy también hay canciones para eso.' },
|
||||
{ month: 10, day: 31, message: 'Esta noche suena diferente.' },
|
||||
{ month: 12, day: 24, message: 'Nochebuena. Que no pare la música.' },
|
||||
{ month: 12, day: 25, message: 'Feliz Navidad. Pon tu banda sonora.' },
|
||||
{ month: 12, day: 31, message: 'Último día del año. Haz que suene bien.' }
|
||||
];
|
||||
|
||||
export function pickGreeting(hour: number): string {
|
||||
const pool =
|
||||
GREETINGS_BY_HOUR.find((slot) => hour < slot.until)?.pool ?? GREETINGS_BY_HOUR[0].pool;
|
||||
const greeting = pool[Math.floor(Math.random() * pool.length)];
|
||||
return /[.!?]$/.test(greeting) ? greeting : `${greeting}.`;
|
||||
const BIRTHDAY_MESSAGE = 'Hoy toca celebrar. Tú eliges la música.';
|
||||
|
||||
const HOUR_SLOTS = [6, 9, 12, 15, 18, 21, 24];
|
||||
|
||||
const WEEKLY_SCHEDULE: string[][] = [
|
||||
[
|
||||
'Domingo de madrugada.',
|
||||
'Buenos días. Tómate el domingo con calma.',
|
||||
'Domingo. Empieza despacio.',
|
||||
'Una mañana tranquila merece buena música.',
|
||||
'Domingo por la tarde. Hora de bajar el ritmo.',
|
||||
'La semana empieza a despedirse.',
|
||||
'Una última canción antes de volver a empezar.'
|
||||
],
|
||||
[
|
||||
'La semana acaba de empezar.',
|
||||
'Buenos días. Empezamos otra semana.',
|
||||
'Lunes. Dale una banda sonora al día.',
|
||||
'Una pausa musical para este lunes.',
|
||||
'El lunes todavía no ha terminado.',
|
||||
'El lunes ya empieza a quedar atrás.',
|
||||
'Sobrevive al lunes con una última canción.'
|
||||
],
|
||||
[
|
||||
'La noche sigue sonando.',
|
||||
'Buenos días. Martes en marcha.',
|
||||
'La semana ya está cogiendo ritmo.',
|
||||
'Martes al mediodía. ¿Qué escuchamos?',
|
||||
'Una tarde cualquiera necesita música.',
|
||||
'El día empieza a bajar el ritmo.',
|
||||
'Un martes más llega a su fin.'
|
||||
],
|
||||
[
|
||||
'Mitad de semana, mitad de noche.',
|
||||
'Buenos días. Ya es miércoles.',
|
||||
'Ya estamos en el ecuador de la semana.',
|
||||
'Miércoles al mediodía. Un poco de música.',
|
||||
'La semana ya va cuesta abajo.',
|
||||
'Unas canciones para terminar el miércoles.',
|
||||
'Mitad de semana superada.'
|
||||
],
|
||||
[
|
||||
'Una noche más antes del fin de semana.',
|
||||
'Buenos días. Ya casi estamos.',
|
||||
'Jueves. El fin de semana se acerca.',
|
||||
'Una pausa musical antes de seguir.',
|
||||
'Queda poco para desconectar.',
|
||||
'Ya se empieza a notar el viernes.',
|
||||
'Una noche más y llega el fin de semana.'
|
||||
],
|
||||
[
|
||||
'La semana ha terminado. La noche continúa.',
|
||||
'Buenos días. Es viernes.',
|
||||
'Viernes. Hoy suena diferente.',
|
||||
'Ya huele a fin de semana.',
|
||||
'Últimas horas de la semana.',
|
||||
'Se acabó la semana. Dale al play.',
|
||||
'Viernes por la noche. Tú eliges la banda sonora.'
|
||||
],
|
||||
[
|
||||
'La noche es joven. Que siga sonando.',
|
||||
'Buenos días. Hoy no hay prisa.',
|
||||
'Sábado. Empieza el día a tu ritmo.',
|
||||
'Mediodía de sábado. ¿Qué ponemos?',
|
||||
'Una tarde de sábado pide música.',
|
||||
'El sábado todavía tiene mucho por delante.',
|
||||
'Esta noche puede sonar cualquier cosa.'
|
||||
]
|
||||
];
|
||||
|
||||
export interface Birthday {
|
||||
month: number;
|
||||
day: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* `birthday` is optional because the user's date of birth isn't part of the
|
||||
* session/profile data yet. Wire it up here once that field exists upstream.
|
||||
*/
|
||||
export function pickGreeting(date: Date, birthday?: Birthday | null): string {
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
|
||||
if (birthday && birthday.month === month && birthday.day === day) return BIRTHDAY_MESSAGE;
|
||||
|
||||
const special = SPECIAL_DATES.find((d) => d.month === month && d.day === day);
|
||||
if (special) return special.message;
|
||||
|
||||
const hour = date.getHours();
|
||||
const slot = HOUR_SLOTS.findIndex((until) => hour < until);
|
||||
return WEEKLY_SCHEDULE[date.getDay()][slot === -1 ? 0 : slot];
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { createApiClient } from '$lib/server/api';
|
||||
import type { YouTubeAlbumResult, YouTubeSong } from '$lib/types';
|
||||
|
||||
const YOUTUBE_FILLER_QUERIES = [
|
||||
export const YOUTUBE_FILLER_QUERIES = [
|
||||
'pop',
|
||||
'rock',
|
||||
'reggaeton',
|
||||
|
||||
Reference in New Issue
Block a user