watchlist redesign

This commit is contained in:
Broque Thomas 2026-03-01 20:35:49 -08:00
parent 046233817d
commit 3dad2eae38
3 changed files with 235 additions and 142 deletions

View file

@ -1405,36 +1405,37 @@
text-align: center;
}
/* Inline remove buttons - compact sizing */
.watchlist-remove-btn {
width: auto;
min-height: auto;
padding: 6px 12px;
font-size: 12px;
min-width: auto;
flex-shrink: 0;
/* Watchlist fullscreen modal on mobile */
.watchlist-fullscreen {
max-width: 100vw;
width: 100vw;
max-height: 100vh;
border-radius: 0;
}
/* Watchlist artist items */
.watchlist-artist-item {
padding: 12px 14px;
gap: 10px;
/* Watchlist card grid on mobile */
.watchlist-artists-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 16px;
padding: 16px;
}
.watchlist-artist-image,
.watchlist-artist-image-placeholder {
width: 44px;
height: 44px;
margin-right: 10px;
.watchlist-card-info {
padding: 10px 12px;
}
.watchlist-artist-name {
font-size: 14px;
.watchlist-card-name {
font-size: 13px;
}
.watchlist-artist-date,
.watchlist-artist-scan {
font-size: 11px;
.watchlist-card-meta {
font-size: 10px;
}
/* Show checkbox and remove always on mobile (no hover) */
.watchlist-card-checkbox,
.watchlist-artist-card .watchlist-card-remove {
opacity: 1;
}
.watchlist-search-container {
@ -1510,7 +1511,7 @@
/* --- Phase 9: Touch & Hover Adaptations --- */
/* Global touch targets - only standalone/action buttons, not inline */
button:not(.watchlist-remove-btn):not(.wishlist-delete-btn):not(.wishlist-delete-album-btn):not(.wishlist-delete-btn-small):not(.wishlist-back-btn):not(.alphabet-btn):not(.filter-btn):not(.playlist-modal-close) {
button:not(.watchlist-card-remove):not(.wishlist-delete-btn):not(.wishlist-delete-album-btn):not(.wishlist-delete-btn-small):not(.wishlist-back-btn):not(.alphabet-btn):not(.filter-btn):not(.playlist-modal-close) {
min-height: 38px;
}
@ -1790,25 +1791,19 @@
font-size: 13px;
}
/* Watchlist items even tighter */
.watchlist-artist-item {
padding: 10px 12px;
/* Watchlist cards — tighter grid on small screens */
.watchlist-artists-grid {
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 12px;
padding: 12px;
}
.watchlist-artist-image,
.watchlist-artist-image-placeholder {
width: 38px;
height: 38px;
margin-right: 8px;
.watchlist-card-name {
font-size: 12px;
}
.watchlist-artist-name {
font-size: 13px;
}
.watchlist-remove-btn {
padding: 5px 10px;
font-size: 11px;
.watchlist-card-info {
padding: 8px 10px;
}
/* Download modal - even more compact */

View file

@ -25766,7 +25766,7 @@ async function showWatchlistModal() {
// Build modal content
modal.innerHTML = `
<div class="modal-container playlist-modal">
<div class="modal-container playlist-modal watchlist-fullscreen">
<div class="playlist-modal-header">
<div class="playlist-header-content" style="width: 100%;">
<h2>👁 Watchlist</h2>
@ -25814,9 +25814,9 @@ async function showWatchlistModal() {
</div>
<span class="playlist-modal-close" onclick="closeWatchlistModal()">&times;</span>
</div>
<div class="playlist-modal-body">
<div class="watchlist-actions" style="margin-bottom: 20px; display: flex; gap: 12px; align-items: center;">
<div class="watchlist-actions" style="margin-bottom: 16px; display: flex; gap: 12px; align-items: center; padding: 0 32px; flex-wrap: wrap;">
<button class="playlist-modal-btn playlist-modal-btn-primary"
id="scan-watchlist-btn"
onclick="startWatchlistScan()"
@ -25838,14 +25838,14 @@ async function showWatchlistModal() {
</div>
${globalOverrideActive ? `
<div class="watchlist-global-override-banner">
<div class="watchlist-global-override-banner" style="margin: 0 32px 12px;">
<span></span>
<span>Global override is active per-artist settings are being ignored during scans.</span>
</div>
` : ''}
<!-- Search Bar -->
<div class="watchlist-search-container" style="margin-bottom: 16px;">
<div class="watchlist-search-container" style="margin-bottom: 12px;">
<input type="text"
id="watchlist-search-input"
class="watchlist-search-input"
@ -25863,58 +25863,50 @@ async function showWatchlistModal() {
</button>
</div>
<div class="watchlist-artists-list" id="watchlist-artists-list">
<div class="watchlist-artists-grid" id="watchlist-artists-list">
${artistsData.artists.map(artist => `
<div class="watchlist-artist-item"
<div class="watchlist-artist-card"
data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '&quot;')}"
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
style="cursor: pointer;">
<label class="watchlist-checkbox-wrapper" onclick="event.stopPropagation();">
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}">
<label class="watchlist-card-checkbox" onclick="event.stopPropagation();">
<input type="checkbox" class="watchlist-select-cb"
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
data-artist-name="${escapeHtml(artist.artist_name)}"
onchange="updateWatchlistBatchBar()">
<span class="watchlist-checkbox-custom"></span>
</label>
${artist.image_url ? `
<img src="${artist.image_url}"
alt="${escapeHtml(artist.artist_name)}"
class="watchlist-artist-image"
loading="lazy"
onerror="this.style.display='none'">
` : `
<div class="watchlist-artist-image-placeholder">
🎤
</div>
`}
<div class="watchlist-artist-info">
<span class="watchlist-artist-name">${escapeHtml(artist.artist_name)}</span>
<span class="watchlist-artist-date">Added ${new Date(artist.date_added).toLocaleDateString()}</span>
${artist.last_scan_timestamp ? `
<span class="watchlist-artist-scan">Last scanned ${new Date(artist.last_scan_timestamp).toLocaleDateString()}</span>
` : ''}
</div>
<button class="playlist-modal-btn playlist-modal-btn-secondary watchlist-remove-btn"
<button class="watchlist-card-remove"
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
data-artist-name="${escapeHtml(artist.artist_name)}"
onclick="event.stopPropagation();">
Remove
</button>
onclick="event.stopPropagation();"
title="Remove from watchlist">&times;</button>
<div class="watchlist-card-image">
${artist.image_url ? `
<img src="${artist.image_url}"
alt="${escapeHtml(artist.artist_name)}"
loading="lazy"
onerror="this.parentElement.innerHTML='<div class=\\'watchlist-card-image-fallback\\'>🎤</div>';">
` : `
<div class="watchlist-card-image-fallback">🎤</div>
`}
</div>
<div class="watchlist-card-info">
<span class="watchlist-card-name">${escapeHtml(artist.artist_name)}</span>
<span class="watchlist-card-meta">Added ${new Date(artist.date_added).toLocaleDateString()}</span>
</div>
</div>
`).join('')}
</div>
</div>
<div class="playlist-modal-footer">
<button class="playlist-modal-btn playlist-modal-btn-secondary" onclick="closeWatchlistModal()">
Close
</button>
</div>
</div>
`;
// Add event listeners for remove buttons
modal.querySelectorAll('.watchlist-remove-btn').forEach(button => {
modal.querySelectorAll('.watchlist-card-remove').forEach(button => {
button.addEventListener('click', () => {
const artistId = button.getAttribute('data-artist-id');
const artistName = button.getAttribute('data-artist-name');
@ -25922,16 +25914,16 @@ async function showWatchlistModal() {
});
});
// Add click handlers to artist items (except for remove button or checkbox)
modal.querySelectorAll('.watchlist-artist-item').forEach(item => {
// Add click handlers to artist cards (except for remove 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-remove-btn') || e.target.closest('.watchlist-checkbox-wrapper')) {
if (e.target.closest('.watchlist-card-remove') || e.target.closest('.watchlist-card-checkbox')) {
return;
}
const artistId = item.getAttribute('data-artist-id');
const artistName = item.querySelector('.watchlist-artist-name').textContent;
const artistName = item.querySelector('.watchlist-card-name').textContent;
console.log(`🎵 Artist card clicked: ${artistName} (${artistId})`);
openWatchlistArtistConfigModal(artistId, artistName);
@ -26402,13 +26394,13 @@ function filterWatchlistArtists() {
if (!searchInput || !artistsList) return;
const searchTerm = searchInput.value.toLowerCase().trim();
const artistItems = artistsList.querySelectorAll('.watchlist-artist-item');
const artistItems = artistsList.querySelectorAll('.watchlist-artist-card');
artistItems.forEach(item => {
const artistName = item.getAttribute('data-artist-name');
if (!searchTerm || artistName.includes(searchTerm)) {
item.style.display = 'flex';
item.style.display = '';
} else {
item.style.display = 'none';
}
@ -26736,7 +26728,7 @@ async function removeFromWatchlistModal(artistId, artistName) {
*/
function getVisibleCheckedWatchlist() {
return Array.from(document.querySelectorAll('.watchlist-select-cb:checked')).filter(cb => {
const item = cb.closest('.watchlist-artist-item');
const item = cb.closest('.watchlist-artist-card');
return item && item.style.display !== 'none';
});
}

View file

@ -8708,34 +8708,178 @@ body {
}
/* Watchlist Modal Styles */
.watchlist-artists-list {
max-height: 400px;
overflow-y: auto;
margin-top: 20px;
padding: 30px;
/* Fullscreen modifier for watchlist modal */
.watchlist-fullscreen {
max-width: 95vw;
width: 95vw;
max-height: 95vh;
}
.watchlist-artist-item {
/* Override playlist-modal-body within watchlist to allow grid scrolling */
.watchlist-fullscreen .playlist-modal-body {
overflow-y: auto;
padding: 0;
min-height: 0;
}
/* Card grid */
.watchlist-artists-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 24px;
padding: 24px 32px 32px;
}
/* Individual artist card */
.watchlist-artist-card {
position: relative;
border-radius: 16px;
overflow: hidden;
cursor: pointer;
background: rgba(18, 18, 18, 1);
border: 1px solid rgba(255, 255, 255, 0.06);
transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}
.watchlist-artist-card:hover {
transform: translateY(-4px);
border-color: rgba(var(--accent-rgb), 0.25);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5),
0 0 20px rgba(var(--accent-rgb), 0.1);
}
.watchlist-artist-card:active {
transform: translateY(-1px);
}
/* Image container — normal flow, sets the card height */
.watchlist-card-image {
width: 100%;
aspect-ratio: 1;
overflow: hidden;
background: linear-gradient(135deg,
rgba(30, 30, 30, 1) 0%,
rgba(20, 20, 20, 1) 100%);
}
.watchlist-card-image img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* Image fallback */
.watchlist-card-image-fallback {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px 16px 24px;
margin-bottom: 12px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
transition: all 0.2s ease;
justify-content: center;
font-size: 56px;
color: rgba(255, 255, 255, 0.3);
}
.watchlist-artist-item:hover {
background: rgba(255, 255, 255, 0.06);
border-color: rgba(var(--accent-rgb), 0.2);
transform: translateY(-1px);
/* Info below the image — normal flow */
.watchlist-card-info {
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 3px;
background: rgba(18, 18, 18, 1);
}
.watchlist-card-name {
font-size: 15px;
font-weight: 700;
color: #ffffff;
line-height: 1.25;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.watchlist-card-meta {
font-size: 11px;
color: rgba(255, 255, 255, 0.5);
font-weight: 500;
}
/* Checkbox — top-left, hidden until hover or checked */
.watchlist-card-checkbox {
position: absolute;
top: 10px;
left: 10px;
z-index: 3;
opacity: 0;
transition: opacity 0.15s ease;
cursor: pointer;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
}
.watchlist-card-checkbox input[type="checkbox"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.watchlist-card-checkbox input:checked+.watchlist-checkbox-custom {
background: rgba(var(--accent-rgb), 0.3);
border-color: rgb(var(--accent-rgb));
}
.watchlist-card-checkbox input:checked+.watchlist-checkbox-custom::after {
content: '✓';
color: rgb(var(--accent-rgb));
font-size: 13px;
font-weight: 700;
}
.watchlist-artist-card:hover .watchlist-card-checkbox,
.watchlist-card-checkbox:has(input:checked) {
opacity: 1;
}
/* Remove × button — top-right, visible on hover */
.watchlist-card-remove {
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);
font-size: 18px;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.watchlist-artist-card:hover .watchlist-card-remove {
opacity: 1;
}
.watchlist-card-remove:hover {
background: rgba(255, 59, 48, 0.7);
color: #ffffff;
}
/* Watchlist Search */
.watchlist-search-container {
padding: 0 30px;
padding: 0 32px;
}
.watchlist-search-input {
@ -8747,7 +8891,7 @@ body {
color: #ffffff;
font-size: 14px;
font-family: inherit;
transition: all 0.2s ease;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.watchlist-search-input:focus {
@ -8761,44 +8905,6 @@ body {
color: rgba(255, 255, 255, 0.4);
}
/* Artist Image */
.watchlist-artist-image {
width: 56px;
height: 56px;
border-radius: 50%;
object-fit: cover;
margin-right: 16px;
flex-shrink: 0;
border: 2px solid rgba(255, 255, 255, 0.1);
transition: all 0.2s ease;
}
.watchlist-artist-item:hover .watchlist-artist-image {
border-color: rgba(var(--accent-rgb), 0.4);
transform: scale(1.05);
}
.watchlist-artist-image-placeholder {
width: 56px;
height: 56px;
border-radius: 50%;
margin-right: 16px;
flex-shrink: 0;
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.2), rgba(var(--accent-rgb), 0.05));
border: 2px solid rgba(var(--accent-rgb), 0.3);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
transition: all 0.2s ease;
}
.watchlist-artist-item:hover .watchlist-artist-image-placeholder {
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.3), rgba(var(--accent-rgb), 0.1));
border-color: rgba(var(--accent-rgb), 0.5);
transform: scale(1.05);
}
/* Watchlist & Wishlist Batch Action Bar */
.watchlist-batch-bar,
.wishlist-batch-bar {
@ -8806,7 +8912,7 @@ body {
align-items: center;
justify-content: space-between;
padding: 10px 20px;
margin: 0 30px 12px;
margin: 0 32px 12px;
background: rgba(255, 59, 48, 0.1);
border: 1px solid rgba(255, 59, 48, 0.25);
border-radius: 10px;