From 34e0503fadd7cecb78b7fd9da1e5e40ce601d589 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Tue, 9 Jun 2026 20:57:27 -0700 Subject: [PATCH] Watchlist scan deck v2: portrait-anchored hero, zero layout shift (#831 polish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boulder's screenshots: the v1 deck shifted around depending on what data had arrived (the album row vanished entirely without art, leaving floating "Processing…/Processing…" text), the images were small, and the feed header floated in empty space. Redesigned in the artist-detail-page language: - Big 148px square portrait (rounded, shadowed) anchors the left side, with the current album stamped as a 62px overlay badge in its corner — when art is missing, both keep their slot and show a glyph placeholder instead of collapsing, so the deck NEVER changes shape mid-scan. - 24px artist name + uppercase accent phase line + a fixed-height "now checking" block (accent left rule) for album + track, with stable placeholders ("Looking for new releases…" / "—") instead of doubled "Processing…" text. - The additions feed is an inset fixed-height panel (artist-page sidebar style): same size whether 0 or 10 tracks, empty state centered. - JS: hide the artist photo when the CURRENT artist has none (previously the prior artist's photo lingered), cleaner placeholder copy. --- webui/index.html | 18 ++--- webui/static/api-monitor.js | 23 ++++-- webui/static/style.css | 154 +++++++++++++++++++++++++++--------- 3 files changed, 139 insertions(+), 56 deletions(-) diff --git a/webui/index.html b/webui/index.html index 2ebf113d..d5136152 100644 --- a/webui/index.html +++ b/webui/index.html @@ -6994,17 +6994,17 @@
-
-
- -
-
Waiting…
-
starting…
+
+
+ +
+
-
- -
+
+
Waiting…
+
starting…
+
diff --git a/webui/static/api-monitor.js b/webui/static/api-monitor.js index aadbb8ab..f508761f 100644 --- a/webui/static/api-monitor.js +++ b/webui/static/api-monitor.js @@ -3276,15 +3276,21 @@ function handleWatchlistScanData(data) { if (liveActivity && data.status === 'scanning') { liveActivity.style.display = liveActivity.classList.contains('wl-scan-deck') ? 'block' : 'flex'; - // Update artist image and name + // Update artist image and name (hide the img when THIS artist has no + // photo, so the previous artist's portrait doesn't linger and the + // glyph placeholder shows instead) const artistImg = document.getElementById('watchlist-artist-img'); const artistName = document.getElementById('watchlist-artist-name'); - if (artistImg && data.current_artist_image_url) { - artistImg.src = data.current_artist_image_url; - artistImg.style.display = 'block'; + if (artistImg) { + if (data.current_artist_image_url) { + artistImg.src = data.current_artist_image_url; + artistImg.style.display = 'block'; + } else { + artistImg.style.display = 'none'; + } } if (artistName) { - artistName.textContent = data.current_artist_name || 'Processing...'; + artistName.textContent = data.current_artist_name || 'Starting…'; } // Update album image and name @@ -3297,13 +3303,14 @@ function handleWatchlistScanData(data) { albumImg.style.display = 'none'; } if (albumName) { - albumName.textContent = data.current_album || (data.current_phase === 'fetching_discography' ? 'Fetching releases...' : 'Processing...'); + albumName.textContent = data.current_album + || (data.current_phase === 'fetching_discography' ? 'Fetching releases…' : 'Looking for new releases…'); } - // Update current track + // Update current track ('' keeps the slot without echoing the album line) const trackName = document.getElementById('watchlist-track-name'); if (trackName) { - trackName.textContent = data.current_track_name || (data.current_phase === 'fetching_discography' ? 'Fetching releases...' : 'Processing...'); + trackName.textContent = data.current_track_name || '—'; } // #831 round 2: scan-deck extras — artist progress bar, live counters, diff --git a/webui/static/style.css b/webui/static/style.css index cbe11a29..2e47c912 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -20077,57 +20077,100 @@ body.helper-mode-active #dashboard-activity-feed:hover { .wl-scan-deck-body { display: grid; - grid-template-columns: minmax(260px, 1fr) minmax(280px, 1.2fr); - gap: 20px; + grid-template-columns: minmax(320px, 1.1fr) minmax(300px, 1fr); + gap: 24px; + align-items: stretch; } @media (max-width: 760px) { .wl-scan-deck-body { grid-template-columns: 1fr; } } -.wl-scan-stage { - display: flex; - flex-direction: column; - gap: 14px; - min-width: 0; -} - -.wl-scan-stage-row { +/* Hero — big square portrait (artist-page language) with the current album + stamped as an overlay badge, so the layout NEVER shifts when album art is + missing: the badge keeps its slot and shows a glyph placeholder instead. */ +.wl-scan-hero { display: flex; align-items: center; - gap: 12px; + gap: 20px; min-width: 0; } -.wl-scan-artist-img { - width: 64px; - height: 64px; - border-radius: 50%; - border: 2px solid rgb(var(--accent-rgb)); - box-shadow: 0 0 18px rgba(var(--accent-rgb), 0.3); - object-fit: cover; - background: #1a1a1a; +.wl-scan-portrait { + position: relative; + width: 148px; + height: 148px; + border-radius: 16px; + background: #181818; + box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(var(--accent-rgb), 0.25); flex-shrink: 0; } -.wl-scan-album-img { - width: 64px; - height: 64px; - border-radius: 8px; - border: 1px solid rgba(255, 255, 255, 0.15); - object-fit: cover; - background: #1a1a1a; - flex-shrink: 0; - transition: opacity 0.3s ease; +/* glyph placeholder behind the (possibly hidden) artist image */ +.wl-scan-portrait::before { + content: '♪'; + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 42px; + color: rgba(255, 255, 255, 0.12); } -.wl-scan-stage-text { +.wl-scan-portrait-img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border-radius: 16px; + object-fit: cover; +} + +.wl-scan-album-thumb { + position: absolute; + right: -12px; + bottom: -12px; + width: 62px; + height: 62px; + border-radius: 10px; + background: #181818; + border: 3px solid #121212; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.55); + overflow: hidden; +} + +.wl-scan-album-thumb::before { + content: '♪'; + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + color: rgba(255, 255, 255, 0.15); +} + +.wl-scan-album-thumb img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.wl-scan-hero-text { min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; + gap: 4px; } .wl-scan-artist-name { - font-size: 16px; - font-weight: 700; + font-size: 24px; + font-weight: 800; + letter-spacing: -0.3px; color: #fff; overflow: hidden; text-overflow: ellipsis; @@ -20136,14 +20179,27 @@ body.helper-mode-active #dashboard-activity-feed:hover { .wl-scan-phase { font-size: 12px; - color: rgba(var(--accent-light-rgb), 0.85); - margin-top: 2px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.6px; + color: rgba(var(--accent-light-rgb), 0.9); +} + +/* Fixed-height "now checking" block — placeholders keep the slot when the + scan hasn't reached an album/track yet, so nothing jumps. */ +.wl-scan-now { + margin-top: 8px; + padding: 8px 12px; + border-left: 2px solid rgba(var(--accent-rgb), 0.5); + background: rgba(255, 255, 255, 0.03); + border-radius: 0 8px 8px 0; + min-height: 44px; } .wl-scan-album-name { - font-size: 13px; + font-size: 14px; font-weight: 600; - color: rgba(255, 255, 255, 0.85); + color: rgba(255, 255, 255, 0.9); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -20158,27 +20214,47 @@ body.helper-mode-active #dashboard-activity-feed:hover { white-space: nowrap; } +/* Feed — inset panel (artist-page sidebar language) with a FIXED height so + the deck is the same size whether 0 or 10 tracks have been added. */ .wl-scan-feed { min-width: 0; + display: flex; + flex-direction: column; + height: 172px; + padding: 12px 14px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.07); + border-radius: 12px; } .wl-scan-feed-label { font-size: 11px; - font-weight: 600; + font-weight: 700; text-transform: uppercase; - letter-spacing: 0.5px; + letter-spacing: 0.6px; color: #6fd99a; - margin-bottom: 6px; + margin-bottom: 8px; + flex-shrink: 0; } .wl-scan-feed-list { - max-height: 176px; + flex: 1; + min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 5px; } +.wl-scan-feed-list .watchlist-live-addition-empty { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: rgba(255, 255, 255, 0.3); + font-size: 12px; +} + .wl-scan-feed-list .watchlist-live-addition-item { padding: 5px 8px; border-radius: 6px;