From 6c3b9ddfc29317d043f7db6716748f7f96d3f76f Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:36:45 -0700 Subject: [PATCH] Optimize watchlist card CSS, backfill missing album covers Watchlist cards: removed spring-bounce transitions, staggered animations, and multi-layer hover shadows. Added CSS containment and will-change for smoother scrolling. Recent releases: backfill missing album cover art on page load via metadata source lookup. Persists found covers to database. --- database/music_database.py | 15 +++++++++++++ web_server.py | 34 ++++++++++++++++++++++++++++ webui/static/style.css | 46 ++++++-------------------------------- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/database/music_database.py b/database/music_database.py index b90bdaeb..e9386cf9 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -7235,6 +7235,21 @@ class MusicDatabase: logger.error(f"Error getting discovery recent albums: {e}") return [] + def update_discovery_recent_album_cover(self, album_id: str, cover_url: str) -> bool: + """Backfill a missing cover URL on a recent album entry.""" + try: + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute(""" + UPDATE discovery_recent_albums SET album_cover_url = ? + WHERE album_spotify_id = ? OR album_itunes_id = ? OR album_deezer_id = ? + """, (cover_url, album_id, album_id, album_id)) + conn.commit() + return cursor.rowcount > 0 + except Exception as e: + logger.debug(f"Error updating recent album cover: {e}") + return False + def clear_discovery_recent_albums(self, profile_id: int = 1) -> bool: """Clear cached recent albums for a profile""" try: diff --git a/web_server.py b/web_server.py index 4c576ed9..15036cfc 100644 --- a/web_server.py +++ b/web_server.py @@ -35785,6 +35785,40 @@ def get_discover_recent_releases(): # Get cached recent albums filtered by source (max 20) albums = database.get_discovery_recent_albums(limit=20, source=active_source, profile_id=get_current_profile_id()) + # Backfill missing cover art from metadata source + for album in albums: + if not album.get('album_cover_url'): + cover = None + album_id = album.get('album_deezer_id') or album.get('album_itunes_id') or album.get('album_spotify_id') + try: + # Try direct ID lookup first + if album_id: + fallback = _get_metadata_fallback_client() + if fallback: + album_data = fallback.get_album(str(album_id)) + if album_data: + imgs = album_data.get('images', []) + cover = album_data.get('image_url') or (imgs[0].get('url') if imgs else None) + + # Fallback: search by name + if not cover and album.get('album_name') and album.get('artist_name'): + fallback = _get_metadata_fallback_client() + if fallback: + results = fallback.search_albums(f"{album['artist_name']} {album['album_name']}", limit=1) + if results and hasattr(results[0], 'image_url') and results[0].image_url: + cover = results[0].image_url + album_id = str(results[0].id) + + if cover: + album['album_cover_url'] = cover + if album_id: + try: + database.update_discovery_recent_album_cover(album_id, cover) + except Exception: + pass + except Exception: + pass + return jsonify({"success": True, "albums": albums, "source": active_source}) except Exception as e: diff --git a/webui/static/style.css b/webui/static/style.css index f87614cc..2a258352 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -12926,53 +12926,22 @@ body.helper-mode-active #dashboard-activity-feed:hover { cursor: pointer; background: rgba(18, 18, 18, 1); border: 1px solid rgba(255, 255, 255, 0.06); - transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), - border-color 0.3s ease, - box-shadow 0.3s ease; + transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); + contain: layout style paint; + will-change: transform; } .watchlist-artist-card:hover { - transform: translateY(-6px); - border-color: rgba(var(--accent-rgb), 0.3); - box-shadow: - 0 16px 40px rgba(0, 0, 0, 0.5), - 0 0 30px rgba(var(--accent-rgb), 0.12), - 0 0 60px rgba(var(--accent-rgb), 0.05); + transform: translateY(-4px); + border-color: rgba(var(--accent-rgb), 0.25); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5); } .watchlist-artist-card:active { transform: translateY(-2px); } -/* Card entrance animation */ -@keyframes watchlistCardIn { - from { - opacity: 0; - transform: translateY(16px) scale(0.97); - } - to { - opacity: 1; - transform: translateY(0) scale(1); - } -} - -.watchlist-artist-card { - animation: watchlistCardIn 0.35s ease-out backwards; -} - -.watchlist-artist-card:nth-child(1) { animation-delay: 0.02s; } -.watchlist-artist-card:nth-child(2) { animation-delay: 0.04s; } -.watchlist-artist-card:nth-child(3) { animation-delay: 0.06s; } -.watchlist-artist-card:nth-child(4) { animation-delay: 0.08s; } -.watchlist-artist-card:nth-child(5) { animation-delay: 0.10s; } -.watchlist-artist-card:nth-child(6) { animation-delay: 0.12s; } -.watchlist-artist-card:nth-child(7) { animation-delay: 0.14s; } -.watchlist-artist-card:nth-child(8) { animation-delay: 0.16s; } -.watchlist-artist-card:nth-child(9) { animation-delay: 0.18s; } -.watchlist-artist-card:nth-child(10) { animation-delay: 0.20s; } -.watchlist-artist-card:nth-child(n+11) { animation-delay: 0.22s; } - /* Image container with gradient fade */ .watchlist-card-image { position: relative; @@ -13001,8 +12970,7 @@ body.helper-mode-active #dashboard-activity-feed:hover { height: 100%; object-fit: cover; display: block; - transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), - filter 0.4s ease; + transition: transform 0.3s ease; } .watchlist-artist-card:hover .watchlist-card-image img {