// Shared poster-tile rendering - used by the home carousels and the library // grid, so the two stay visually/behaviorally identical instead of drifting. export function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } /** "m:ss" (or "h:mm:ss" past an hour) - shared by the album track list and * the music player, so a track's listed duration and its playing position * always read the same way. */ export function formatDuration(totalSeconds) { const s = Math.max(0, Math.round(totalSeconds || 0)); const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = s % 60; const pad = (n) => String(n).padStart(2, '0'); return h > 0 ? `${h}:${pad(m)}:${pad(sec)}` : `${m}:${pad(sec)}`; } // Small deterministic palette so the same person always gets the same // avatar color across a session, without needing the server to assign one // - shared by the video player's presence overlay and the music player's // listener list, so the same person reads as the same color in either. const AVATAR_COLORS = ['#52b54b', '#4c8bf5', '#f5a623', '#e05d5d', '#9b59b6', '#1abc9c', '#e67e22', '#3498db']; export function colorForName(name) { let hash = 0; for (let i = 0; i < name.length; i++) hash = (hash * 31 + name.charCodeAt(i)) >>> 0; return AVATAR_COLORS[hash % AVATAR_COLORS.length]; } export function initialsFor(name) { return (name || '').trim().slice(0, 2).toUpperCase() || '?'; } // Drill-down targets: a tile for a type with no direct playback (Series, // MusicArtist, MusicAlbum) links into its own browsing page instead of // getting a "start a party" button - canWatchParty is already false for // all three server-side (see WATCH_PARTY_TYPES in server/src/index.js). const DRILL_DOWN = { Series: { route: 'series', label: 'Voir les épisodes' }, MusicArtist: { route: 'artist', label: 'Voir les albums' }, MusicAlbum: { route: 'album', label: 'Voir les morceaux' } }; export function tileHtml(item) { const drill = DRILL_DOWN[item.type]; const drillHref = drill ? `#/${drill.route}/${item.id}` : null; const partyLabel = item.type === 'Audio' ? 'Écouter ensemble' : 'Lancer une watch party'; return `