import React, { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { MediaItem } from '../api/jellyfin'; import { useJellyfinContext } from '../context/JellyfinContext/JellyfinContext'; import { usePlaybackContext } from '../context/PlaybackContext/PlaybackContext'; import { InstantMixService } from '../services/InstantMixService'; import { useInstantMix } from '../hooks/useInstantMix'; import { useJellyfinTracksData } from '../hooks/Jellyfin/Infinite/useJellyfinTracksData'; import { MediaList } from '../components/MediaList'; import { CarDashboardDemo } from '../components/CarDashboardDemo'; import './HomeEnhanced.css'; interface DailyMix { id: string; name: string; description: string; image: string; tracks: MediaItem[]; lastRefresh: number; } interface ThemedMix { title: string; description: string; theme: string; image?: string; tracks: MediaItem[]; } interface DiscoverPreview { title: string; description?: string; tracks: MediaItem[]; } export const HomeEnhanced: React.FC = () => { const navigate = useNavigate(); const api = useJellyfinContext(); const { playTrack, setCurrentPlaylistSimple } = usePlaybackContext(); const { playInstantMix } = useInstantMix(); const { items: allTracks, isLoading: tracksLoading } = useJellyfinTracksData(); const [backgroundImages, setBackgroundImages] = useState([]); const [currentImageIndex, setCurrentImageIndex] = useState(0); const [themedMixes, setThemedMixes] = useState([]); const [recentlyPlayed, setRecentlyPlayed] = useState([]); const [discoverPreview, setDiscoverPreview] = useState(null); const [welcomePrompt, setWelcomePrompt] = useState(null); // Loading states for individual sections const [loadingStates, setLoadingStates] = useState({ background: true, themedMixes: true, recentlyPlayed: true, discover: true }); const instantMixService = InstantMixService.getInstance(); const imageRotationRef = useRef(null); // --- Welcome prompt rotation: automatic, deterministic by hour, weekday, week-of-month and month --- const weekOfMonth = (d: Date): number => { const day = d.getDate(); return Math.ceil((day - d.getDay()) / 7) || Math.ceil(day / 7); }; const pickWelcomePrompt = (): string => { const now = new Date(); const hour = now.getHours(); const dow = now.getDay(); // 0-6 Sun-Sat const wom = weekOfMonth(now); // 1-based const month = now.getMonth(); // 0-11 const basePrompts = [ 'Find something new to love today.', 'Curated picks waiting for you.', 'Press play and let the day unfold.', 'Fresh tracks, every time you visit.', 'Your soundtrack for the moment.' ]; const timePrompts: Record = { dawn: ['Gentle dawns need soft sounds.', 'Rise with mellow melodies.'], morning: ['Kickstart your morning with energy.', 'Coffee & tunes, a perfect pairing.'], midday: ['Midday melodies to keep you moving.', 'A quick soundtrack for lunch.'], afternoon: ['Afternoon grooves to keep focus.', 'Afternoon flow: tracks to finish strong.'], evening: ['Evening grooves to unwind.', 'Settle in with some sunset sessions.'], night: ['Late-night beats and chilled vibes.', 'Soundtrack for the midnight wanderer.'] }; const dowPrompts: Record = { 1: ['Motivation Monday: set the tone with a new playlist.'], 2: ['Tune-up Tuesday: explore a fresh artist.'], 3: ['Wellness Wednesday: mellow tracks to balance the week.'], 4: ['Throwback Thursday: rediscover an old favorite.'], 5: ['Feel-good Friday: start the weekend with a beat.'], 6: ['Weekend Vibes: time to relax and groove.'], 0: ['Sunday Chill: slow down with soothing sounds.'] }; const weekPrompts: Record = { 1: ['Fresh month — try something new this week.'], 2: ['Mid-month discoveries await.'], 3: ['Late-month picks: dig deeper into your library.'], 4: ['End-of-month wrap: your month in music.'] }; const monthPrompts: Record = { 11: ['Holiday classics & cozy new finds.'], 0: ['New year, new tracks to discover.'], 5: ['Summer vibes: poolside & roadtrip-ready.'], 6: ['Festival season feels — find your anthem.'] }; let timeBucket = 'morning'; if (hour >= 5 && hour < 7) timeBucket = 'dawn'; else if (hour >= 7 && hour < 12) timeBucket = 'morning'; else if (hour >= 12 && hour < 13) timeBucket = 'midday'; else if (hour >= 13 && hour < 17) timeBucket = 'afternoon'; else if (hour >= 17 && hour < 20) timeBucket = 'evening'; else timeBucket = 'night'; const pool: string[] = []; pool.push(...basePrompts); if (timePrompts[timeBucket]) pool.push(...timePrompts[timeBucket]); if (dowPrompts[dow]) pool.push(...dowPrompts[dow]); if (weekPrompts[wom]) pool.push(...weekPrompts[wom]); if (monthPrompts[month]) pool.push(...monthPrompts[month]); // Deterministic pick that changes predictably with time: combination of date parts const seed = now.getFullYear() + (now.getMonth() + 1) * 31 + now.getDate() * 7 + hour + dow + wom; const idx = Math.abs(seed * 97) % Math.max(1, pool.length); return pool[idx] || pool[Math.floor(Math.random() * pool.length)]; }; useEffect(() => { // update prompt immediately and at minute boundaries so it stays in sync with hour/day/week changes const update = () => setWelcomePrompt(pickWelcomePrompt()); update(); // compute ms until next minute to align updates const now = new Date(); const msToNextMinute = (60 - now.getSeconds()) * 1000 - now.getMilliseconds(); const timeoutId = window.setTimeout(() => { update(); // after aligning, update every 60s const intervalId = window.setInterval(update, 60000); // store intervalId on cleanup via closure (window as any).__homeWelcomeInterval = intervalId; }, msToNextMinute); return () => { clearTimeout(timeoutId); const id = (window as any).__homeWelcomeInterval; if (id) clearInterval(id); }; }, []); useEffect(() => { // Show UI immediately and load content in background if (!tracksLoading && allTracks.length > 0) { initializeHomeDataAsync(); } // UI shows immediately regardless of loading state return () => { if (imageRotationRef.current) { clearInterval(imageRotationRef.current); } }; }, [tracksLoading, allTracks]); // Start rotation when images are loaded useEffect(() => { setupImageRotation(); }, [backgroundImages]); const initializeHomeDataAsync = async () => { // Start loading everything in parallel without blocking UI const loadingPromises = [ loadBackgroundImages(), loadDailyMixes(), loadThemedMixes(), loadRecentlyPlayed() ]; // Load discover with its own error handling (independent of other content) setTimeout(() => { loadDiscoverPreview(); // Load discover with a slight delay to not block other content }, 500); Promise.all(loadingPromises).then(() => { // Generate recommendations after we have recently played generateRecommendedTracks(); }).catch((error) => { console.error('Error initializing home data:', error); }); // Start image rotation immediately if we have images setupImageRotation(); }; const loadBackgroundImages = async () => { try { // Get tracks with album art const tracksWithArt = allTracks .filter(track => track.AlbumId && track.ImageTags?.Primary) .slice(0, 8); // Take top 8 for rotation const images = tracksWithArt.map(track => api.getImageUrl(track, 'Primary', { width: 2560, height: 1440 }) // Higher resolution for full screen ); if (images.length > 0) { setBackgroundImages(images); // Rotation will start via useEffect } } catch (error) { console.error('Error loading background images:', error); } finally { setLoadingStates(prev => ({ ...prev, background: false })); } }; const setupImageRotation = () => { if (imageRotationRef.current) { clearInterval(imageRotationRef.current); } if (backgroundImages.length > 1) { imageRotationRef.current = setInterval(() => { setCurrentImageIndex(prev => (prev + 1) % backgroundImages.length); }, 6000); // Change every 6 seconds (faster rotation) } }; const loadDiscoverPreview = async () => { try { // Determine the correct backend URL based on environment let fetchUrl; if (window.location.hostname === 'music.samcloud.ca') { fetchUrl = '/api/discover'; } else { const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5140'; fetchUrl = `${backendUrl}/api/discover`; } console.log('Home: Fetching discover data from:', fetchUrl); // Add timeout using AbortController const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 8000); // Increased to 8 seconds const response = await fetch(fetchUrl, { signal: controller.signal, headers: { 'Accept': 'application/json', } }); clearTimeout(timeoutId); if (response.ok) { const data = await response.json(); console.log('Home: Received discover data:', data); // Try multiple sections for preview (prioritize for_you, then new_releases, then trending) if (data.for_you && data.for_you.tracks && data.for_you.tracks.length > 0) { console.log('Home: Using for_you section with', data.for_you.tracks.length, 'tracks'); setDiscoverPreview({ title: data.for_you.title || 'Made for You', description: data.for_you.description || 'Personalized tracks available for download', tracks: data.for_you.tracks.slice(0, 12) }); } else if (data.new_releases && data.new_releases.tracks && data.new_releases.tracks.length > 0) { console.log('Home: Using new_releases section with', data.new_releases.tracks.length, 'tracks'); setDiscoverPreview({ title: data.new_releases.title || 'New Releases', description: data.new_releases.description || 'Fresh tracks available for download', tracks: data.new_releases.tracks.slice(0, 12) }); } else if (data.trending && data.trending.tracks && data.trending.tracks.length > 0) { console.log('Home: Using trending section with', data.trending.tracks.length, 'tracks'); setDiscoverPreview({ title: data.trending.title || 'Trending Now', description: data.trending.description || 'Popular tracks available for download', tracks: data.trending.tracks.slice(0, 12) }); } else { console.log('Home: No valid backend sections found'); setDiscoverPreview({ title: 'Discover Unavailable', description: 'Backend discovery service returned no content', tracks: [] }); } } else { console.log('Home: Discover server responded with error:', response.status); setDiscoverPreview({ title: 'Discover Unavailable', description: 'Backend discovery service is not responding', tracks: [] }); } } catch (error) { console.log('Home: Discover server error:', error); // No local fallback - just show unavailable state setDiscoverPreview({ title: 'Discover Unavailable', description: 'Music discovery service is temporarily unavailable', tracks: [] }); } finally { setLoadingStates(prev => ({ ...prev, discover: false })); } }; const loadThemedMixes = async () => { try { // Get Spotify-based themed mixes from backend only let fetchUrl; if (window.location.hostname === 'music.samcloud.ca') { fetchUrl = '/api/discover/daily-mixes'; } else { const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5140'; fetchUrl = `${backendUrl}/api/discover/daily-mixes`; } console.log('Home: Fetching Spotify themed mixes from:', fetchUrl); // Add timeout for backend const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); const response = await fetch(fetchUrl, { signal: controller.signal, headers: { 'Accept': 'application/json', } }); clearTimeout(timeoutId); if (response.ok) { const data = await response.json(); console.log('Home: Received Spotify themed mixes data:', data); if (data.mixes && Array.isArray(data.mixes) && data.mixes.length > 0) { // Rank mixes client-side against the user's library to surface playlists more likely to match the user's taste try { const ranked = rankThemedMixes(data.mixes); setThemedMixes(ranked); console.log(`Home: Set ${ranked.length} Spotify themed mixes from backend (ranked)`); } catch (err) { console.warn('Ranking themed mixes failed, falling back to raw mixes', err); setThemedMixes(data.mixes); } } else { console.log('Home: No Spotify themed mixes in response - generating local Spotify-style mixes'); const localMixes = generateSpotifyStyleMixes(); setThemedMixes(localMixes); } } else { console.log('Home: Spotify themed mixes server error:', response.status, '- using local generator'); const localMixes = generateSpotifyStyleMixes(); setThemedMixes(localMixes); } } catch (error) { console.log('Home: Spotify themed mixes error:', error, '- using local generator'); const localMixes = generateSpotifyStyleMixes(); setThemedMixes(localMixes); } finally { setLoadingStates(prev => ({ ...prev, themedMixes: false })); } }; // --- Helper: Generate Spotify-styled themed mixes locally from recently played / library --- const generateSpotifyStyleMixes = (): ThemedMix[] => { // Create a few themed playlists: "Recent Vibes", "Artist Radar", "Mood Boosters" try { const recent = recentlyPlayed.slice(0, 30); const byArtist = new Map(); recent.forEach(track => { const artist = (track.Artists && track.Artists[0]) || 'Unknown'; if (!byArtist.has(artist)) byArtist.set(artist, []); byArtist.get(artist)!.push(track); }); const artistRadar: MediaItem[] = []; // pick artists with >1 recent plays and sample similar tracks from allTracks for (const [artist, tracks] of byArtist.entries()) { if (tracks.length >= 1) { const candidates = allTracks.filter(t => (t.Artists && t.Artists[0]) === artist && !recent.find(r => r.Id === t.Id)); artistRadar.push(...(candidates.slice(0, 4))); if (artistRadar.length >= 20) break; } } const moodBoosters = allTracks .filter(t => t.UserData?.PlayCount && t.UserData.PlayCount > 3) .slice(0, 20); const recentVibes = [...recent.slice(0, 20)]; const mixes: ThemedMix[] = [ { title: 'Recent Vibes', description: 'Your most recent listens', theme: 'recent', image: recentVibes[0] ? getTrackImage(recentVibes[0]) : '/default-thumbnail.png', tracks: recentVibes }, { title: 'Artist Radar', description: 'New tracks from artists you recently played', theme: 'artist-radar', image: artistRadar[0] ? getTrackImage(artistRadar[0]) : '/default-thumbnail.png', tracks: artistRadar }, { title: 'Mood Boosters', description: 'High playcount tracks you love', theme: 'mood-boosters', image: moodBoosters[0] ? getTrackImage(moodBoosters[0]) : '/default-thumbnail.png', tracks: moodBoosters } ]; return mixes; } catch (e) { console.error('generateSpotifyStyleMixes error', e); return []; } } const loadDailyMixes = async () => { try { // Check for existing saved mixes const savedMixes = instantMixService.getSavedMixes(); // Check if mixes need refresh (refresh daily or if less than 3 mixes) const now = Date.now(); const oneDayMs = 24 * 60 * 60 * 1000; const needsRefresh = savedMixes.length < 6 || // Changed from 3 to 6 savedMixes.some(mix => (now - mix.createdAt) > oneDayMs); if (savedMixes.length > 0 && !needsRefresh) { const dailyMixData = savedMixes.map((mix: any) => ({ id: mix.id, name: mix.name, description: `${mix.tracks.length} songs • Based on ${mix.seedItem?.Name || 'your music'}`, image: getTrackImage(mix.tracks[0]), tracks: mix.tracks, lastRefresh: mix.createdAt })); setDailyMixes(dailyMixData); } else { // Generate new daily mixes await generateDailyMixes(); } } catch (error) { console.error('Error loading daily mixes:', error); } finally { setLoadingStates(prev => ({ ...prev, mixes: false })); } }; const generateDailyMixes = async () => { try { if (allTracks.length < 10) return; // Generate more varied types of mixes const mixTypes = [ { type: 'mood' as const, name: 'Chill Mix', description: 'Relaxing vibes' }, { type: 'mood' as const, name: 'Energy Mix', description: 'High energy tracks' }, { type: 'similar' as const, name: 'Discovery Mix', description: 'New discoveries' }, { type: 'artist' as const, name: 'Artist Radio', description: 'Based on your favorites' }, { type: 'genre' as const, name: 'Genre Mix', description: 'Your favorite genres' }, { type: 'mood' as const, name: 'Focus Mix', description: 'Perfect for concentration' } ]; const generatedMixes: DailyMix[] = []; for (const mixConfig of mixTypes) { // Use different seed tracks for variety const seedTrack = allTracks[Math.floor(Math.random() * allTracks.length)]; const mixTracks = await instantMixService.generateInstantMix({ seedItem: seedTrack, mixType: mixConfig.type, trackCount: 30 // Increased from 25 }, allTracks); if (mixTracks.length > 0) { const mixId = instantMixService.saveMixAsVirtualPlaylist( mixConfig.name, mixTracks, mixConfig.type, seedTrack ); generatedMixes.push({ id: mixId, name: mixConfig.name, description: `${mixTracks.length} songs • ${mixConfig.description}`, image: getTrackImage(mixTracks[0]), tracks: mixTracks, lastRefresh: Date.now() }); } } setDailyMixes(generatedMixes); } catch (error) { console.error('Error generating daily mixes:', error); } }; const loadRecentlyPlayed = async () => { try { const recent = await api.fetchRecentlyPlayed(0, 12); setRecentlyPlayed(recent); } catch (error) { console.error('Error loading recently played:', error); } finally { setLoadingStates(prev => ({ ...prev, recentlyPlayed: false })); } }; const generateRecommendedTracks = () => { try { // Simple recommendation: random tracks that aren't recently played const recentIds = new Set(recentlyPlayed.map(track => track.Id)); const availableTracks = allTracks.filter(track => !recentIds.has(track.Id)); // Shuffle and take first 8 const shuffled = [...availableTracks].sort(() => Math.random() - 0.5); setRecommendedTracks(shuffled.slice(0, 8)); } catch (error) { console.error('Error generating recommendations:', error); } finally { setLoadingStates(prev => ({ ...prev, recommendations: false })); } }; const handleMixTap = async (mix: DailyMix) => { // Simple single-click to play, like themed mixes setCurrentPlaylistSimple({ playlist: mix.tracks, title: mix.name }); playTrack(0); }; const handleMixDoubleClick = async (mix: DailyMix) => { // Double-click to show queue setMixQueueTracks(mix.tracks); setShowMixQueue(mix.id); }; const handleRecommendedTrackPlay = async (track: MediaItem) => { // Generate instant mix from this track try { await playInstantMix(track, 'similar'); } catch (error) { console.error('Error playing recommended track:', error); // Fallback to just playing the track setCurrentPlaylistSimple({ playlist: [track], title: track.Name || 'Unknown Track' }); playTrack(0); } }; const handleThemedMixPlay = async (themedMix: ThemedMix) => { try { console.log('Playing themed mix:', themedMix.title, 'with', themedMix.tracks.length, 'tracks'); if (themedMix.tracks.length === 0) { console.warn('No tracks in themed mix'); return; } // Set the playlist and play the first track setCurrentPlaylistSimple({ playlist: themedMix.tracks, title: themedMix.title }); playTrack(0); } catch (error) { console.error('Error playing themed mix:', error); } }; // Long-press / context handler to preview queue (touch friendly) const handleThemedMixLongPress = (mix: ThemedMix) => { if (!mix || !mix.tracks) return; setMixQueueTracks(mix.tracks); setShowMixQueue(mix.title || mix.theme || 'themed-queue'); }; // Attempt to extract a playlist id from backend mix object const getPlaylistId = (mix: any): string | null => { return mix.playlistId || mix.spotifyId || mix.id || mix.externalId || (mix.tracks && mix.tracks[0] && (mix.tracks[0].PlaylistId || mix.tracks[0].PlaylistId)) || null; }; const handlePlaylistDownload = async (mix: any) => { try { const id = getPlaylistId(mix); if (!id) { window.alert('Unable to determine playlist id for download.'); return; } const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5140'; const downloadEndpoint = `${backendUrl}/api/spotify/download-playlist`; // POST to backend to initiate download of this Spotify playlist const resp = await fetch(downloadEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ playlistId: id }) }); if (resp.ok) { window.alert('Playlist download started. Check Downloads or server logs.'); } else { const text = await resp.text(); console.error('Playlist download failed:', text); window.alert('Playlist download failed. See console for details.'); } } catch (err) { console.error('Error initiating playlist download:', err); window.alert('Playlist download failed.'); } }; // Simple client-side ranking: prefer playlists containing your top artists const rankThemedMixes = (mixes: any[]) => { if (!allTracks || allTracks.length === 0) return mixes; const artistFreq = new Map(); for (const t of allTracks) { const artists = t.Artists || (t.AlbumArtist ? [t.AlbumArtist] : []); for (const a of artists) { if (!a) continue; const k = a.toLowerCase(); artistFreq.set(k, (artistFreq.get(k) || 0) + 1); } } const topArtists = Array.from(artistFreq.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 12) .map(x => x[0]); const scored = mixes.map(mix => { let score = 0; const tracks = mix.tracks || []; for (const tr of tracks) { const artists = tr.Artists || (tr.AlbumArtist ? [tr.AlbumArtist] : []); for (const a of artists) { if (!a) continue; const lower = a.toLowerCase(); if (topArtists.includes(lower)) score += 3; // strong match } // small bonus for track title matches to artist keywords if (tr.Name) { const name = tr.Name.toLowerCase(); for (const ta of topArtists) { if (name.includes(ta.split(' ')[0])) score += 0.25; } } } return { mix, score }; }); scored.sort((a, b) => b.score - a.score); return scored.map(s => s.mix); }; const getTrackImage = (track: MediaItem): string => { if (track.AlbumId && track.ImageTags?.Primary) { return api.getImageUrl(track, 'Primary', { width: 300, height: 300 }); } return '/default-thumbnail.png'; }; const closeMixQueue = () => { setShowMixQueue(null); setMixQueueTracks([]); setPlayingQueue(false); }; const handleDiscoverClick = () => { navigate('/discover'); }; // Only show loading if tracks are still loading if (tracksLoading) { return (

Loading your music library...

); } return (
{/* Dynamic Background */}
{backgroundImages.map((image, index) => (
))} {backgroundImages[currentImageIndex] && (
)}
{/* Main Content */}
{/* Hero Section */}

{getTimeOfDayGreeting()}

{welcomePrompt || 'Your personal music universe awaits'}

{/* Quick Access Grid */}
{/* Made for You Section */}

{loadingStates.discover ? 'Discover' : discoverPreview?.title || 'Made for You'}

{discoverPreview?.description || 'Personalized recommendations based on your listening'}

{loadingStates.discover ? (
{[...Array(6)].map((_, index) => (
))}
) : discoverPreview && discoverPreview.tracks.length > 0 ? ( ({ ...t }))} isLoading={false} type="song" title="" discoverMode={true} onDiscoverDownload={(item: any) => console.log('discover download', item)} infiniteData={{ pages: [discoverPreview.tracks.map(t => ({ ...t }))], pageParams: [0] }} /> ) : (

No personalized picks available right now

)}
{/* Themed Mixes */}

Themed Mixes

{loadingStates.themedMixes ? 'Loading...' : `${themedMixes.length} themes`}

{loadingStates.themedMixes ? 'Loading Spotify themed playlists...' : 'Curated Spotify playlists available for discovery and download' }

{loadingStates.themedMixes ? ( [...Array(4)].map((_, index) => (
)) ) : themedMixes.length > 0 ? ( themedMixes.slice(0, 6).map((mix, index) => (
handleThemedMixPlay(mix)} >
{mix.title} { (e.target as HTMLImageElement).src = '/default-thumbnail.png'; }} />
▶

{mix.title}

{mix.description}

{mix.tracks.length} tracks
)) ) : (

🎵 Spotify themed playlists temporarily unavailable

)}
{/* Recently Played */}

Recently Played

{loadingStates.recentlyPlayed ? ( [...Array(6)].map((_, index) => (
)) ) : recentlyPlayed.length > 0 ? ( recentlyPlayed.slice(0, 6).map((track) => (
handleRecommendedTrackPlay(track)} >
{track.Name}
▶

{track.Name}

{track.Artists?.join(', ') || 'Unknown Artist'}

)) ) : (

No recently played tracks

)}
{/* Car Dashboard Optimization Demo */}
{/* Remove unused sections - clean up */}
{/* Mix Queue Overlay */} {showMixQueue && (
e.stopPropagation()}>

Queue Preview

{mixQueueTracks.slice(0, 10).map((track, index) => (
{index + 1} {track.Name}
{track.Name} {track.Artists?.join(', ') || 'Unknown Artist'}
))} {mixQueueTracks.length > 10 && (
+{mixQueueTracks.length - 10} more tracks
)}
)}
); }; const getTimeOfDayGreeting = (): string => { const hour = new Date().getHours(); // Dawn (5-6am) if (hour >= 5 && hour < 7) return 'Rise and Shine'; // Morning (7-11am) if (hour >= 7 && hour < 12) return 'Good Morning, Music Lover'; // Noon (12pm) if (hour === 12) return 'Midday Melodies'; // Afternoon (1-4pm) if (hour >= 13 && hour < 17) return 'Afternoon Vibes'; // Evening (5-7pm) if (hour >= 17 && hour < 20) return 'Evening Grooves'; // Sunset/Dusk (8-9pm) if (hour >= 20 && hour < 22) return 'Sunset Sessions'; // Night (10pm-12am) if (hour >= 22 || hour === 0) return 'Late Night Beats'; // Late Night (1-4am) if (hour >= 1 && hour < 5) return 'Midnight Mood'; // Default fallback return 'Welcome Back'; }; export default HomeEnhanced;