Redesign SoulSync Discover cards to grid layout matching Server Playlists
Some checks failed
Compile the app and run tests / sanity-check (push) Has been cancelled
Some checks failed
Compile the app and run tests / sanity-check (push) Has been cancelled
The current cards are full-width horizontal rows, which makes the tab feel
dense once a few playlists accumulate. Rework to a grid of compact cards
that match the Server Playlists visual language already used on the same
Sync page, so the two surfaces feel like the same app.
Changes (all visual — no impact on JohnBaumb's sync/poll logic):
- `.discover-sync-grid` wrapper renders cards as `repeat(auto-fill,
minmax(240px, 1fr))`. 4-6 cards per row at desktop width.
- Per-card hue derived deterministically from playlist type so each card
has a consistent color identity across reloads. Drives the icon tint,
source badge color, toggle color, action-button gradient, and a soft
radial glow in the top-right corner.
- Top row: 40x40 icon chip + source badge ("DEEZER" / "SPOTIFY") pushed
to the top-right (mirrors the chevron placement on Server Playlists).
- Body: playlist name (15px bold, 2-line clamp), track count + status
pill on one line, last-synced subtle line below.
- Compact toggles in a small bordered panel, stacked rows of
label+switch. Smaller 34x18 switch matches the denser card size.
- Full-width gradient action button ("Sync Now") at the bottom.
- Hover: card lifts 2px, glow + border intensify.
Deliberately kept purely visual to avoid touching JohnBaumb's logic:
- Button content stays flat text ("Sync Now" / "Syncing...") so his
`btn.textContent = ...` poll updates still work unchanged.
- Status text format matches the strings his sync-poll code writes
("Synced 10/15" / "Synced" / "Not synced" / "Syncing..." / "Failed"),
so initial render and post-sync updates stay consistent.
- All ids and state-managed class hooks preserved: `discover-sync-card-${type}`,
`discover-sync-btn-${type}`, `.discover-sync-status`, the
syncing/synced/not-synced modifier classes, and the
`discover-sync-card-highlight` deep-link animation class.
Staging branch for review — not targeting dev directly. JohnBaumb can
cherry-pick onto his PR branch if he wants the redesign, or skip it.
This commit is contained in:
parent
97b0ee960a
commit
e40eb213b4
2 changed files with 243 additions and 139 deletions
|
|
@ -9077,78 +9077,104 @@ async function loadDiscoverSyncPlaylists() {
|
|||
}
|
||||
}
|
||||
|
||||
// Grid container lazy-init — we wrap cards in a .discover-sync-grid div so
|
||||
// the vertical scroll container holds a proper grid. Only one grid per
|
||||
// container; subsequent calls append to the existing grid.
|
||||
function _getOrCreateDiscoverSyncGrid(container) {
|
||||
let grid = container.querySelector(':scope > .discover-sync-grid');
|
||||
if (!grid) {
|
||||
grid = document.createElement('div');
|
||||
grid.className = 'discover-sync-grid';
|
||||
container.appendChild(grid);
|
||||
}
|
||||
return grid;
|
||||
}
|
||||
|
||||
// Deterministic hue per playlist type — same playlist always gets the same
|
||||
// gradient-glow color across reloads. Mirrors the i*37+200 formula used by
|
||||
// server-pl-card for color-wheel spread.
|
||||
function _discoverSyncHue(playlistType) {
|
||||
let hash = 0;
|
||||
const s = String(playlistType || '');
|
||||
for (let i = 0; i < s.length; i++) hash = (hash * 31 + s.charCodeAt(i)) | 0;
|
||||
return ((hash % 360) + 360) % 360;
|
||||
}
|
||||
|
||||
function renderDiscoverSyncCard(playlist, container, sourceLabel) {
|
||||
const grid = _getOrCreateDiscoverSyncGrid(container);
|
||||
const card = document.createElement('div');
|
||||
const isEmpty = playlist.track_count === 0;
|
||||
card.className = `discover-sync-card${isEmpty ? ' discover-sync-card-empty' : ''}`;
|
||||
card.id = `discover-sync-card-${playlist.type}`;
|
||||
card.style.setProperty('--card-hue', _discoverSyncHue(playlist.type));
|
||||
|
||||
const lastSyncedText = playlist.last_synced
|
||||
? `Last synced ${timeAgo(playlist.last_synced)}`
|
||||
? `Synced ${timeAgo(playlist.last_synced)}`
|
||||
: 'Never synced';
|
||||
|
||||
const statusClass = playlist.sync_status === 'syncing' ? 'syncing' :
|
||||
playlist.sync_status === 'synced' ? 'synced' : 'not-synced';
|
||||
// Text format mirrors the strings the sync-poll code writes back into
|
||||
// this element on transitions (see pollDiscoverBatchFromTab) — keeping
|
||||
// them aligned means the user sees consistent text across the
|
||||
// render → syncing → synced lifecycle.
|
||||
let statusText = playlist.sync_status === 'syncing' ? 'Syncing...' :
|
||||
playlist.sync_status === 'synced' ? 'Synced' : 'Not synced';
|
||||
|
||||
// Show matched/total counts if available (only when matched > 0, meaning completion was recorded)
|
||||
if (playlist.sync_status === 'synced' && playlist.matched_tracks > 0 && playlist.total_sync_tracks > 0) {
|
||||
statusText = `Synced ${playlist.matched_tracks}/${playlist.total_sync_tracks}`;
|
||||
}
|
||||
|
||||
const trackLabel = isEmpty ? 'No tracks yet' : `${playlist.track_count} tracks`;
|
||||
const safePlaylistName = (playlist.name || '').replace(/'/g, "\\'");
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="discover-sync-card-icon">${playlist.icon}</div>
|
||||
<div class="discover-sync-card-info">
|
||||
<div class="discover-sync-card-name">${playlist.name}
|
||||
<span class="discover-sync-card-meta-inline">
|
||||
<span class="discover-sync-source-badge">${sourceLabel || 'unknown'}</span>
|
||||
<span class="discover-sync-separator">\u00b7</span>
|
||||
<span class="discover-sync-track-count">${trackLabel}</span>
|
||||
<span class="discover-sync-separator">\u00b7</span>
|
||||
<span class="discover-sync-status ${statusClass}">${statusText}</span>
|
||||
<span class="discover-sync-separator">\u00b7</span>
|
||||
<span class="discover-sync-last-synced">${lastSyncedText}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="discover-sync-card-glow"></div>
|
||||
<div class="discover-sync-card-top">
|
||||
<div class="discover-sync-card-icon">${playlist.icon || '🎵'}</div>
|
||||
<div class="discover-sync-source-badge">${sourceLabel || 'unknown'}</div>
|
||||
</div>
|
||||
<div class="discover-sync-card-actions">
|
||||
<div class="discover-sync-toggle-wrapper" title="${isEmpty ? 'No tracks available — visit Discover first' : 'Keep this playlist updated automatically'}">
|
||||
<label class="discover-sync-toggle-label">Keep updated</label>
|
||||
<label class="discover-sync-toggle">
|
||||
<div class="discover-sync-card-body">
|
||||
<div class="discover-sync-card-name">${playlist.name}</div>
|
||||
<div class="discover-sync-card-meta">
|
||||
<span class="discover-sync-track-count">${trackLabel}</span>
|
||||
<span class="discover-sync-status ${statusClass}">${statusText}</span>
|
||||
</div>
|
||||
<div class="discover-sync-last-synced">${lastSyncedText}</div>
|
||||
</div>
|
||||
<div class="discover-sync-card-toggles">
|
||||
<label class="discover-sync-toggle-row" title="${isEmpty ? 'No tracks available — visit Discover first' : 'Keep this playlist updated automatically'}">
|
||||
<span class="discover-sync-toggle-label">Keep updated</span>
|
||||
<span class="discover-sync-toggle">
|
||||
<input type="checkbox" ${playlist.auto_update ? 'checked' : ''} ${isEmpty ? 'disabled' : ''}
|
||||
onchange="toggleDiscoverAutoUpdate('${playlist.type}', this.checked)">
|
||||
<span class="discover-sync-toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="discover-sync-toggle-wrapper" title="Coming soon: download any available quality for this batch, even if it's below your global quality profile. Useful for rotating discover playlists where quantity matters more than quality.">
|
||||
<label class="discover-sync-toggle-label" style="opacity:0.5">Any Quality</label>
|
||||
<label class="discover-sync-toggle" style="opacity:0.5;cursor:not-allowed">
|
||||
</span>
|
||||
</label>
|
||||
<label class="discover-sync-toggle-row disabled" title="Coming soon: download any available quality for this batch, even if it's below your global quality profile. Useful for rotating discover playlists where quantity matters more than quality.">
|
||||
<span class="discover-sync-toggle-label">Any Quality</span>
|
||||
<span class="discover-sync-toggle">
|
||||
<input type="checkbox" id="discover-any-quality-${playlist.type}" disabled>
|
||||
<span class="discover-sync-toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button class="discover-sync-btn" id="discover-sync-btn-${playlist.type}"
|
||||
onclick="syncDiscoverPlaylistFromTab('${playlist.type}', '${playlist.name}')"
|
||||
${playlist.sync_status === 'syncing' || isEmpty ? 'disabled' : ''}>
|
||||
\u27f3 Sync Now
|
||||
</button>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<button class="discover-sync-card-action" id="discover-sync-btn-${playlist.type}"
|
||||
onclick="event.stopPropagation(); syncDiscoverPlaylistFromTab('${playlist.type}', '${safePlaylistName}')"
|
||||
${playlist.sync_status === 'syncing' || isEmpty ? 'disabled' : ''}>⟳ Sync Now</button>
|
||||
`;
|
||||
|
||||
// Make the icon + info area clickable to view tracks
|
||||
// Clicking the top (icon + badge) or body (name + meta) opens the track list.
|
||||
// The Sync Now button stops propagation so it doesn't double-trigger.
|
||||
if (!isEmpty) {
|
||||
const clickArea = card.querySelector('.discover-sync-card-info');
|
||||
const iconArea = card.querySelector('.discover-sync-card-icon');
|
||||
[clickArea, iconArea].forEach(el => {
|
||||
const openTracks = () => openDiscoverPlaylistModal(playlist.type, playlist.name, playlist.icon);
|
||||
card.querySelectorAll('.discover-sync-card-top, .discover-sync-card-body').forEach(el => {
|
||||
el.style.cursor = 'pointer';
|
||||
el.addEventListener('click', () => openDiscoverPlaylistModal(playlist.type, playlist.name, playlist.icon));
|
||||
el.addEventListener('click', openTracks);
|
||||
});
|
||||
}
|
||||
|
||||
container.appendChild(card);
|
||||
grid.appendChild(card);
|
||||
}
|
||||
|
||||
async function toggleDiscoverAutoUpdate(playlistType, enabled) {
|
||||
|
|
|
|||
|
|
@ -59593,134 +59593,197 @@ body[data-artist-source="source"] #artist-detail-page #library-artist-enhance-bt
|
|||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────── */
|
||||
/* SoulSync Discover cards — grid layout mirroring server-pl-card pattern */
|
||||
/* ─────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
.discover-sync-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.discover-sync-card {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
position: relative;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 10px;
|
||||
margin: 3px 6px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 14px;
|
||||
padding: 14px 14px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
transition: all 0.25s ease;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-height: 260px;
|
||||
overflow: hidden;
|
||||
transition: transform 0.25s ease, border-color 0.25s ease, background 0.25s ease;
|
||||
}
|
||||
|
||||
.discover-sync-card:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(167, 139, 250, 0.2);
|
||||
transform: translateY(-2px);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-color: hsla(var(--card-hue, 260), 80%, 70%, 0.35);
|
||||
}
|
||||
|
||||
.discover-sync-card-glow {
|
||||
position: absolute;
|
||||
top: -40%;
|
||||
right: -20%;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
background: radial-gradient(circle,
|
||||
hsla(var(--card-hue, 260), 80%, 60%, 0.22) 0%,
|
||||
hsla(var(--card-hue, 260), 80%, 60%, 0) 70%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.discover-sync-card:hover .discover-sync-card-glow {
|
||||
opacity: 1.4;
|
||||
}
|
||||
|
||||
.discover-sync-card > *:not(.discover-sync-card-glow) {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ── Top row: icon + source badge ── */
|
||||
.discover-sync-card-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.discover-sync-card-icon {
|
||||
font-size: 20px;
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 22px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg,
|
||||
hsla(var(--card-hue, 260), 70%, 55%, 0.2),
|
||||
hsla(var(--card-hue, 260), 70%, 40%, 0.1));
|
||||
border: 1px solid hsla(var(--card-hue, 260), 70%, 60%, 0.2);
|
||||
border-radius: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.discover-sync-card-info {
|
||||
.discover-sync-source-badge {
|
||||
font-size: 9.5px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.6px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 5px;
|
||||
color: hsla(var(--card-hue, 260), 80%, 80%, 1);
|
||||
background: hsla(var(--card-hue, 260), 70%, 55%, 0.12);
|
||||
border: 1px solid hsla(var(--card-hue, 260), 70%, 60%, 0.25);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ── Body: name + meta line + last-synced ── */
|
||||
.discover-sync-card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.discover-sync-card-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.discover-sync-card-meta-inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.discover-sync-source-badge {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 1px 7px;
|
||||
border-radius: 4px;
|
||||
color: rgb(var(--accent-light-rgb));
|
||||
background: rgba(var(--accent-rgb), 0.1);
|
||||
border: 1px solid rgba(var(--accent-rgb), 0.2);
|
||||
}
|
||||
|
||||
.discover-sync-card-desc {
|
||||
display: none;
|
||||
line-height: 1.25;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.discover-sync-card-meta {
|
||||
display: none;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.discover-sync-separator {
|
||||
opacity: 0.4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 11.5px;
|
||||
font-weight: 500;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.discover-sync-track-count {
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.discover-sync-status {
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.discover-sync-status.synced {
|
||||
color: #22c55e;
|
||||
color: #4ade80;
|
||||
background: rgba(34, 197, 94, 0.12);
|
||||
}
|
||||
|
||||
.discover-sync-status.syncing {
|
||||
color: #facc15;
|
||||
color: #fde047;
|
||||
background: rgba(250, 204, 21, 0.14);
|
||||
}
|
||||
|
||||
.discover-sync-status.not-synced {
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.discover-sync-last-synced {
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.discover-sync-card-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.discover-sync-toggle-wrapper {
|
||||
/* ── Toggles (stacked rows of label + switch) ── */
|
||||
.discover-sync-card-toggles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 8px 10px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.discover-sync-toggle-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
.discover-sync-toggle-row.disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.discover-sync-toggle-label {
|
||||
font-size: 10.5px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 11.5px;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.discover-sync-toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 22px;
|
||||
cursor: pointer;
|
||||
width: 34px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.discover-sync-toggle input {
|
||||
|
|
@ -59736,56 +59799,65 @@ body[data-artist-source="source"] #artist-detail-page #library-artist-enhance-bt
|
|||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border-radius: 22px;
|
||||
border-radius: 18px;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.discover-sync-toggle-slider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.discover-sync-toggle input:checked + .discover-sync-toggle-slider {
|
||||
background: #a78bfa;
|
||||
background: hsla(var(--card-hue, 260), 75%, 60%, 1);
|
||||
}
|
||||
|
||||
.discover-sync-toggle input:checked + .discover-sync-toggle-slider::before {
|
||||
transform: translateX(18px);
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
.discover-sync-btn {
|
||||
padding: 8px 16px;
|
||||
background: linear-gradient(135deg, #a78bfa, #7c3aed);
|
||||
/* ── Primary action button ── */
|
||||
/* Kept as a flat-text button (no nested children) because the sync-poll
|
||||
code writes `btn.textContent = 'Syncing…'` / `'⟳ Sync Now'` on state
|
||||
transitions — any child elements would be wiped on every toggle. */
|
||||
.discover-sync-card-action {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
background: linear-gradient(135deg,
|
||||
hsla(var(--card-hue, 260), 70%, 55%, 0.85),
|
||||
hsla(var(--card-hue, 260), 70%, 42%, 0.85));
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
border: 1px solid hsla(var(--card-hue, 260), 70%, 65%, 0.35);
|
||||
border-radius: 10px;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.25s ease;
|
||||
text-align: center;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.discover-sync-btn:hover:not(:disabled) {
|
||||
.discover-sync-card-action:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(167, 139, 250, 0.35);
|
||||
filter: brightness(1.08);
|
||||
box-shadow: 0 6px 18px hsla(var(--card-hue, 260), 70%, 50%, 0.35);
|
||||
}
|
||||
|
||||
.discover-sync-btn:disabled {
|
||||
opacity: 0.4;
|
||||
.discover-sync-card-action:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
filter: grayscale(30%);
|
||||
}
|
||||
|
||||
/* Empty-state styling */
|
||||
/* ── Empty / highlight states ── */
|
||||
.discover-sync-card-empty {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
|
@ -59794,16 +59866,22 @@ body[data-artist-source="source"] #artist-detail-page #library-artist-enhance-bt
|
|||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* Deep-link highlight animation */
|
||||
.discover-sync-card-highlight {
|
||||
animation: discover-card-glow 2.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes discover-card-glow {
|
||||
0% { border-color: rgba(167, 139, 250, 0.7); box-shadow: 0 0 20px rgba(167, 139, 250, 0.3); }
|
||||
100% { border-color: rgba(255, 255, 255, 0.08); box-shadow: none; }
|
||||
0% {
|
||||
border-color: hsla(var(--card-hue, 260), 80%, 70%, 0.9);
|
||||
box-shadow: 0 0 24px hsla(var(--card-hue, 260), 80%, 60%, 0.4);
|
||||
}
|
||||
100% {
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Existing empty-state hint + source info (unchanged) ── */
|
||||
.discover-sync-empty-hint {
|
||||
background: rgba(255, 193, 7, 0.08);
|
||||
border: 1px solid rgba(255, 193, 7, 0.25);
|
||||
|
|
@ -59839,14 +59917,14 @@ body[data-artist-source="source"] #artist-detail-page #library-artist-enhance-bt
|
|||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.discover-sync-card {
|
||||
flex-wrap: wrap;
|
||||
.discover-sync-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.discover-sync-card-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
.discover-sync-card {
|
||||
min-height: 240px;
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue