@@ -18802,6 +18811,103 @@ async function pollWatchlistScanStatus() {
}
}
+/**
+ * Update similar artists for discovery feature
+ */
+async function updateSimilarArtists() {
+ try {
+ const button = document.getElementById('update-similar-artists-btn');
+ const scanButton = document.getElementById('scan-watchlist-btn');
+
+ button.disabled = true;
+ button.textContent = 'Updating...';
+ if (scanButton) scanButton.disabled = true;
+
+ const response = await fetch('/api/watchlist/update-similar-artists', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' }
+ });
+
+ const data = await response.json();
+ if (!data.success) {
+ throw new Error(data.error || 'Failed to update similar artists');
+ }
+
+ showToast('Updating similar artists in background...', 'success');
+
+ // Poll for completion
+ pollSimilarArtistsUpdate();
+
+ } catch (error) {
+ console.error('Error updating similar artists:', error);
+ const button = document.getElementById('update-similar-artists-btn');
+ const scanButton = document.getElementById('scan-watchlist-btn');
+
+ button.disabled = false;
+ button.textContent = 'Update Similar Artists';
+ if (scanButton) scanButton.disabled = false;
+
+ showToast(`Error: ${error.message}`, 'error');
+ }
+}
+
+/**
+ * Poll similar artists update status
+ */
+async function pollSimilarArtistsUpdate() {
+ try {
+ const response = await fetch('/api/watchlist/similar-artists-status');
+ const data = await response.json();
+
+ if (data.success) {
+ const button = document.getElementById('update-similar-artists-btn');
+ const scanButton = document.getElementById('scan-watchlist-btn');
+
+ if (data.status === 'completed') {
+ if (button) {
+ button.disabled = false;
+ button.textContent = 'Update Similar Artists';
+ }
+ if (scanButton) scanButton.disabled = false;
+
+ showToast(`Updated similar artists for ${data.artists_processed || 0} artists!`, 'success');
+ return; // Stop polling
+
+ } else if (data.status === 'error') {
+ if (button) {
+ button.disabled = false;
+ button.textContent = 'Update Similar Artists';
+ }
+ if (scanButton) scanButton.disabled = false;
+
+ showToast('Error updating similar artists', 'error');
+ return; // Stop polling
+ } else if (data.status === 'running') {
+ // Update button text with progress
+ if (button && data.current_artist) {
+ button.textContent = `Updating... (${data.artists_processed || 0}/${data.total_artists || 0})`;
+ }
+ }
+ }
+
+ // Continue polling if still running
+ if (data.success && data.status === 'running') {
+ setTimeout(pollSimilarArtistsUpdate, 1000); // Poll every 1 second
+ }
+
+ } catch (error) {
+ console.error('Error polling similar artists update:', error);
+ const button = document.getElementById('update-similar-artists-btn');
+ const scanButton = document.getElementById('scan-watchlist-btn');
+
+ if (button) {
+ button.disabled = false;
+ button.textContent = 'Update Similar Artists';
+ }
+ if (scanButton) scanButton.disabled = false;
+ }
+}
+
/**
* Remove artist from watchlist via modal
*/
@@ -24098,3 +24204,361 @@ async function selectPlexLibrary() {
alert('Error selecting library. Please try again.');
}
}
+
+// ============================================
+// == DISCOVER PAGE ==
+// ============================================
+
+let discoverHeroIndex = 0;
+let discoverHeroArtists = [];
+let discoverHeroInterval = null;
+
+async function loadDiscoverPage() {
+ console.log('Loading discover page...');
+
+ // Load all sections
+ await Promise.all([
+ loadDiscoverHero(),
+ loadDiscoverRecentReleases(),
+ loadDiscoverReleaseRadar(),
+ loadDiscoverWeekly(),
+ loadMoreForYou()
+ ]);
+}
+
+async function loadDiscoverHero() {
+ try {
+ const response = await fetch('/api/discover/hero');
+ if (!response.ok) {
+ console.error('Failed to fetch discover hero');
+ return;
+ }
+
+ const data = await response.json();
+ if (!data.success || !data.artists || data.artists.length === 0) {
+ console.log('No hero artists available');
+ showDiscoverHeroEmpty();
+ return;
+ }
+
+ discoverHeroArtists = data.artists;
+ discoverHeroIndex = 0;
+
+ // Display first artist
+ displayDiscoverHeroArtist(discoverHeroArtists[0]);
+
+ // Start slideshow (change every 8 seconds)
+ if (discoverHeroInterval) {
+ clearInterval(discoverHeroInterval);
+ }
+ if (discoverHeroArtists.length > 1) {
+ discoverHeroInterval = setInterval(() => {
+ discoverHeroIndex = (discoverHeroIndex + 1) % discoverHeroArtists.length;
+ displayDiscoverHeroArtist(discoverHeroArtists[discoverHeroIndex]);
+ }, 8000);
+ }
+
+ } catch (error) {
+ console.error('Error loading discover hero:', error);
+ showDiscoverHeroEmpty();
+ }
+}
+
+function displayDiscoverHeroArtist(artist) {
+ const titleEl = document.getElementById('discover-hero-title');
+ const subtitleEl = document.getElementById('discover-hero-subtitle');
+ const imageEl = document.getElementById('discover-hero-image');
+ const bgEl = document.getElementById('discover-hero-bg');
+
+ if (titleEl) {
+ titleEl.textContent = artist.artist_name;
+ }
+
+ if (subtitleEl) {
+ const genres = artist.genres && artist.genres.length > 0
+ ? artist.genres.slice(0, 3).join(', ')
+ : 'Discover new music';
+ subtitleEl.textContent = genres;
+ }
+
+ if (imageEl && artist.image_url) {
+ imageEl.innerHTML = `

`;
+ } else if (imageEl) {
+ imageEl.innerHTML = '
🎧
';
+ }
+
+ if (bgEl && artist.image_url) {
+ bgEl.style.backgroundImage = `url('${artist.image_url}')`;
+ bgEl.style.backgroundSize = 'cover';
+ bgEl.style.backgroundPosition = 'center';
+ }
+}
+
+function showDiscoverHeroEmpty() {
+ const titleEl = document.getElementById('discover-hero-title');
+ const subtitleEl = document.getElementById('discover-hero-subtitle');
+
+ if (titleEl) titleEl.textContent = 'No Recommendations Yet';
+ if (subtitleEl) subtitleEl.textContent = 'Run a watchlist scan to generate personalized recommendations';
+}
+
+async function loadDiscoverRecentReleases() {
+ try {
+ const carousel = document.getElementById('recent-releases-carousel');
+ if (!carousel) return;
+
+ carousel.innerHTML = '
Loading recent releases...
';
+
+ const response = await fetch('/api/discover/recent-releases');
+ if (!response.ok) {
+ throw new Error('Failed to fetch recent releases');
+ }
+
+ const data = await response.json();
+ if (!data.success || !data.releases || data.releases.length === 0) {
+ carousel.innerHTML = '
';
+ return;
+ }
+
+ // Build carousel HTML
+ let html = '';
+ data.releases.forEach(release => {
+ const coverUrl = release.album_cover_url || '/static/placeholder-album.png';
+ html += `
+
+
+

+
+
+
${release.album_name}
+
${release.release_date}
+
+
+ `;
+ });
+
+ carousel.innerHTML = html;
+
+ } catch (error) {
+ console.error('Error loading recent releases:', error);
+ const carousel = document.getElementById('recent-releases-carousel');
+ if (carousel) {
+ carousel.innerHTML = '
Failed to load recent releases
';
+ }
+ }
+}
+
+async function loadDiscoverReleaseRadar() {
+ try {
+ const playlistContainer = document.getElementById('release-radar-playlist');
+ if (!playlistContainer) return;
+
+ playlistContainer.innerHTML = '
';
+
+ const response = await fetch('/api/discover/release-radar');
+ if (!response.ok) {
+ throw new Error('Failed to fetch release radar');
+ }
+
+ const data = await response.json();
+ if (!data.success || !data.tracks || data.tracks.length === 0) {
+ playlistContainer.innerHTML = '
No new releases available
';
+ return;
+ }
+
+ // Build compact playlist HTML
+ let html = '
';
+ data.tracks.forEach((track, index) => {
+ const coverUrl = track.album_cover_url || '/static/placeholder-album.png';
+ const durationMin = Math.floor(track.duration_ms / 60000);
+ const durationSec = Math.floor((track.duration_ms % 60000) / 1000);
+ const duration = `${durationMin}:${durationSec.toString().padStart(2, '0')}`;
+
+ html += `
+
+
${index + 1}
+
+

+
+
+
${track.track_name}
+
${track.artist_name}
+
+
${track.album_name}
+
${duration}
+
+ `;
+ });
+ html += '
';
+
+ playlistContainer.innerHTML = html;
+
+ } catch (error) {
+ console.error('Error loading release radar:', error);
+ const playlistContainer = document.getElementById('release-radar-playlist');
+ if (playlistContainer) {
+ playlistContainer.innerHTML = '
Failed to load release radar
';
+ }
+ }
+}
+
+async function loadDiscoverWeekly() {
+ try {
+ const playlistContainer = document.getElementById('discovery-weekly-playlist');
+ if (!playlistContainer) return;
+
+ playlistContainer.innerHTML = '
Curating your discovery playlist...
';
+
+ const response = await fetch('/api/discover/weekly');
+ if (!response.ok) {
+ throw new Error('Failed to fetch discovery weekly');
+ }
+
+ const data = await response.json();
+ if (!data.success || !data.tracks || data.tracks.length === 0) {
+ playlistContainer.innerHTML = '
';
+ return;
+ }
+
+ // Build compact playlist HTML
+ let html = '
';
+ data.tracks.forEach((track, index) => {
+ const coverUrl = track.album_cover_url || '/static/placeholder-album.png';
+ const durationMin = Math.floor(track.duration_ms / 60000);
+ const durationSec = Math.floor((track.duration_ms % 60000) / 1000);
+ const duration = `${durationMin}:${durationSec.toString().padStart(2, '0')}`;
+
+ html += `
+
+
${index + 1}
+
+

+
+
+
${track.track_name}
+
${track.artist_name}
+
+
${track.album_name}
+
${duration}
+
+ `;
+ });
+ html += '
';
+
+ playlistContainer.innerHTML = html;
+
+ } catch (error) {
+ console.error('Error loading discovery weekly:', error);
+ const playlistContainer = document.getElementById('discovery-weekly-playlist');
+ if (playlistContainer) {
+ playlistContainer.innerHTML = '
Failed to load discovery weekly
';
+ }
+ }
+}
+
+async function loadMoreForYou() {
+ try {
+ const grid = document.getElementById('more-playlists-grid');
+ if (!grid) return;
+
+ grid.innerHTML = '
';
+
+ // Fetch discovery pool tracks to create curated playlists
+ const response = await fetch('/api/discover/weekly');
+ if (!response.ok) {
+ throw new Error('Failed to fetch tracks for playlists');
+ }
+
+ const data = await response.json();
+ if (!data.success || !data.tracks || data.tracks.length === 0) {
+ grid.innerHTML = '
No playlists available yet
';
+ return;
+ }
+
+ const tracks = data.tracks;
+
+ // Create curated playlists
+ const playlists = [];
+
+ // 1. Popular Picks (by popularity score)
+ const popularTracks = [...tracks].sort((a, b) => b.popularity - a.popularity).slice(0, 20);
+ if (popularTracks.length > 0) {
+ playlists.push({
+ name: 'Popular Picks',
+ description: 'Trending tracks from similar artists',
+ track_count: popularTracks.length,
+ cover: popularTracks[0].album_cover_url
+ });
+ }
+
+ // 2. Deep Cuts (lower popularity, hidden gems)
+ const deepCuts = [...tracks].filter(t => t.popularity < 50).slice(0, 20);
+ if (deepCuts.length > 0) {
+ playlists.push({
+ name: 'Deep Cuts',
+ description: 'Hidden gems you might have missed',
+ track_count: deepCuts.length,
+ cover: deepCuts[0].album_cover_url
+ });
+ }
+
+ // 3. Fresh Finds (newest additions to pool)
+ const freshFinds = [...tracks].slice(0, 20);
+ if (freshFinds.length > 0) {
+ playlists.push({
+ name: 'Fresh Finds',
+ description: 'Recently added to your discovery pool',
+ track_count: freshFinds.length,
+ cover: freshFinds[0].album_cover_url
+ });
+ }
+
+ // 4. Artist Mix (group by artist diversity)
+ const artistMap = {};
+ tracks.forEach(track => {
+ if (!artistMap[track.artist_name]) {
+ artistMap[track.artist_name] = [];
+ }
+ if (artistMap[track.artist_name].length < 3) {
+ artistMap[track.artist_name].push(track);
+ }
+ });
+ const mixTracks = Object.values(artistMap).flat().slice(0, 25);
+ if (mixTracks.length > 0) {
+ playlists.push({
+ name: 'Artist Mix',
+ description: 'Diverse selection from multiple artists',
+ track_count: mixTracks.length,
+ cover: mixTracks[0].album_cover_url
+ });
+ }
+
+ // Build playlist grid HTML
+ let html = '';
+ playlists.forEach(playlist => {
+ const coverUrl = playlist.cover || '/static/placeholder-album.png';
+ html += `
+
+
+

+
▶
+
+
+
${playlist.name}
+
${playlist.description}
+
${playlist.track_count} tracks
+
+
+ `;
+ });
+
+ grid.innerHTML = html;
+
+ } catch (error) {
+ console.error('Error loading more for you:', error);
+ const grid = document.getElementById('more-playlists-grid');
+ if (grid) {
+ grid.innerHTML = '
';
+ }
+ }
+}
diff --git a/webui/static/style.css b/webui/static/style.css
index d7fa1d4f..c37d372a 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -16100,3 +16100,569 @@ body {
.tool-help-modal-body strong {
color: #fff;
}
+
+/* ====================================
+ Discover Page Styles
+ ==================================== */
+
+.discover-container {
+ width: 100%;
+ max-width: 1600px;
+ margin: 0 auto;
+ padding: 0;
+}
+
+/* Hero Section */
+.discover-hero {
+ position: relative;
+ width: 100%;
+ height: 500px;
+ border-radius: 12px;
+ overflow: hidden;
+ margin-bottom: 40px;
+}
+
+.discover-hero-background {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ background-size: cover;
+ background-position: center;
+ filter: blur(0px);
+}
+
+.discover-hero-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 70%, transparent 100%);
+}
+
+.discover-hero-content {
+ position: relative;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 60px;
+ gap: 40px;
+}
+
+.discover-hero-info {
+ flex: 1;
+ max-width: 600px;
+ z-index: 1;
+}
+
+.discover-hero-label {
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 2px;
+ color: #1db954;
+ margin-bottom: 12px;
+ text-transform: uppercase;
+}
+
+.discover-hero-title {
+ font-size: 56px;
+ font-weight: 800;
+ color: #fff;
+ margin: 0 0 16px 0;
+ line-height: 1.1;
+ text-shadow: 0 4px 12px rgba(0,0,0,0.4);
+}
+
+.discover-hero-subtitle {
+ font-size: 18px;
+ color: rgba(255, 255, 255, 0.85);
+ margin-bottom: 32px;
+ line-height: 1.6;
+}
+
+.discover-hero-actions {
+ display: flex;
+ gap: 16px;
+}
+
+.discover-hero-button {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 14px 32px;
+ border: none;
+ border-radius: 50px;
+ font-size: 16px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.discover-hero-button.primary {
+ background-color: #1db954;
+ color: #fff;
+}
+
+.discover-hero-button.primary:hover {
+ background-color: #1ed760;
+ transform: scale(1.05);
+}
+
+.discover-hero-button.secondary {
+ background-color: rgba(255, 255, 255, 0.1);
+ color: #fff;
+ border: 2px solid rgba(255, 255, 255, 0.3);
+}
+
+.discover-hero-button.secondary:hover {
+ background-color: rgba(255, 255, 255, 0.2);
+ border-color: rgba(255, 255, 255, 0.5);
+}
+
+.discover-hero-image {
+ flex-shrink: 0;
+ width: 320px;
+ height: 320px;
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
+}
+
+.hero-image-placeholder {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 120px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.discover-hero-image img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+/* Discover Sections */
+.discover-section {
+ margin-bottom: 50px;
+ padding: 0 20px;
+}
+
+.discover-section-header {
+ margin-bottom: 24px;
+}
+
+.discover-section-title {
+ font-size: 28px;
+ font-weight: 700;
+ color: #fff;
+ margin: 0 0 8px 0;
+}
+
+.discover-section-subtitle {
+ font-size: 14px;
+ color: #999;
+ margin: 0;
+}
+
+/* Carousel Styles */
+.discover-carousel {
+ display: flex;
+ gap: 20px;
+ overflow-x: auto;
+ padding-bottom: 20px;
+ scroll-behavior: smooth;
+}
+
+.discover-carousel::-webkit-scrollbar {
+ height: 8px;
+}
+
+.discover-carousel::-webkit-scrollbar-track {
+ background: #2a2a2a;
+ border-radius: 4px;
+}
+
+.discover-carousel::-webkit-scrollbar-thumb {
+ background: #555;
+ border-radius: 4px;
+}
+
+.discover-carousel::-webkit-scrollbar-thumb:hover {
+ background: #666;
+}
+
+/* Playlist Grid */
+.discover-playlists-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 20px;
+}
+
+/* Loading State */
+.discover-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 20px;
+ color: #999;
+}
+
+.discover-loading .loading-spinner {
+ width: 40px;
+ height: 40px;
+ border: 4px solid #333;
+ border-top-color: #1db954;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 16px;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+.discover-loading p {
+ margin: 0;
+ font-size: 14px;
+}
+
+/* Responsive adjustments */
+@media (max-width: 768px) {
+ .discover-hero {
+ height: auto;
+ min-height: 400px;
+ }
+
+ .discover-hero-content {
+ flex-direction: column;
+ padding: 40px 20px;
+ text-align: center;
+ }
+
+ .discover-hero-title {
+ font-size: 36px;
+ }
+
+ .discover-hero-image {
+ width: 240px;
+ height: 240px;
+ }
+
+ .discover-hero-actions {
+ justify-content: center;
+ }
+
+ .discover-section {
+ padding: 0 16px;
+ }
+}
+
+/* Discover Cards */
+.discover-card {
+ flex-shrink: 0;
+ width: 200px;
+ background: #1a1a1a;
+ border-radius: 8px;
+ overflow: hidden;
+ cursor: pointer;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+}
+
+.discover-card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
+}
+
+.discover-card-image {
+ width: 100%;
+ height: 200px;
+ overflow: hidden;
+}
+
+.discover-card-image img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.discover-card-info {
+ padding: 12px;
+}
+
+.discover-card-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #fff;
+ margin: 0 0 4px 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.discover-card-subtitle {
+ font-size: 12px;
+ color: #999;
+ margin: 0 0 4px 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.discover-card-meta {
+ font-size: 11px;
+ color: #666;
+ margin: 0;
+}
+
+/* Discover Empty State */
+.discover-empty {
+ text-align: center;
+ padding: 60px 20px;
+ color: #999;
+}
+
+/* Discover Playlist Tracks */
+.discover-playlist-tracks {
+ background: #1a1a1a;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.discover-playlist-track {
+ display: grid;
+ grid-template-columns: 40px 50px 1fr 2fr 80px;
+ gap: 16px;
+ align-items: center;
+ padding: 12px 16px;
+ border-bottom: 1px solid #2a2a2a;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
+.discover-playlist-track:hover {
+ background-color: #2a2a2a;
+}
+
+.playlist-track-number {
+ font-size: 14px;
+ color: #999;
+ text-align: center;
+}
+
+.playlist-track-image {
+ width: 50px;
+ height: 50px;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.playlist-track-image img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.playlist-track-info {
+ min-width: 0;
+}
+
+.playlist-track-name {
+ font-size: 14px;
+ font-weight: 500;
+ color: #fff;
+ margin-bottom: 4px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.playlist-track-artist {
+ font-size: 12px;
+ color: #999;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.playlist-track-album {
+ font-size: 12px;
+ color: #999;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.playlist-track-duration {
+ font-size: 12px;
+ color: #999;
+ text-align: right;
+}
+
+/* Compact Playlist Layout */
+.discover-playlist-container.compact {
+ max-height: 500px;
+ overflow-y: auto;
+ background: #1a1a1a;
+ border-radius: 8px;
+}
+
+.discover-playlist-tracks-compact {
+ background: #1a1a1a;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.discover-playlist-track-compact {
+ display: grid;
+ grid-template-columns: 30px 40px 1fr 1.5fr 60px;
+ gap: 12px;
+ align-items: center;
+ padding: 8px 12px;
+ border-bottom: 1px solid #2a2a2a;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
+.discover-playlist-track-compact:hover {
+ background-color: #2a2a2a;
+}
+
+.track-compact-number {
+ font-size: 13px;
+ color: #999;
+ text-align: center;
+}
+
+.track-compact-image {
+ width: 40px;
+ height: 40px;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.track-compact-image img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.track-compact-info {
+ min-width: 0;
+}
+
+.track-compact-name {
+ font-size: 13px;
+ font-weight: 500;
+ color: #fff;
+ margin-bottom: 2px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.track-compact-artist {
+ font-size: 11px;
+ color: #999;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.track-compact-album {
+ font-size: 11px;
+ color: #999;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.track-compact-duration {
+ font-size: 11px;
+ color: #999;
+ text-align: right;
+}
+
+/* Discover Playlist Cards */
+.discover-playlist-card {
+ background: #1a1a1a;
+ border-radius: 8px;
+ overflow: hidden;
+ cursor: pointer;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+}
+
+.discover-playlist-card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
+}
+
+.discover-playlist-cover {
+ position: relative;
+ width: 100%;
+ padding-bottom: 100%;
+ overflow: hidden;
+}
+
+.discover-playlist-cover img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.playlist-play-overlay {
+ position: absolute;
+ bottom: 12px;
+ right: 12px;
+ width: 48px;
+ height: 48px;
+ background-color: #1db954;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 20px;
+ color: #fff;
+ opacity: 0;
+ transform: translateY(8px);
+ transition: all 0.2s ease;
+}
+
+.discover-playlist-card:hover .playlist-play-overlay {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.discover-playlist-info {
+ padding: 16px;
+}
+
+.discover-playlist-name {
+ font-size: 16px;
+ font-weight: 600;
+ color: #fff;
+ margin: 0 0 8px 0;
+}
+
+.discover-playlist-description {
+ font-size: 13px;
+ color: #999;
+ margin: 0 0 8px 0;
+ line-height: 1.4;
+}
+
+.discover-playlist-count {
+ font-size: 12px;
+ color: #666;
+ margin: 0;
+}