diff --git a/web_server.py b/web_server.py
index 64568752..0cb5fe08 100644
--- a/web_server.py
+++ b/web_server.py
@@ -21527,7 +21527,14 @@ def get_watchlist_artists():
"created_at": artist.created_at.isoformat() if artist.created_at else None,
"updated_at": artist.updated_at.isoformat() if artist.updated_at else None,
"image_url": artist.image_url, # Cached during watchlist scans
- "itunes_artist_id": artist.itunes_artist_id # For iTunes-only artists
+ "itunes_artist_id": artist.itunes_artist_id, # For iTunes-only artists
+ "include_albums": artist.include_albums,
+ "include_eps": artist.include_eps,
+ "include_singles": artist.include_singles,
+ "include_live": artist.include_live,
+ "include_remixes": artist.include_remixes,
+ "include_acoustic": artist.include_acoustic,
+ "include_compilations": artist.include_compilations,
})
return jsonify({"success": True, "artists": artists_data})
@@ -22362,7 +22369,8 @@ def watchlist_artist_config(artist_id):
cursor.execute("""
SELECT include_albums, include_eps, include_singles,
include_live, include_remixes, include_acoustic, include_compilations,
- artist_name, image_url, spotify_artist_id, itunes_artist_id
+ artist_name, image_url, spotify_artist_id, itunes_artist_id,
+ last_scan_timestamp, date_added
FROM watchlist_artists
WHERE spotify_artist_id = ? OR itunes_artist_id = ?
""", (artist_id, artist_id))
@@ -22405,6 +22413,53 @@ def watchlist_artist_config(artist_id):
'genres': []
}
+ # Enrich with library artist data (banner, bio, style, mood, label)
+ try:
+ conn2 = sqlite3.connect(str(database.database_path))
+ cur2 = conn2.cursor()
+ cur2.execute("""
+ SELECT banner_url, summary, style, mood, label, genres
+ FROM artists
+ WHERE spotify_artist_id = ? OR itunes_artist_id = ?
+ LIMIT 1
+ """, (artist_id, artist_id))
+ lib_row = cur2.fetchone()
+ if lib_row:
+ artist_info['banner_url'] = lib_row[0]
+ artist_info['summary'] = lib_row[1]
+ artist_info['style'] = lib_row[2]
+ artist_info['mood'] = lib_row[3]
+ artist_info['label'] = lib_row[4]
+ # Backfill genres from library if Spotify didn't provide any
+ if not artist_info.get('genres') and lib_row[5]:
+ try:
+ artist_info['genres'] = json.loads(lib_row[5])
+ except (json.JSONDecodeError, TypeError):
+ pass
+
+ # Get recent releases for this watchlist artist
+ cur2.execute("""
+ SELECT rr.album_name, rr.release_date, rr.album_cover_url, rr.track_count
+ FROM recent_releases rr
+ JOIN watchlist_artists wa ON rr.watchlist_artist_id = wa.id
+ WHERE wa.spotify_artist_id = ? OR wa.itunes_artist_id = ?
+ ORDER BY rr.release_date DESC
+ LIMIT 6
+ """, (artist_id, artist_id))
+ releases = [
+ {
+ 'album_name': r[0],
+ 'release_date': r[1],
+ 'album_cover_url': r[2],
+ 'track_count': r[3],
+ }
+ for r in cur2.fetchall()
+ ]
+ conn2.close()
+ except Exception as e:
+ print(f"Warning: Could not enrich artist from library: {e}")
+ releases = []
+
config = {
'include_albums': bool(result[0]), # Convert INTEGER to boolean
'include_eps': bool(result[1]),
@@ -22412,13 +22467,16 @@ def watchlist_artist_config(artist_id):
'include_live': bool(result[3]),
'include_remixes': bool(result[4]),
'include_acoustic': bool(result[5]),
- 'include_compilations': bool(result[6])
+ 'include_compilations': bool(result[6]),
+ 'last_scan_timestamp': result[11],
+ 'date_added': result[12],
}
return jsonify({
"success": True,
"config": config,
- "artist": artist_info
+ "artist": artist_info,
+ "recent_releases": releases
})
else: # POST
diff --git a/webui/static/mobile.css b/webui/static/mobile.css
index 8116056b..1fde95fe 100644
--- a/webui/static/mobile.css
+++ b/webui/static/mobile.css
@@ -1486,12 +1486,34 @@
font-size: 10px;
}
- /* Show checkbox and remove always on mobile (no hover) */
+ /* Show checkbox, remove, and gear always on mobile (no hover) */
.watchlist-card-checkbox,
- .watchlist-artist-card .watchlist-card-remove {
+ .watchlist-artist-card .watchlist-card-remove,
+ .watchlist-artist-card .watchlist-card-gear {
opacity: 1;
}
+ .watchlist-detail-hero {
+ flex-direction: column;
+ text-align: center;
+ }
+
+ .watchlist-detail-hero-stats {
+ justify-content: center;
+ }
+
+ .watchlist-detail-hero-genres {
+ justify-content: center;
+ }
+
+ .watchlist-detail-actions {
+ flex-direction: column;
+ }
+
+ .watchlist-detail-actions button {
+ width: 100%;
+ }
+
.watchlist-search-container {
padding: 0 16px;
}
@@ -1904,6 +1926,32 @@
padding: 8px 10px;
}
+ .watchlist-pill {
+ font-size: 8px;
+ padding: 1px 4px;
+ }
+
+ .watchlist-card-pills {
+ padding: 0 10px 6px;
+ }
+
+ .watchlist-detail-content {
+ padding: 20px 16px;
+ }
+
+ .watchlist-detail-hero img {
+ width: 80px;
+ height: 80px;
+ }
+
+ .watchlist-detail-hero-name {
+ font-size: 22px;
+ }
+
+ .watchlist-detail-releases {
+ grid-template-columns: 1fr;
+ }
+
/* Download modal - even more compact */
.download-missing-modal-hero {
padding: 10px 12px;
diff --git a/webui/static/script.js b/webui/static/script.js
index f424f8e6..da4beebd 100644
--- a/webui/static/script.js
+++ b/webui/static/script.js
@@ -26757,25 +26757,25 @@ async function showWatchlistModal() {
-
+
-
-
![Artist]()
-
Waiting...
+
+
![Artist]()
+
Waiting...
-
-
![Album]()
-
Waiting...
+
+
![Album]()
+
Waiting...
-
-
Current Track:
-
Waiting...
+
+
Current Track:
+
Waiting...
-
✨ Recently Added:
+
✨ Recently Added:
@@ -26845,7 +26845,16 @@ async function showWatchlistModal() {
- ${artistsData.artists.map(artist => `
+ ${artistsData.artists.map(artist => {
+ const pills = [];
+ if (artist.include_albums) pills.push('
Albums');
+ if (artist.include_eps) pills.push('
EPs');
+ if (artist.include_singles) pills.push('
Singles');
+ if (artist.include_live) pills.push('
Live');
+ if (artist.include_remixes) pills.push('
Remixes');
+ if (artist.include_acoustic) pills.push('
Acoustic');
+ if (artist.include_compilations) pills.push('
Compilations');
+ return `
@@ -26858,11 +26867,13 @@ async function showWatchlistModal() {
-
+ title="Artist settings">
+
+
${artist.image_url ? `
@@ -26877,29 +26888,29 @@ async function showWatchlistModal() {
${escapeHtml(artist.artist_name)}
- Added ${new Date(artist.date_added).toLocaleDateString()}
+ ${formatRelativeScanTime(artist.last_scan_timestamp)}
+ ${pills.length > 0 ? `
${pills.join('')}
` : ''}
- `).join('')}
+ `}).join('')}
`;
- // Add event listeners for remove buttons
- modal.querySelectorAll('.watchlist-card-remove').forEach(button => {
+ // Add event listeners for gear buttons
+ modal.querySelectorAll('.watchlist-card-gear').forEach(button => {
button.addEventListener('click', () => {
const artistId = button.getAttribute('data-artist-id');
const artistName = button.getAttribute('data-artist-name');
- removeFromWatchlistModal(artistId, artistName);
+ openWatchlistArtistConfigModal(artistId, artistName);
});
});
- // Add click handlers to artist cards (except for remove button or checkbox)
+ // Add click handlers to artist cards (except for gear button or checkbox)
modal.querySelectorAll('.watchlist-artist-card').forEach(item => {
item.addEventListener('click', (e) => {
- // Don't trigger if clicking the remove button or checkbox
- if (e.target.closest('.watchlist-card-remove') || e.target.closest('.watchlist-card-checkbox')) {
+ if (e.target.closest('.watchlist-card-gear') || e.target.closest('.watchlist-card-checkbox')) {
return;
}
@@ -26907,7 +26918,7 @@ async function showWatchlistModal() {
const artistName = item.querySelector('.watchlist-card-name').textContent;
console.log(`🎵 Artist card clicked: ${artistName} (${artistId})`);
- openWatchlistArtistConfigModal(artistId, artistName);
+ openWatchlistArtistDetailView(artistId, artistName);
});
});
@@ -27113,6 +27124,177 @@ function closeWatchlistArtistConfigModal() {
}
}
+/**
+ * Open watchlist artist detail view (slides in from right)
+ */
+async function openWatchlistArtistDetailView(artistId, artistName) {
+ try {
+ const response = await fetch(`/api/watchlist/artist/${artistId}/config`);
+ const data = await response.json();
+
+ if (!data.success) {
+ showToast(`Error loading artist info: ${data.error}`, 'error');
+ return;
+ }
+
+ const { config, artist, recent_releases } = data;
+
+ // Remove existing overlay if any
+ const existing = document.querySelector('.watchlist-artist-detail-overlay');
+ if (existing) existing.remove();
+
+ const overlay = document.createElement('div');
+ overlay.className = 'watchlist-artist-detail-overlay';
+
+ // Build pills
+ const pills = [];
+ if (config.include_albums) pills.push('
Albums');
+ if (config.include_eps) pills.push('
EPs');
+ if (config.include_singles) pills.push('
Singles');
+ if (config.include_live) pills.push('
Live');
+ if (config.include_remixes) pills.push('
Remixes');
+ if (config.include_acoustic) pills.push('
Acoustic');
+ if (config.include_compilations) pills.push('
Compilations');
+
+ // Build scan info
+ const scanTimeText = config.last_scan_timestamp ? formatRelativeScanTime(config.last_scan_timestamp) : 'Never scanned';
+ const dateAddedText = config.date_added ? `Added ${new Date(config.date_added).toLocaleDateString()}` : '';
+
+ // Build metadata tags (style, mood, label)
+ const metaTags = [];
+ if (artist.style) metaTags.push(`
${escapeHtml(artist.style)}`);
+ if (artist.mood) metaTags.push(`
${escapeHtml(artist.mood)}`);
+ if (artist.label) metaTags.push(`
${escapeHtml(artist.label)}`);
+
+ overlay.innerHTML = `
+ ${artist.banner_url ? `
+
+

+
+
+ ` : ''}
+
+
+
+
+
+ ${artist.image_url ? `

` : ''}
+
+
${escapeHtml(artist.name)}
+ ${artist.followers || artist.popularity ? `
+
+ ${artist.followers ? `
+
+ ${formatNumber(artist.followers)}
+ Followers
+
` : ''}
+ ${artist.popularity ? `
+
+ ${artist.popularity}/100
+ Popularity
+
` : ''}
+
+ ` : ''}
+ ${artist.genres && artist.genres.length > 0 ? `
+
+ ${artist.genres.map(g => `${escapeHtml(g)}`).join('')}
+
+ ` : ''}
+
+
+
+ ${artist.summary ? `
+
+
About
+
${escapeHtml(artist.summary)}
+
+ ` : ''}
+
+ ${metaTags.length > 0 ? `
+
+
Info
+
${metaTags.join('')}
+
+ ` : ''}
+
+ ${recent_releases && recent_releases.length > 0 ? `
+
+
Recent Releases
+
+ ${recent_releases.map(r => `
+
+ ${r.album_cover_url ? `

` : ''}
+
+ ${escapeHtml(r.album_name)}
+ ${r.release_date}${r.track_count ? ` · ${r.track_count} tracks` : ''}
+
+
+ `).join('')}
+
+
+ ` : ''}
+
+
+
Watchlist
+
+ ${scanTimeText}
+ ${dateAddedText ? `·${dateAddedText}` : ''}
+
+
+ ${pills.length > 0 ? pills.join('') : 'No release types enabled'}
+
+
+
+
+
+
+
+
+ `;
+
+ // Wire up event listeners (avoids inline onclick escaping issues)
+ overlay.querySelector('.watchlist-detail-back-btn').addEventListener('click', () => {
+ closeWatchlistArtistDetailView();
+ });
+
+ overlay.querySelector('.watchlist-detail-settings-action').addEventListener('click', () => {
+ // Remove overlay immediately so it doesn't block the config modal
+ const detailOverlay = document.querySelector('.watchlist-artist-detail-overlay');
+ if (detailOverlay) detailOverlay.remove();
+ openWatchlistArtistConfigModal(artistId, artistName);
+ });
+
+ overlay.querySelector('.watchlist-detail-remove-action').addEventListener('click', () => {
+ removeFromWatchlistModal(artistId, artistName);
+ });
+
+ // Append to the modal container
+ const container = document.querySelector('.watchlist-fullscreen');
+ if (container) {
+ container.appendChild(overlay);
+ // Trigger slide-in animation
+ requestAnimationFrame(() => overlay.classList.add('visible'));
+ }
+
+ } catch (error) {
+ console.error('Error opening artist detail view:', error);
+ showToast(`Error: ${error.message}`, 'error');
+ }
+}
+
+/**
+ * Close watchlist artist detail view (slides out)
+ */
+function closeWatchlistArtistDetailView() {
+ const overlay = document.querySelector('.watchlist-artist-detail-overlay');
+ if (overlay) {
+ overlay.classList.remove('visible');
+ overlay.addEventListener('transitionend', () => overlay.remove(), { once: true });
+ }
+}
+
/**
* Open global watchlist settings modal
*/
@@ -27365,6 +27547,23 @@ function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
+/**
+ * Format last scan timestamp as relative time
+ */
+function formatRelativeScanTime(isoString) {
+ if (!isoString) return 'Never scanned';
+ const diff = Date.now() - new Date(isoString).getTime();
+ const mins = Math.floor(diff / 60000);
+ if (mins < 1) return 'Scanned just now';
+ if (mins < 60) return `Scanned ${mins}m ago`;
+ const hrs = Math.floor(mins / 60);
+ if (hrs < 24) return `Scanned ${hrs}h ago`;
+ const days = Math.floor(hrs / 24);
+ if (days < 30) return `Scanned ${days}d ago`;
+ const months = Math.floor(days / 30);
+ return `Scanned ${months}mo ago`;
+}
+
/**
* Filter watchlist artists based on search input
*/
@@ -27476,16 +27675,16 @@ function handleWatchlistScanData(data) {
if (additionsFeed) {
if (data.recent_wishlist_additions && data.recent_wishlist_additions.length > 0) {
additionsFeed.innerHTML = data.recent_wishlist_additions.map(item => `
-
-

-
-
${item.track_name}
-
${item.artist_name}
+
+

+
+
${item.track_name}
+
${item.artist_name}
`).join('');
} else {
- additionsFeed.innerHTML = '
No tracks added yet...
';
+ additionsFeed.innerHTML = '
No tracks added yet...
';
}
}
} else if (liveActivity && data.status !== 'scanning') {
@@ -27523,8 +27722,8 @@ function handleWatchlistScanData(data) {
// Update the scan status display with completion message and summary
statusDiv.innerHTML = `
-
-
${completionMessage}
+
+
${completionMessage}
Artists: ${totalArtists}
•
@@ -27694,6 +27893,9 @@ async function removeFromWatchlistModal(artistId, artistName) {
console.log(`❌ Removed ${artistName} from watchlist`);
+ // Close detail view if open
+ closeWatchlistArtistDetailView();
+
// Refresh the modal
showWatchlistModal();
diff --git a/webui/static/style.css b/webui/static/style.css
index c5b29d48..fe445a44 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -8714,6 +8714,8 @@ body {
max-width: 95vw;
width: 95vw;
max-height: 95vh;
+ position: relative;
+ overflow: hidden;
}
/* Override playlist-modal-body within watchlist to allow grid scrolling */
@@ -8807,6 +8809,34 @@ body {
font-weight: 500;
}
+/* Card release-type pills */
+.watchlist-card-pills {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ padding: 0 16px 10px;
+}
+
+.watchlist-pill {
+ font-size: 9px;
+ font-weight: 600;
+ text-transform: uppercase;
+ padding: 2px 6px;
+ border-radius: 6px;
+ line-height: 1.3;
+ letter-spacing: 0.3px;
+}
+
+.watchlist-pill-active {
+ background: rgba(var(--accent-rgb), 0.15);
+ color: rgb(var(--accent-rgb));
+}
+
+.watchlist-pill-filter {
+ background: rgba(255, 152, 0, 0.15);
+ color: #ff9800;
+}
+
/* Checkbox — top-left, hidden until hover or checked */
.watchlist-card-checkbox {
position: absolute;
@@ -8877,6 +8907,442 @@ body {
color: #ffffff;
}
+/* Gear icon — top-right, visible on hover */
+.watchlist-card-gear {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ z-index: 3;
+ opacity: 0;
+ transition: opacity 0.15s ease, background 0.15s ease;
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ border: none;
+ background: rgba(0, 0, 0, 0.55);
+ color: rgba(255, 255, 255, 0.7);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+}
+
+.watchlist-artist-card:hover .watchlist-card-gear {
+ opacity: 1;
+}
+
+.watchlist-card-gear:hover {
+ background: rgba(var(--accent-rgb), 0.7);
+ color: #ffffff;
+}
+
+.watchlist-card-gear svg {
+ width: 16px;
+ height: 16px;
+ fill: currentColor;
+}
+
+/* Watchlist Artist Detail Overlay */
+.watchlist-artist-detail-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(18, 18, 18, 0.98);
+ z-index: 10;
+ transform: translateX(100%);
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ overflow-y: auto;
+}
+
+.watchlist-artist-detail-overlay.visible {
+ transform: translateX(0);
+}
+
+/* Banner at top of detail view */
+.watchlist-detail-banner {
+ position: relative;
+ width: 100%;
+}
+
+.watchlist-detail-banner img {
+ width: 100%;
+ height: auto;
+ display: block;
+}
+
+.watchlist-detail-banner-fade {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 80px;
+ background: linear-gradient(to bottom, transparent, rgba(18, 18, 18, 0.98));
+}
+
+.watchlist-detail-content {
+ padding: 32px;
+ max-width: 640px;
+ margin: 0 auto;
+}
+
+.watchlist-detail-content.has-banner {
+ padding-top: 16px;
+}
+
+/* Bio text */
+.watchlist-detail-bio {
+ font-size: 13px;
+ line-height: 1.6;
+ color: rgba(255, 255, 255, 0.6);
+ margin: 0;
+ display: -webkit-box;
+ -webkit-line-clamp: 4;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+/* Watchlist info row */
+.watchlist-detail-watchlist-info {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.watchlist-detail-info-sep {
+ color: rgba(255, 255, 255, 0.3);
+ font-size: 11px;
+}
+
+/* Recent releases grid */
+.watchlist-detail-releases {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ gap: 10px;
+}
+
+.watchlist-detail-release {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px;
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.04);
+ transition: background 0.15s ease;
+}
+
+.watchlist-detail-release:hover {
+ background: rgba(255, 255, 255, 0.08);
+}
+
+.watchlist-detail-release img {
+ width: 48px;
+ height: 48px;
+ border-radius: 4px;
+ object-fit: cover;
+ flex-shrink: 0;
+}
+
+.watchlist-detail-release-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ min-width: 0;
+}
+
+.watchlist-detail-release-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: #ffffff;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.watchlist-detail-release-meta {
+ font-size: 11px;
+ color: rgba(255, 255, 255, 0.4);
+}
+
+.watchlist-detail-back {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ background: none;
+ border: none;
+ color: rgba(255, 255, 255, 0.7);
+ font-size: 14px;
+ cursor: pointer;
+ padding: 8px 0;
+ margin-bottom: 24px;
+ transition: color 0.15s ease;
+}
+
+.watchlist-detail-back:hover {
+ color: rgb(var(--accent-rgb));
+}
+
+.watchlist-detail-hero {
+ display: flex;
+ align-items: center;
+ gap: 24px;
+ margin-bottom: 32px;
+}
+
+.watchlist-detail-hero img {
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ object-fit: cover;
+ border: 3px solid rgba(var(--accent-rgb), 0.3);
+ background: #1a1a1a;
+ flex-shrink: 0;
+}
+
+.watchlist-detail-hero-info {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ min-width: 0;
+}
+
+.watchlist-detail-hero-name {
+ font-size: 28px;
+ font-weight: 700;
+ color: #ffffff;
+ margin: 0;
+ line-height: 1.2;
+}
+
+.watchlist-detail-hero-stats {
+ display: flex;
+ gap: 20px;
+}
+
+.watchlist-detail-stat {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.watchlist-detail-stat-value {
+ font-size: 16px;
+ font-weight: 700;
+ color: rgb(var(--accent-rgb));
+}
+
+.watchlist-detail-stat-label {
+ font-size: 10px;
+ text-transform: uppercase;
+ color: rgba(255, 255, 255, 0.4);
+ font-weight: 600;
+ letter-spacing: 0.5px;
+}
+
+.watchlist-detail-hero-genres {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ margin-top: 4px;
+}
+
+.watchlist-detail-genre-tag {
+ font-size: 10px;
+ padding: 3px 8px;
+ border-radius: 12px;
+ background: rgba(255, 255, 255, 0.08);
+ color: rgba(255, 255, 255, 0.6);
+ text-transform: capitalize;
+}
+
+.watchlist-detail-section {
+ margin-bottom: 24px;
+}
+
+.watchlist-detail-section-title {
+ font-size: 11px;
+ text-transform: uppercase;
+ color: rgba(255, 255, 255, 0.4);
+ font-weight: 600;
+ letter-spacing: 0.5px;
+ margin-bottom: 12px;
+}
+
+.watchlist-detail-actions {
+ display: flex;
+ gap: 12px;
+ margin-top: 32px;
+}
+
+.watchlist-detail-actions button {
+ flex: 1;
+ padding: 12px 20px;
+ border-radius: 8px;
+ border: none;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.15s ease, transform 0.1s ease;
+}
+
+.watchlist-detail-actions button:active {
+ transform: scale(0.98);
+}
+
+.watchlist-detail-settings-btn {
+ background: rgba(var(--accent-rgb), 0.15);
+ color: rgb(var(--accent-rgb));
+}
+
+.watchlist-detail-settings-btn:hover {
+ background: rgba(var(--accent-rgb), 0.25);
+}
+
+.watchlist-detail-remove-btn {
+ background: rgba(255, 255, 255, 0.06);
+ color: rgba(255, 255, 255, 0.7);
+}
+
+.watchlist-detail-remove-btn:hover {
+ background: rgba(255, 59, 48, 0.2);
+ color: #ff3b30;
+}
+
+/* Live activity classes (replaces inline styles) */
+.watchlist-live-activity {
+ display: flex;
+ gap: 15px;
+ margin-top: 15px;
+ padding: 15px;
+ background: #2a2a2a;
+ border-radius: 8px;
+ border: 1px solid #444;
+ justify-content: center;
+ align-items: flex-start;
+}
+
+.watchlist-live-activity-col {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+}
+
+.watchlist-live-activity-artist-img {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ border: 2px solid rgb(var(--accent-rgb));
+ object-fit: cover;
+ background: #1a1a1a;
+}
+
+.watchlist-live-activity-album-img {
+ width: 80px;
+ height: 80px;
+ border-radius: 6px;
+ border: 2px solid #ffc107;
+ object-fit: cover;
+ background: #1a1a1a;
+}
+
+.watchlist-live-activity-label {
+ font-size: 11px;
+ font-weight: bold;
+ color: #fff;
+ text-align: center;
+ max-width: 100px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.watchlist-live-activity-feed {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ min-width: 250px;
+ max-width: 300px;
+}
+
+.watchlist-live-activity-feed-label {
+ font-size: 10px;
+ color: #b3b3b3;
+ text-transform: uppercase;
+}
+
+.watchlist-live-activity-feed-label-orange {
+ font-size: 10px;
+ color: #ff9800;
+ text-transform: uppercase;
+}
+
+.watchlist-live-activity-track {
+ font-size: 11px;
+ font-weight: bold;
+ color: rgb(var(--accent-light-rgb));
+ margin-bottom: 8px;
+}
+
+.watchlist-live-addition-item {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ padding: 3px;
+ background: #1a1a1a;
+ border-radius: 4px;
+}
+
+.watchlist-live-addition-item img {
+ width: 24px;
+ height: 24px;
+ border-radius: 3px;
+ object-fit: cover;
+}
+
+.watchlist-live-addition-item-info {
+ flex: 1;
+ overflow: hidden;
+}
+
+.watchlist-live-addition-item-track {
+ font-weight: bold;
+ font-size: 10px;
+ color: rgb(var(--accent-light-rgb));
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.watchlist-live-addition-item-artist {
+ font-size: 9px;
+ color: #b3b3b3;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.watchlist-live-addition-empty {
+ color: #666;
+ font-size: 10px;
+}
+
+.watchlist-scan-completion {
+ text-align: center;
+ padding: 15px;
+ background: #2a2a2a;
+ border-radius: 8px;
+ border: 1px solid #444;
+}
+
+.watchlist-scan-completion-message {
+ font-size: 14px;
+ color: rgb(var(--accent-light-rgb));
+ margin-bottom: 10px;
+}
+
/* Watchlist Search */
.watchlist-search-container {
padding: 0 32px;