diff --git a/web_server.py b/web_server.py index 7b746b06..c3360965 100644 --- a/web_server.py +++ b/web_server.py @@ -12993,7 +12993,7 @@ def _run_post_processing_worker(task_id, batch_id): return download_dir = docker_resolve_path(config_manager.get('soulseek.download_path', './downloads')) - transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './transfer')) + transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './Transfer')) # Try to get context for generating the correct final filename task_basename = extract_filename(task_filename) diff --git a/webui/static/script.js b/webui/static/script.js index 66358a5e..8eff3fff 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -91,6 +91,31 @@ let artistsSearchController = null; let artistCompletionController = null; // Track ongoing completion check to cancel when navigating away let similarArtistsController = null; // Track ongoing similar artists stream to cancel when navigating away +// --- Lazy Background Image Observer --- +// Watches elements with data-bg-src, applies background-image when visible, unobserves after. +const lazyBgObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const el = entry.target; + const src = el.dataset.bgSrc; + if (src) { + el.style.backgroundImage = `url('${src}')`; + delete el.dataset.bgSrc; + } + lazyBgObserver.unobserve(el); + } + }); +}, { rootMargin: '200px' }); + +/** + * Observe all elements with data-bg-src within a container for lazy background loading. + */ +function observeLazyBackgrounds(container) { + if (!container) return; + const elements = container.querySelectorAll('[data-bg-src]'); + elements.forEach(el => lazyBgObserver.observe(el)); +} + // --- MusicBrainz Integration Constants --- const MUSICBRAINZ_LOGO_URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/MusicBrainz_Logo_%282016%29.svg/500px-MusicBrainz_Logo_%282016%29.svg.png'; @@ -6340,6 +6365,11 @@ function generateMosaicBackground(coverUrls) { `; } + // Cap covers per row to 15 for GPU performance (avoids hundreds of tiles) + if (coverUrls.length > 15) { + coverUrls = coverUrls.slice(0, 15); + } + const rows = 4; let mosaicHTML = '
+
+
+
${album.artist} · ${album.total_tracks} tracks · ${album.release_date ? album.release_date.substring(0,4) : ''}
diff --git a/webui/static/style.css b/webui/static/style.css index 0da2407e..c59199f1 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -161,8 +161,6 @@ body { background: linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.03) 100%); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.1); transform: translateX(4px); box-shadow: @@ -175,8 +173,6 @@ body { rgba(29, 185, 84, 0.16) 0%, rgba(29, 185, 84, 0.12) 50%, rgba(29, 185, 84, 0.08) 100%); - backdrop-filter: blur(20px) saturate(1.6); - -webkit-backdrop-filter: blur(20px) saturate(1.6); border: 1px solid rgba(29, 185, 84, 0.25); transform: translateX(6px); box-shadow: @@ -191,8 +187,6 @@ body { rgba(29, 185, 84, 0.22) 0%, rgba(29, 185, 84, 0.18) 50%, rgba(29, 185, 84, 0.12) 100%); - backdrop-filter: blur(20px) saturate(1.8); - -webkit-backdrop-filter: blur(20px) saturate(1.8); border: 1px solid rgba(29, 185, 84, 0.35); transform: translateX(8px); box-shadow: @@ -214,8 +208,6 @@ body { background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.04) 100%); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.06); color: rgba(255, 255, 255, 0.8); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); @@ -229,8 +221,6 @@ body { background: linear-gradient(135deg, rgba(29, 185, 84, 0.25) 0%, rgba(30, 215, 96, 0.20) 100%); - backdrop-filter: blur(10px) saturate(1.6); - -webkit-backdrop-filter: blur(10px) saturate(1.6); border: 1px solid rgba(29, 185, 84, 0.3); font-weight: 700; box-shadow: @@ -996,8 +986,6 @@ body { .stat-card { background: linear-gradient(135deg, rgba(29, 185, 84, 0.08) 0%, rgba(255, 255, 255, 0.03) 100%); - backdrop-filter: blur(20px) saturate(1.5); - -webkit-backdrop-filter: blur(20px) saturate(1.5); border: 1px solid rgba(29, 185, 84, 0.15); border-radius: 16px; padding: 24px; @@ -1087,8 +1075,6 @@ body { border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 16px; padding: 20px; - backdrop-filter: blur(12px) saturate(1.1); - -webkit-backdrop-filter: blur(12px) saturate(1.1); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.05); position: relative; overflow: hidden; @@ -3507,7 +3493,6 @@ body { background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 100%); - backdrop-filter: blur(12px) saturate(1.1); border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.08); border-top: 1px solid rgba(255, 255, 255, 0.12); @@ -3540,7 +3525,6 @@ body { background: linear-gradient(135deg, rgba(30, 30, 30, 0.98) 0%, rgba(22, 22, 22, 1.0) 100%); - backdrop-filter: blur(16px) saturate(1.2); border-color: rgba(29, 185, 84, 0.3); border-top-color: rgba(29, 185, 84, 0.4); @@ -3612,7 +3596,6 @@ body { background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 100%); - backdrop-filter: blur(12px) saturate(1.1); border-radius: 24px; border: 1px solid rgba(255, 255, 255, 0.08); border-top: 1px solid rgba(255, 255, 255, 0.12); @@ -3642,7 +3625,6 @@ body { background: linear-gradient(135deg, rgba(30, 30, 30, 0.98) 0%, rgba(22, 22, 22, 1.0) 100%); - backdrop-filter: blur(16px) saturate(1.2); border-color: rgba(29, 185, 84, 0.3); border-top-color: rgba(29, 185, 84, 0.4); @@ -3746,7 +3728,6 @@ body { background: linear-gradient(135deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); - backdrop-filter: blur(8px); border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.05); padding: 12px; @@ -3767,7 +3748,6 @@ body { background: linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.02) 100%); - backdrop-filter: blur(10px); border-color: rgba(29, 185, 84, 0.2); /* Enhanced subtle depth */ @@ -4281,10 +4261,8 @@ body { /* Apple-style liquid glassmorphic foundation */ background: linear-gradient(135deg, - rgba(20, 20, 20, 0.55) 0%, - rgba(12, 12, 12, 0.65) 100%); - backdrop-filter: blur(40px) saturate(1.8); - -webkit-backdrop-filter: blur(40px) saturate(1.8); + rgba(20, 20, 20, 0.85) 0%, + rgba(12, 12, 12, 0.92) 100%); border-radius: 24px; border: 1px solid rgba(255, 255, 255, 0.08); border-top: 1px solid rgba(255, 255, 255, 0.12); @@ -4439,7 +4417,6 @@ body { background: linear-gradient(135deg, rgba(20, 20, 20, 0.95) 0%, rgba(12, 12, 12, 0.98) 100%); - backdrop-filter: blur(20px) saturate(1.2); border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.12); border-top: 1px solid rgba(255, 255, 255, 0.18); @@ -4551,7 +4528,6 @@ body { background: linear-gradient(135deg, rgba(20, 20, 20, 0.95) 0%, rgba(12, 12, 12, 0.98) 100%); - backdrop-filter: blur(20px) saturate(1.2); border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.12); border-top: 1px solid rgba(255, 255, 255, 0.18); @@ -4611,7 +4587,6 @@ body { background: linear-gradient(135deg, rgba(20, 20, 20, 0.95) 0%, rgba(12, 12, 12, 0.98) 100%); - backdrop-filter: blur(20px) saturate(1.2); border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.12); border-top: 1px solid rgba(255, 255, 255, 0.18); @@ -5064,7 +5039,6 @@ body { .sync-main-panel, .sync-sidebar { background: linear-gradient(135deg, rgba(26, 26, 26, 0.95), rgba(18, 18, 18, 0.98)); - backdrop-filter: blur(10px); border-radius: 16px; border: 1px solid rgba(255, 255, 255, 0.08); padding: 20px; @@ -5543,7 +5517,6 @@ body { border: none; border-radius: 50%; background: rgba(1, 255, 149, 0.2); - backdrop-filter: blur(10px); color: #01FF95; font-size: 24px; font-weight: 600; @@ -5654,7 +5627,6 @@ body { rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 50%, rgba(12, 12, 12, 0.99) 100%); - backdrop-filter: blur(24px) saturate(1.3); /* Enhanced borders matching modal hero */ border: 1px solid rgba(1, 255, 149, 0.3); @@ -5779,7 +5751,6 @@ body { background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 100%); - backdrop-filter: blur(20px) saturate(1.2); /* Enhanced borders matching modal */ border-radius: 20px; @@ -5818,7 +5789,6 @@ body { background: linear-gradient(135deg, rgba(30, 30, 30, 0.98) 0%, rgba(22, 22, 22, 1.0) 100%); - backdrop-filter: blur(24px) saturate(1.3); transform: translateY(-6px) scale(1.02); border-color: rgba(1, 255, 149, 0.4); @@ -5911,7 +5881,6 @@ body { background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 100%); - backdrop-filter: blur(20px) saturate(1.2); /* Enhanced borders matching modal */ border-radius: 16px; @@ -5980,7 +5949,6 @@ body { background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 100%); - backdrop-filter: blur(20px) saturate(1.2); /* Enhanced borders matching modal */ border-radius: 16px; @@ -6022,7 +5990,6 @@ body { background: linear-gradient(135deg, rgba(30, 30, 30, 0.98) 0%, rgba(22, 22, 22, 1.0) 100%); - backdrop-filter: blur(24px) saturate(1.3); transform: translateY(-4px) scale(1.02); border-color: rgba(1, 255, 149, 0.4); @@ -6300,7 +6267,6 @@ body { background: linear-gradient(135deg, rgba(20, 20, 20, 0.95) 0%, rgba(12, 12, 12, 0.98) 100%); - backdrop-filter: blur(20px) saturate(1.2); /* Enhanced borders */ border-radius: 20px; @@ -6344,7 +6310,6 @@ body { background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 100%); - backdrop-filter: blur(20px) saturate(1.2); /* Enhanced borders matching modal */ border-radius: 16px; @@ -6386,7 +6351,6 @@ body { background: linear-gradient(135deg, rgba(30, 30, 30, 0.98) 0%, rgba(22, 22, 22, 1.0) 100%); - backdrop-filter: blur(24px) saturate(1.3); transform: translateY(-4px) scale(1.02); border-color: rgba(1, 255, 149, 0.4); @@ -6623,7 +6587,6 @@ body { background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 100%); - backdrop-filter: blur(20px) saturate(1.2); /* Enhanced borders matching modal */ border-radius: 16px; @@ -6662,7 +6625,6 @@ body { background: linear-gradient(135deg, rgba(30, 30, 30, 0.98) 0%, rgba(22, 22, 22, 1.0) 100%); - backdrop-filter: blur(24px) saturate(1.3); transform: translateY(-4px) scale(1.02); border-color: rgba(138, 43, 226, 0.5); @@ -6855,7 +6817,6 @@ body { background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 100%); - backdrop-filter: blur(20px) saturate(1.2); /* Enhanced borders matching modal */ border-radius: 16px; @@ -6894,7 +6855,6 @@ body { background: linear-gradient(135deg, rgba(30, 30, 30, 0.98) 0%, rgba(22, 22, 22, 1.0) 100%); - backdrop-filter: blur(24px) saturate(1.3); transform: translateY(-6px) scale(1.02); border-color: rgba(138, 43, 226, 0.5); @@ -7229,7 +7189,6 @@ body { background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(18, 18, 18, 0.98) 100%); - backdrop-filter: blur(12px) saturate(1.1); border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.08); border-top: 1px solid rgba(255, 255, 255, 0.12); @@ -7253,7 +7212,6 @@ body { background: linear-gradient(135deg, rgba(30, 30, 30, 0.98) 0%, rgba(22, 22, 22, 1.0) 100%); - backdrop-filter: blur(16px) saturate(1.2); border-color: rgba(29, 185, 84, 0.3); border-top-color: rgba(29, 185, 84, 0.4); @@ -8316,6 +8274,7 @@ body { height: 100%; opacity: 0; animation: fadeInRow 1s ease-out forwards, infiniteScroll var(--speed, 30s) linear infinite; + will-change: transform; } .wishlist-mosaic-row.scroll-right { @@ -8400,14 +8359,14 @@ body { } /* Infinite scrolling animation */ -/* Content is duplicated 3x, animation moves by -33.333% (one set) for seamless loop */ +/* Content is duplicated 2x, animation moves by -50% (one set) for seamless loop */ @keyframes infiniteScroll { 0% { transform: translateX(0); } 100% { - transform: translateX(-33.333%); + transform: translateX(-50%); } }