From ee266920a91c612cd6e687184e4e146061c9dba8 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Mon, 17 Nov 2025 08:49:49 -0800 Subject: [PATCH] discover progress --- core/personalized_playlists.py | 26 ++++++- webui/index.html | 11 --- webui/static/script.js | 99 +++--------------------- webui/static/style.css | 137 +++++---------------------------- 4 files changed, 50 insertions(+), 223 deletions(-) diff --git a/core/personalized_playlists.py b/core/personalized_playlists.py index 5f223aad..b86c99f8 100644 --- a/core/personalized_playlists.py +++ b/core/personalized_playlists.py @@ -111,11 +111,30 @@ class PersonalizedPlaylistsService: logger.warning(f"No tracks found for {decade}s") return [] - # Apply diversity constraint: max 3 tracks per album, max 4 per artist # Shuffle first for randomness import random random.shuffle(all_tracks) + # Count unique artists to determine diversity level + unique_artists = len(set(track['artist_name'] for track in all_tracks)) + + # Adaptive diversity limits based on artist variety + if unique_artists >= 20: + # Good variety - apply diversity constraints + max_per_album = 3 + max_per_artist = 5 + elif unique_artists >= 10: + # Moderate variety - more lenient + max_per_album = 4 + max_per_artist = 8 + else: + # Low variety - very lenient to hit 50 tracks + max_per_album = 5 + max_per_artist = 12 + + logger.info(f"{decade}s has {unique_artists} unique artists - using limits: {max_per_album} per album, {max_per_artist} per artist") + + # Apply diversity constraints tracks_by_album = {} tracks_by_artist = {} diverse_tracks = [] @@ -128,8 +147,7 @@ class PersonalizedPlaylistsService: album_count = tracks_by_album.get(album, 0) artist_count = tracks_by_artist.get(artist, 0) - # Apply more lenient limits: max 3 per album, max 4 per artist - if album_count < 3 and artist_count < 4: + if album_count < max_per_album and artist_count < max_per_artist: diverse_tracks.append(track) tracks_by_album[album] = album_count + 1 tracks_by_artist[artist] = artist_count + 1 @@ -137,7 +155,7 @@ class PersonalizedPlaylistsService: if len(diverse_tracks) >= limit: break - logger.info(f"Found {len(diverse_tracks)} tracks from {decade}s in discovery pool (with diversity filtering)") + logger.info(f"Found {len(diverse_tracks)} tracks from {decade}s in discovery pool (adaptive diversity)") return diverse_tracks[:limit] except Exception as e: diff --git a/webui/index.html b/webui/index.html index 4f191ca2..8c77a3be 100644 --- a/webui/index.html +++ b/webui/index.html @@ -2071,17 +2071,6 @@ - - -
-
-

⚡ Quick Picks

-

Jump into curated experiences

-
-
- -
-
diff --git a/webui/static/script.js b/webui/static/script.js index e2336b6e..4f7502fe 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -24742,8 +24742,7 @@ async function loadDiscoverPage() { loadPersonalizedForgottenFavorites(), // NEW: Forgotten favorites loadDiscoveryShuffle(), // NEW: Discovery Shuffle loadFamiliarFavorites(), // NEW: Familiar Favorites - loadDecadeBrowser(), // Decade browser - loadQuickPicks() // Quick picks action cards + loadDecadeBrowser() // Decade browser ]); // Check for active syncs after page load @@ -25275,17 +25274,20 @@ async function loadDecadeBrowser() { return; } - // Build decade cards from available decades + // Build decade cards matching Recent Releases style let html = ''; data.decades.forEach(decade => { const icon = getDecadeIcon(decade.year); const label = `${decade.year}s`; html += ` -
-
-
${icon}
-
${label}
-
${decade.track_count} tracks
+
+
+
${icon}
+
+
+

${label}

+

${decade.track_count} tracks

+

Classics

`; @@ -25350,87 +25352,6 @@ async function openDecadePlaylist(decade) { } } -// =============================== -// QUICK PICKS -// =============================== - -function loadQuickPicks() { - try { - const grid = document.getElementById('quick-picks-grid'); - if (!grid) return; - - const quickPicks = [ - { - icon: '🎲', - title: 'Surprise Me', - description: 'Shuffle your discovery pool', - gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - action: 'scrollToSection("personalized-discovery-shuffle")' - }, - { - icon: '🔥', - title: 'Trending Now', - description: 'High popularity discoveries', - gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', - action: 'scrollToSection("personalized-popular-picks")' - }, - { - icon: '💎', - title: 'Deep Cuts', - description: 'Underground hidden gems', - gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', - action: 'scrollToSection("personalized-hidden-gems")' - }, - { - icon: '⏰', - title: 'Time Machine', - description: 'Browse by decade', - gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', - action: 'scrollToSection("decade-browser-carousel")' - } - ]; - - let html = ''; - quickPicks.forEach(pick => { - html += ` -
-
${pick.icon}
-

${pick.title}

-

${pick.description}

-
- `; - }); - - grid.innerHTML = html; - - } catch (error) { - console.error('Error loading quick picks:', error); - } -} - -function scrollToSection(sectionId) { - const section = document.getElementById(sectionId); - if (!section) { - console.warn(`Section not found: ${sectionId}`); - return; - } - - // Show section if it's hidden - const parentSection = section.closest('.discover-section'); - if (parentSection) { - const currentDisplay = window.getComputedStyle(parentSection).display; - if (currentDisplay === 'none') { - parentSection.style.display = 'block'; - console.log(`Showing hidden section: ${sectionId}`); - } - } - - // Wait a tick for layout, then scroll - setTimeout(() => { - section.scrollIntoView({ behavior: 'smooth', block: 'start' }); - }, 100); -} - // =============================== // SEASONAL DISCOVERY // =============================== diff --git a/webui/static/style.css b/webui/static/style.css index 3d21e3ca..57e089a1 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -17296,147 +17296,46 @@ body { } /* ======================= - QUICK PICKS SECTION + DECADE BROWSER SECTION - Modern Style matching Recent Releases ======================= */ -.quick-picks-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 20px; - margin-top: 16px; +.decade-card-modern { + /* Inherits from .discover-card */ } -.quick-pick-card { - position: relative; - padding: 32px 24px; - border-radius: 16px; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - overflow: hidden; -} - -.quick-pick-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: inherit; - opacity: 0.9; - transition: opacity 0.3s ease; -} - -.quick-pick-card:hover::before { - opacity: 1; -} - -.quick-pick-card:hover { - transform: translateY(-4px); - box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); -} - -.quick-pick-icon { - position: relative; - font-size: 48px; - margin-bottom: 16px; - z-index: 1; -} - -.quick-pick-title { - position: relative; - font-size: 20px; - font-weight: 700; - color: #fff; - margin: 0 0 8px 0; - z-index: 1; -} - -.quick-pick-description { - position: relative; - font-size: 14px; - color: rgba(255, 255, 255, 0.8); - margin: 0; - z-index: 1; -} - -/* ======================= - DECADE BROWSER SECTION - ======================= */ - -.decade-card { - position: relative; - width: 100%; - aspect-ratio: 1; - border-radius: 16px; - overflow: hidden; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +.decade-card-image { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); -} - -.decade-card:hover { - transform: translateY(-8px) scale(1.02); - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6); -} - -.decade-card-content { - position: relative; - width: 100%; - height: 100%; display: flex; - flex-direction: column; align-items: center; justify-content: center; - gap: 16px; - padding: 24px; - background: linear-gradient(135deg, - rgba(102, 126, 234, 0.15) 0%, - rgba(118, 75, 162, 0.15) 100%); + position: relative; + overflow: hidden; } -.decade-card::before { +.decade-card-image::before { content: ''; position: absolute; inset: 0; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - opacity: 0; + background: linear-gradient(135deg, + rgba(102, 126, 234, 0.1) 0%, + rgba(118, 75, 162, 0.1) 100%); + opacity: 1; transition: opacity 0.3s ease; } -.decade-card:hover::before { - opacity: 0.2; +.decade-card-modern:hover .decade-card-image::before { + opacity: 0.3; } -.decade-icon { - position: relative; - font-size: 80px; +.decade-icon-large { + font-size: 96px; line-height: 1; + position: relative; z-index: 1; transition: transform 0.3s ease; } -.decade-card:hover .decade-icon { - transform: scale(1.1); -} - -.decade-year { - position: relative; - font-size: 36px; - font-weight: 900; - color: #fff; - text-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); - letter-spacing: -1px; - z-index: 1; -} - -.decade-title { - position: relative; - font-size: 16px; - font-weight: 600; - color: rgba(255, 255, 255, 0.7); - text-transform: uppercase; - letter-spacing: 2px; - z-index: 1; +.decade-card-modern:hover .decade-icon-large { + transform: scale(1.1) rotate(5deg); }