Mirrored playlist modal: revamp + fix header clipping on long playlists

The preview modal looked amateur and its header/footer clipped on long
playlists (wolf39's 316-track "Road trip" showed neither title nor buttons).

Root cause of the clip: .mm-list (the scroll area) was a flex child with
flex:1 but no min-height:0. Flex items default to min-height:auto, so the
list refused to shrink below its content, the modal blew past max-height,
and overflow:hidden + vertical centering pushed the header off the top and
the footer off the bottom. Now the list has min-height:0 and the hero +
action bar are flex-shrink:0, so they stay pinned and the list scrolls.

Visual revamp to match the rest of the app, using data already returned by
/api/mirrored-playlists/<id> (image_url on the playlist and each track):
- Hero uses real artwork (playlist cover -> first track art -> gradient
  fallback) with a blurred art backdrop + darkening overlay, replacing the
  emoji-in-a-box. Eyebrow + large title + meta line (source pill, owner,
  track count, total runtime, mirrored-ago).
- Track rows gain per-track album thumbnails, two-line title/artist, album,
  duration, and a sticky column header. Missing art falls back to a gradient
  tile via onerror (no broken-image icons).
- Cleaner action bar: primary Discover, secondary Auto-Sync, ghost Edit/
  Close, quiet danger-outline Delete.

Old .mirrored-modal-* / .mirrored-track-* / .mirrored-btn-* classes removed
from style.css and replaced with the new .mm-* set; the _escJs escaping in
the footer buttons (apostrophe fix) is preserved.
This commit is contained in:
BoulderBadgeDad 2026-06-01 09:31:20 -07:00
parent ffb9249ded
commit 6e7948b642
2 changed files with 288 additions and 218 deletions

View file

@ -829,52 +829,74 @@ async function openMirroredPlaylistModal(playlistId) {
const tracks = data.tracks || [];
const source = data.source || 'unknown';
const sourceRef = getMirroredSourceRef(data);
const sourceIcons = { spotify: '🎵', tidal: '🌊', youtube: '▶', beatport: '🎛' };
const sourceIcons = { spotify: '🎵', spotify_public: '🎵', tidal: '🌊', youtube: '▶', beatport: '🎛', deezer: '🎧', qobuz: '♫' };
const sourceLabels = { spotify: 'Spotify', spotify_public: 'Spotify', tidal: 'Tidal', youtube: 'YouTube', beatport: 'Beatport', deezer: 'Deezer', qobuz: 'Qobuz' };
const sourceIcon = sourceIcons[source] || '📋';
const srcLabel = sourceLabels[source] || source;
// Hero artwork: playlist cover → first track with art → gradient fallback.
const heroArt = data.image_url || (tracks.find(t => t.image_url) || {}).image_url || '';
// Total runtime for the meta line.
const totalMs = tracks.reduce((sum, t) => sum + (t.duration_ms || 0), 0);
const totalMin = Math.round(totalMs / 60000);
const totalLabel = totalMin >= 60 ? `${Math.floor(totalMin / 60)} hr ${totalMin % 60} min` : `${totalMin} min`;
const trackRows = tracks.map(t => {
const dur = t.duration_ms ? `${Math.floor(t.duration_ms / 60000)}:${String(Math.floor((t.duration_ms % 60000) / 1000)).padStart(2, '0')}` : '';
return `<div class="mirrored-track-row">
<span class="track-pos">${t.position}</span>
<span class="track-title">${_esc(t.track_name)}</span>
<span class="track-artist">${_esc(t.artist_name)}</span>
<span class="track-album">${_esc(t.album_name)}</span>
<span class="track-duration">${dur}</span>
const art = t.image_url
? `<img class="mm-row-art" src="${_escAttr(t.image_url)}" loading="lazy" onerror="this.onerror=null;this.classList.add('mm-art-empty');this.removeAttribute('src');">`
: `<span class="mm-row-art mm-art-empty"></span>`;
return `<div class="mm-row">
<span class="mm-row-pos">${t.position}</span>
${art}
<div class="mm-row-main">
<span class="mm-row-title">${_esc(t.track_name)}</span>
<span class="mm-row-artist">${_esc(t.artist_name)}</span>
</div>
<span class="mm-row-album">${_esc(t.album_name || '')}</span>
<span class="mm-row-dur">${dur}</span>
</div>`;
}).join('');
const heroCover = heroArt
? `<div class="mm-cover" style="background-image:url('${_escAttr(heroArt)}')"></div>`
: `<div class="mm-cover mm-cover-empty ${_escAttr(source)}">${sourceIcon}</div>`;
overlay.innerHTML = `
<div class="mirrored-modal">
<div class="mirrored-modal-header">
<div class="mirrored-modal-hero">
<div class="mirrored-modal-hero-icon ${_escAttr(source)}">${sourceIcon}</div>
<div class="mirrored-modal-hero-info">
<h2 class="mirrored-modal-hero-title">${_esc(data.name)}</h2>
<div class="mirrored-modal-hero-subtitle">
<span class="mirrored-modal-hero-badge">${_esc(source)}</span>
<span>${tracks.length} tracks</span>
<span>&middot;</span>
<span>Mirrored ${timeAgo(data.updated_at || data.mirrored_at)}</span>
<div class="mm-hero">
${heroArt ? `<div class="mm-hero-bg" style="background-image:url('${_escAttr(heroArt)}')"></div>` : ''}
<div class="mm-hero-content">
${heroCover}
<div class="mm-hero-info">
<span class="mm-eyebrow">Mirrored Playlist</span>
<h2 class="mm-title">${_esc(data.name)}</h2>
<div class="mm-meta">
<span class="mm-source-pill ${_escAttr(source)}">${_esc(srcLabel)}</span>
${data.owner ? `<span class="mm-meta-item">${_esc(data.owner)}</span><span class="mm-dot">&middot;</span>` : ''}
<span class="mm-meta-item">${tracks.length} tracks</span>
${totalMs ? `<span class="mm-dot">&middot;</span><span class="mm-meta-item">${totalLabel}</span>` : ''}
<span class="mm-dot">&middot;</span>
<span class="mm-meta-item">Mirrored ${timeAgo(data.updated_at || data.mirrored_at)}</span>
</div>
</div>
</div>
<span class="mirrored-modal-close" onclick="closeMirroredModal()">&times;</span>
<button class="mm-close" onclick="closeMirroredModal()" aria-label="Close">&times;</button>
</div>
<div class="mirrored-modal-tracks">
<div class="mirrored-track-header">
<span>#</span><span>Track</span><span>Artist</span><span>Album</span><span style="text-align:right">Time</span>
<div class="mm-list">
<div class="mm-list-head">
<span>#</span><span></span><span>Title</span><span>Album</span><span class="mm-col-dur">Time</span>
</div>
${trackRows}
${trackRows || '<div class="mm-empty">No tracks in this mirror yet.</div>'}
</div>
<div class="mirrored-modal-footer">
<div class="mirrored-modal-footer-left">
<button class="mirrored-btn-delete" onclick="closeMirroredModal(); deleteMirroredPlaylist(${playlistId}, '${_escJs(data.name)}')">Delete Mirror</button>
</div>
<div class="mirrored-modal-footer-right" style="display:flex;gap:10px;">
<button class="mirrored-btn-close" onclick="editMirroredSourceRef(${playlistId}, '${_escJs(data.name)}', '${_escJs(source)}', '${_escJs(sourceRef)}')">Edit Source</button>
<button class="mirrored-btn-pipeline" onclick="runMirroredPlaylistPipeline(${playlistId}, '${_escJs(data.name)}')">Auto-Sync</button>
<button class="mirrored-btn-close" onclick="closeMirroredModal()">Close</button>
<button class="mirrored-btn-discover" onclick="discoverMirroredPlaylist(${playlistId})">Discover</button>
<div class="mm-actions">
<button class="mm-btn mm-btn-danger" onclick="closeMirroredModal(); deleteMirroredPlaylist(${playlistId}, '${_escJs(data.name)}')">Delete Mirror</button>
<div class="mm-actions-right">
<button class="mm-btn mm-btn-ghost" onclick="editMirroredSourceRef(${playlistId}, '${_escJs(data.name)}', '${_escJs(source)}', '${_escJs(sourceRef)}')">Edit Source</button>
<button class="mm-btn mm-btn-secondary" onclick="runMirroredPlaylistPipeline(${playlistId}, '${_escJs(data.name)}')">Auto-Sync</button>
<button class="mm-btn mm-btn-ghost" onclick="closeMirroredModal()">Close</button>
<button class="mm-btn mm-btn-primary" onclick="discoverMirroredPlaylist(${playlistId})">Discover</button>
</div>
</div>
</div>

View file

@ -15244,315 +15244,363 @@ body.helper-mode-active #dashboard-activity-feed:hover {
.mirrored-modal-overlay {
position: fixed;
inset: 0;
background: rgba(18, 18, 18, 0.85);
background: rgba(8, 8, 10, 0.78);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(12px);
animation: mirroredOverlayFadeIn 0.3s ease forwards;
padding: 24px;
backdrop-filter: blur(14px) saturate(1.1);
animation: mirroredOverlayFadeIn 0.25s ease forwards;
}
@keyframes mirroredOverlayFadeIn {
from { opacity: 0; backdrop-filter: blur(0px); }
to { opacity: 1; backdrop-filter: blur(12px); }
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes mirroredModalSlideIn {
from { opacity: 0; transform: scale(0.92) translateY(20px); }
from { opacity: 0; transform: scale(0.96) translateY(16px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.mirrored-modal {
background: linear-gradient(135deg, rgba(20, 20, 20, 0.95) 0%, rgba(12, 12, 12, 0.98) 100%);
backdrop-filter: blur(20px) saturate(1.2);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.12);
border-top: 1px solid rgba(255, 255, 255, 0.18);
width: 90%;
max-width: 950px;
max-height: 85vh;
position: relative;
background: #161616;
border-radius: 18px;
border: 1px solid rgba(255, 255, 255, 0.10);
width: 100%;
max-width: 900px;
max-height: 88vh;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow:
0 25px 80px rgba(0, 0, 0, 0.7),
0 8px 32px rgba(0, 0, 0, 0.4),
0 0 40px rgba(var(--accent-rgb), 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.15);
animation: mirroredModalSlideIn 0.35s cubic-bezier(0.4, 0, 0.2, 1) forwards;
box-shadow: 0 30px 90px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(0, 0, 0, 0.4);
animation: mirroredModalSlideIn 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.mirrored-modal-header {
background: linear-gradient(135deg, rgba(45, 45, 45, 0.8) 0%, rgba(26, 26, 26, 0.9) 100%);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
padding: 0;
/* ---------- Hero ---------- */
.mm-hero {
position: relative;
flex-shrink: 0; /* header is pinned, never clipped */
padding: 30px 32px;
overflow: hidden;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.mirrored-modal-hero {
display: flex;
align-items: center;
padding: 28px 32px;
padding-right: 60px;
gap: 20px;
.mm-hero-bg {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
filter: blur(44px) saturate(1.5);
opacity: 0.4;
transform: scale(1.25);
z-index: 0;
}
.mm-hero::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(22, 22, 22, 0.55) 0%, rgba(22, 22, 22, 0.88) 100%);
z-index: 0;
}
.mm-hero-content {
position: relative;
z-index: 2;
min-height: 110px;
z-index: 1;
display: flex;
align-items: flex-end;
gap: 22px;
padding-right: 44px;
}
.mirrored-modal-hero-icon {
width: 72px;
height: 72px;
.mm-cover {
width: 128px;
height: 128px;
flex-shrink: 0;
border-radius: 12px;
background-size: cover;
background-position: center;
background-color: #242424;
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.55);
border: 1px solid rgba(255, 255, 255, 0.10);
}
.mm-cover-empty {
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
border-radius: 14px;
background: linear-gradient(135deg, rgba(167, 139, 250, 0.2) 0%, rgba(167, 139, 250, 0.08) 100%);
border: 1px solid rgba(167, 139, 250, 0.3);
box-shadow: 0 8px 24px rgba(167, 139, 250, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1);
flex-shrink: 0;
color: #a78bfa;
font-size: 52px;
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.22), rgba(var(--accent-rgb), 0.06));
color: rgb(var(--accent-light-rgb));
}
.mirrored-modal-hero-icon.spotify { background: linear-gradient(135deg, rgba(29, 185, 84, 0.2) 0%, rgba(29, 185, 84, 0.08) 100%); border-color: rgba(29, 185, 84, 0.3); box-shadow: 0 8px 24px rgba(29, 185, 84, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1); color: #1db954; }
.mirrored-modal-hero-icon.tidal { background: linear-gradient(135deg, rgba(255, 102, 0, 0.2) 0%, rgba(255, 102, 0, 0.08) 100%); border-color: rgba(255, 102, 0, 0.3); box-shadow: 0 8px 24px rgba(255, 102, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1); color: #ff6600; }
.mirrored-modal-hero-icon.youtube { background: linear-gradient(135deg, rgba(255, 0, 0, 0.2) 0%, rgba(255, 0, 0, 0.08) 100%); border-color: rgba(255, 0, 0, 0.3); box-shadow: 0 8px 24px rgba(255, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1); color: #ff4444; }
.mirrored-modal-hero-icon.beatport { background: linear-gradient(135deg, rgba(1, 255, 149, 0.2) 0%, rgba(1, 255, 149, 0.08) 100%); border-color: rgba(1, 255, 149, 0.3); box-shadow: 0 8px 24px rgba(1, 255, 149, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1); color: #01ff95; }
.mm-cover-empty.spotify,
.mm-cover-empty.spotify_public { background: linear-gradient(135deg, rgba(29, 185, 84, 0.25), rgba(29, 185, 84, 0.06)); color: #1db954; }
.mm-cover-empty.tidal { background: linear-gradient(135deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.04)); color: #fff; }
.mm-cover-empty.youtube { background: linear-gradient(135deg, rgba(255, 0, 0, 0.22), rgba(255, 0, 0, 0.06)); color: #ff4d4d; }
.mirrored-modal-hero-info {
.mm-hero-info {
flex: 1;
min-width: 0;
}
.mirrored-modal-hero-title {
color: #ffffff;
font-size: 22px;
.mm-eyebrow {
display: block;
text-transform: uppercase;
letter-spacing: 1.2px;
font-size: 11px;
font-weight: 700;
color: rgba(255, 255, 255, 0.5);
margin-bottom: 8px;
}
.mm-title {
margin: 0;
line-height: 1.3;
color: #fff;
font-weight: 800;
font-size: clamp(22px, 3vw, 34px);
line-height: 1.12;
letter-spacing: -0.5px;
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.mirrored-modal-hero-subtitle {
color: #b3b3b3;
font-size: 14px;
margin-top: 6px;
.mm-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
gap: 8px;
margin-top: 13px;
font-size: 13px;
color: rgba(255, 255, 255, 0.62);
}
.mirrored-modal-hero-badge {
.mm-dot { color: rgba(255, 255, 255, 0.3); }
.mm-source-pill {
font-size: 11px;
font-weight: 600;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 3px 10px;
border-radius: 6px;
border-radius: 20px;
color: rgb(var(--accent-light-rgb));
background: rgba(var(--accent-rgb), 0.1);
border: 1px solid rgba(var(--accent-rgb), 0.2);
background: rgba(var(--accent-rgb), 0.14);
border: 1px solid rgba(var(--accent-rgb), 0.25);
}
.mirrored-modal-close {
.mm-source-pill.spotify,
.mm-source-pill.spotify_public { color: #1db954; background: rgba(29, 185, 84, 0.14); border-color: rgba(29, 185, 84, 0.3); }
.mm-close {
position: absolute;
top: 24px;
right: 24px;
z-index: 3;
color: #b3b3b3;
font-size: 18px;
font-weight: 300;
cursor: pointer;
transition: all 0.2s ease;
width: 32px;
height: 32px;
top: 18px;
right: 18px;
z-index: 2;
width: 34px;
height: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
font-size: 22px;
line-height: 1;
cursor: pointer;
color: rgba(255, 255, 255, 0.7);
background: rgba(0, 0, 0, 0.35);
border: 1px solid rgba(255, 255, 255, 0.12);
transition: all 0.18s ease;
}
.mirrored-modal-close:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.15);
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
.mm-close:hover {
color: #fff;
background: rgba(0, 0, 0, 0.55);
transform: scale(1.08);
}
.mirrored-modal-tracks {
flex: 1;
/* ---------- Track list ---------- */
.mm-list {
flex: 1 1 auto;
min-height: 0; /* clip fix: list scrolls instead of growing the modal */
overflow-y: auto;
padding: 0;
background: #121212;
padding: 6px 0 10px;
}
.mirrored-modal-tracks::-webkit-scrollbar { width: 8px; }
.mirrored-modal-tracks::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.05); }
.mirrored-modal-tracks::-webkit-scrollbar-thumb { background: rgba(var(--accent-rgb), 0.3); border-radius: 4px; }
.mirrored-modal-tracks::-webkit-scrollbar-thumb:hover { background: rgba(var(--accent-rgb), 0.5); }
.mm-list::-webkit-scrollbar { width: 10px; }
.mm-list::-webkit-scrollbar-track { background: transparent; }
.mm-list::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.14); border-radius: 5px; border: 2px solid #121212; }
.mm-list::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.24); }
.mirrored-track-header {
.mm-list-head,
.mm-row {
display: grid;
grid-template-columns: 40px 1.6fr 1.4fr 1fr 56px;
grid-template-columns: 34px 40px minmax(0, 1.6fr) minmax(0, 1.1fr) 56px;
align-items: center;
padding: 10px 28px;
font-size: 11px;
font-weight: 600;
color: #666;
text-transform: uppercase;
letter-spacing: 0.8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
gap: 14px;
padding: 0 26px;
}
.mm-list-head {
height: 34px;
position: sticky;
top: 0;
background: #121212;
z-index: 1;
background: #121212;
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: rgba(255, 255, 255, 0.4);
}
.mirrored-track-row {
display: grid;
grid-template-columns: 40px 1.6fr 1.4fr 1fr 56px;
align-items: center;
padding: 11px 28px;
.mm-col-dur { text-align: right; }
.mm-row {
height: 56px;
margin: 1px 8px;
padding: 0 18px;
border-radius: 8px;
transition: background 0.15s ease;
}
.mm-row:hover { background: rgba(255, 255, 255, 0.06); }
.mm-row-pos {
color: rgba(255, 255, 255, 0.4);
font-size: 13px;
color: #b3b3b3;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}
.mirrored-track-row:hover {
background: linear-gradient(90deg, rgba(var(--accent-rgb), 0.06) 0%, rgba(var(--accent-rgb), 0.02) 100%);
transform: translateX(2px);
}
.mirrored-track-row .track-pos {
color: #555;
font-variant-numeric: tabular-nums;
text-align: center;
font-size: 12px;
font-weight: 500;
}
.mirrored-track-row:hover .track-pos {
color: rgb(var(--accent-light-rgb));
.mm-row-art {
width: 40px;
height: 40px;
border-radius: 5px;
object-fit: cover;
background: #242424;
flex-shrink: 0;
}
.mirrored-track-row .track-title {
.mm-art-empty {
background: linear-gradient(135deg, #2c2c2c, #1b1b1b);
display: inline-block;
}
.mm-row-main {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.mm-row-title {
color: #fff;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 12px;
}
.mirrored-track-row .track-artist {
width: auto;
.mm-row-artist {
color: rgba(255, 255, 255, 0.55);
font-size: 12.5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 12px;
}
.mirrored-track-row .track-album {
.mm-row-album {
color: rgba(255, 255, 255, 0.45);
font-size: 13px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 12px;
color: #777;
}
.mirrored-track-row .track-duration {
.mm-row-dur {
text-align: right;
color: #666;
font-size: 12px;
color: rgba(255, 255, 255, 0.4);
font-size: 13px;
font-variant-numeric: tabular-nums;
}
.mirrored-modal-footer {
background: linear-gradient(135deg, rgba(42, 42, 42, 0.9) 0%, rgba(30, 30, 30, 0.95) 100%);
border-top: 1px solid rgba(255, 255, 255, 0.08);
padding: 18px 32px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
.mm-empty {
padding: 48px;
text-align: center;
color: rgba(255, 255, 255, 0.4);
}
.mirrored-modal-footer button {
padding: 10px 20px;
border-radius: 10px;
/* ---------- Action bar ---------- */
.mm-actions {
flex-shrink: 0; /* footer is pinned, never clipped */
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 26px;
background: #181818;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.mm-actions-right {
display: flex;
align-items: center;
gap: 10px;
}
.mm-btn {
padding: 9px 18px;
border-radius: 9px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
border: none;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
min-width: 100px;
border: 1px solid transparent;
transition: all 0.18s ease;
white-space: nowrap;
}
.mirrored-btn-close {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.15) !important;
}
.mirrored-btn-close:hover {
background: rgba(255, 255, 255, 0.18);
color: #ffffff;
border-color: rgba(255, 255, 255, 0.25) !important;
transform: translateY(-1px);
}
.mirrored-btn-discover {
background: linear-gradient(135deg, rgb(var(--accent-rgb)) 0%, rgb(var(--accent-light-rgb)) 100%);
.mm-btn-primary {
background: rgb(var(--accent-rgb));
color: #000;
box-shadow: 0 4px 16px rgba(var(--accent-rgb), 0.3);
border: 1px solid rgba(var(--accent-rgb), 0.3) !important;
box-shadow: 0 4px 16px rgba(var(--accent-rgb), 0.28);
}
.mm-btn-primary:hover { transform: translateY(-1px); box-shadow: 0 6px 22px rgba(var(--accent-rgb), 0.42); }
.mirrored-btn-discover:hover {
transform: translateY(-1px);
box-shadow: 0 8px 24px rgba(var(--accent-rgb), 0.4);
}
.mirrored-btn-pipeline {
.mm-btn-secondary {
background: rgba(56, 189, 248, 0.12);
color: #7dd3fc;
border: 1px solid rgba(56, 189, 248, 0.28) !important;
border-color: rgba(56, 189, 248, 0.28);
}
.mm-btn-secondary:hover { background: rgba(56, 189, 248, 0.2); color: #e0f2fe; border-color: rgba(56, 189, 248, 0.45); }
.mirrored-btn-pipeline:hover {
background: rgba(56, 189, 248, 0.2);
border-color: rgba(56, 189, 248, 0.45) !important;
color: #e0f2fe;
transform: translateY(-1px);
.mm-btn-ghost {
background: rgba(255, 255, 255, 0.07);
color: rgba(255, 255, 255, 0.8);
border-color: rgba(255, 255, 255, 0.12);
}
.mm-btn-ghost:hover { background: rgba(255, 255, 255, 0.13); color: #fff; }
.mirrored-btn-delete {
background: rgba(244, 67, 54, 0.12);
.mm-btn-danger {
background: transparent;
color: #ef5350;
border: 1px solid rgba(244, 67, 54, 0.2) !important;
border-color: rgba(239, 83, 80, 0.35);
}
.mm-btn-danger:hover { background: rgba(239, 83, 80, 0.14); color: #ff6b66; border-color: rgba(239, 83, 80, 0.55); }
.mirrored-btn-delete:hover {
background: rgba(244, 67, 54, 0.2);
color: #ff6659;
border-color: rgba(244, 67, 54, 0.35) !important;
transform: translateY(-1px);
@media (max-width: 640px) {
.mm-hero-content { flex-direction: column; align-items: flex-start; gap: 14px; }
.mm-cover { width: 92px; height: 92px; }
.mm-actions { flex-direction: column; align-items: stretch; }
.mm-actions-right { justify-content: space-between; }
.mm-btn { flex: 1; text-align: center; }
}
.sync-tab-content {