+
@@ -32479,7 +32620,7 @@ async function loadGenreBrowserTabs() {
tabsHTML += `
`;
@@ -32495,11 +32636,11 @@ async function loadGenreBrowserTabs() {
${genre.track_count} tracks
-
â
Download
@@ -34795,13 +34936,7 @@ let discoverDownloads = {}; // playlistId -> { name, type, status, virtualPlayli
function addDiscoverDownload(playlistId, playlistName, playlistType, imageUrl = null) {
console.log(`đĨ [DOWNLOAD SIDEBAR] Adding discover download: ${playlistName} (${playlistId}) type: ${playlistType}, image: ${imageUrl}`);
- // Check if download sidebar exists
- const downloadSidebar = document.getElementById('discover-download-sidebar');
- if (!downloadSidebar) {
- console.warn('â ī¸ [DOWNLOAD SIDEBAR] Download sidebar element not found - user might not be on discover page');
- return;
- }
-
+ // Always register the download in state (needed for dashboard even when not on discover page)
discoverDownloads[playlistId] = {
name: playlistName,
type: playlistType,
@@ -34812,7 +34947,17 @@ function addDiscoverDownload(playlistId, playlistName, playlistType, imageUrl =
};
console.log(`đ [DOWNLOAD SIDEBAR] Active downloads:`, Object.keys(discoverDownloads));
- updateDiscoverDownloadBar();
+
+ // Update discover page sidebar if it exists (user is on discover page)
+ const downloadSidebar = document.getElementById('discover-download-sidebar');
+ if (downloadSidebar) {
+ updateDiscoverDownloadBar(); // Also saves snapshot internally
+ } else {
+ console.log('âšī¸ [DOWNLOAD SIDEBAR] Sidebar not present - skipping sidebar UI update');
+ saveDiscoverDownloadSnapshot(); // Persist state even when sidebar is absent
+ }
+
+ updateDashboardDownloads();
monitorDiscoverDownload(playlistId);
}
@@ -34840,6 +34985,7 @@ function monitorDiscoverDownload(playlistId) {
console.log(`â
[DOWNLOAD BAR] Process completed: ${discoverDownloads[playlistId].name}`);
discoverDownloads[playlistId].status = 'completed';
updateDiscoverDownloadBar();
+ updateDashboardDownloads();
clearInterval(checkInterval);
// Auto-remove completed downloads after 30 seconds
@@ -34864,6 +35010,7 @@ function monitorDiscoverDownload(playlistId) {
console.log(`â
[DOWNLOAD BAR] Sync completed: ${discoverDownloads[playlistId].name}`);
discoverDownloads[playlistId].status = 'completed';
updateDiscoverDownloadBar();
+ updateDashboardDownloads();
clearInterval(checkInterval);
// Auto-remove completed downloads after 30 seconds
@@ -34897,6 +35044,7 @@ function removeDiscoverDownload(playlistId) {
console.log(`đī¸ Removing discover download: ${playlistId}`);
delete discoverDownloads[playlistId];
updateDiscoverDownloadBar();
+ updateDashboardDownloads();
saveDiscoverDownloadSnapshot(); // Save state after removal
}
diff --git a/webui/static/style.css b/webui/static/style.css
index 65b4f966..6c86c924 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -4368,6 +4368,147 @@ body {
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
+/* Dashboard Active Downloads */
+.dashboard-downloads-group {
+ margin-bottom: 20px;
+}
+
+.dashboard-downloads-group:last-child {
+ margin-bottom: 0;
+}
+
+.dashboard-downloads-group-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 12px;
+}
+
+.dashboard-downloads-group-label {
+ font-size: 14px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: rgba(255, 255, 255, 0.5);
+}
+
+.dashboard-downloads-group-count {
+ background: rgba(29, 185, 84, 0.2);
+ color: #1db954;
+ font-size: 12px;
+ font-weight: 700;
+ padding: 2px 10px;
+ border-radius: 12px;
+ border: 1px solid rgba(29, 185, 84, 0.3);
+}
+
+.dashboard-bubble-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 24px;
+ justify-content: center;
+ align-items: center;
+ padding: 12px;
+}
+
+/* Dashboard Discover Bubble (150px circle, matching artist/search) */
+.dashboard-discover-bubble {
+ position: relative;
+ width: 150px;
+ height: 150px;
+ border-radius: 50%;
+ cursor: pointer;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ background: linear-gradient(135deg,
+ rgba(26, 26, 26, 0.95) 0%,
+ rgba(18, 18, 18, 0.98) 100%);
+ border: 2px solid rgba(255, 255, 255, 0.1);
+ box-shadow:
+ 0 4px 12px rgba(0, 0, 0, 0.4),
+ 0 0 0 1px rgba(29, 185, 84, 0.05),
+ inset 0 1px 0 rgba(255, 255, 255, 0.06);
+ overflow: hidden;
+}
+
+.dashboard-discover-bubble:hover {
+ transform: translateY(-3px) scale(1.05);
+ border-color: rgba(29, 185, 84, 0.3);
+ box-shadow:
+ 0 8px 20px rgba(0, 0, 0, 0.5),
+ 0 0 0 1px rgba(29, 185, 84, 0.2),
+ 0 0 15px rgba(29, 185, 84, 0.1),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+}
+
+.dashboard-discover-bubble.completed {
+ border-color: rgba(34, 197, 94, 0.4);
+}
+
+.dashboard-discover-bubble.completed:hover {
+ border-color: rgba(34, 197, 94, 0.6);
+ box-shadow:
+ 0 8px 20px rgba(0, 0, 0, 0.5),
+ 0 0 0 1px rgba(34, 197, 94, 0.3),
+ 0 0 15px rgba(34, 197, 94, 0.15),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+}
+
+.dashboard-discover-bubble-image {
+ position: absolute;
+ inset: 0;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ border-radius: 50%;
+}
+
+.dashboard-discover-bubble-overlay {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(135deg,
+ rgba(0, 0, 0, 0.3) 0%,
+ rgba(0, 0, 0, 0.6) 100%);
+ border-radius: 50%;
+}
+
+.dashboard-discover-bubble-content {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 8px;
+ z-index: 1;
+}
+
+.dashboard-discover-bubble-name {
+ font-size: 12px;
+ font-weight: 700;
+ color: #ffffff;
+ text-align: center;
+ line-height: 1.2;
+ max-width: 120px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
+}
+
+.dashboard-discover-bubble-status {
+ font-size: 10px;
+ font-weight: 600;
+ color: rgba(29, 185, 84, 0.9);
+ margin-top: 4px;
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
+}
+
+.dashboard-discover-bubble.completed .dashboard-discover-bubble-status {
+ color: rgba(34, 197, 94, 1);
+}
+
/* Header Styling */
.dashboard-header {
display: flex;
@@ -13251,7 +13392,8 @@ body {
#artist-hero-section #artist-detail-image {
width: 100%;
- height: 100%;
+ height: auto;
+ max-width: 50vw;
}
.artist-detail-image {
@@ -22756,8 +22898,13 @@ body {
}
@keyframes hydrabase-spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
}
/* Tooltip */