Bring Glass Stage closer to the mockup: HUD chips, sleep card, chapter badge, A-B group, playlist banner and P2P strip

This commit is contained in:
Claude
2026-10-01 21:45:44 +00:00
parent 19973c75c0
commit 6bf2636864
3 changed files with 191 additions and 4 deletions

View File

@@ -2758,7 +2758,7 @@ function fitLyricLines(list, { maxShare = 0.17, maxPx = 160, minPx = 18, uniform
const Notes = (() => {
const LC = window.LyricsCore;
const panel = $('notesPanel');
const noop = { load() {}, tick() {}, addBookmark() {}, handleSyncKey() { return false; }, serviceRefresh() {} };
const noop = { load() {}, tick() {}, addBookmark() {}, handleSyncKey() { return false; }, serviceRefresh() {}, chapterAt() { return null; } };
if (!panel || !LC) return noop;
const CACHE_KEY = 'ytpNotesCache';
@@ -3897,6 +3897,7 @@ const Notes = (() => {
return {
load, tick, addBookmark, addNote, handleSyncKey, serviceRefresh, refreshFlags: () => { sv.flagsKey = ''; renderServiceButtons(); setPanelFlagMode(sv.panelFlag && flagsEnabled()); paintFlags(); loadFlags(); },
stageLyrics: { render: renderStage, set: setStageLyrics, on: stageOn },
chapterAt: (t) => { const c = chapters().slice().sort((a, b) => a.t - b.t); let k = -1; c.forEach((x, i) => { if (x.t <= t) k = i; }); return k < 0 ? null : { n: k + 1, of: c.length, title: c[k].title || '' }; },
};
})();
@@ -3905,6 +3906,7 @@ const Notes = (() => {
const skip = (d) => { try { Player.seek(Math.max(0, Player.master.currentTime + d)); } catch { /* not ready */ } };
const on = (id, fn) => { const el = document.getElementById(id); if (el) el.addEventListener('click', fn); };
on('back10Btn', () => skip(-10)); on('fwd10Btn', () => skip(10));
on('upnextClear', () => clearQueue());
on('stgBack', () => skip(-10)); on('stgFwd', () => skip(10));
on('stgPlay', () => $('playBtn').click());
const setAudio = (v) => { const t = $('audioOnlyToggle'); if (t && t.checked !== v) { t.checked = v; t.dispatchEvent(new Event('change', { bubbles: true })); } };
@@ -3932,6 +3934,19 @@ const Notes = (() => {
box.innerHTML = list.map(([t, a]) => `<button type="button" class="dchip${a ? ' on' : ''}"></button>`).join('');
[...box.children].forEach((b, i) => { b.textContent = list[i][0]; b.onclick = openMoreControls; });
}
const ch = $('chBadge');
if (ch) {
const c = Notes.chapterAt ? Notes.chapterAt((Player.master && Player.master.currentTime) || 0) : null;
ch.hidden = !c;
if (c) ch.textContent = `CH ${String(c.n).padStart(2, '0')}: ${String(c.title).toUpperCase().slice(0, 18)}`;
}
const V = $('video');
const zoom = $('stgZoom'), buf = $('stgBuf');
if (zoom && V) { const m = /scale\(([\d.]+)\)/.exec(V.style.transform || ''); zoom.textContent = 'Pinch ' + (m ? Number(m[1]).toFixed(1) : '1.0') + '×'; }
if (buf && V && V.duration && V.buffered.length) {
const end = V.buffered.end(V.buffered.length - 1);
buf.textContent = end >= V.duration - 1 ? 'Fully buffered' : 'Buffered ' + Math.round((end / V.duration) * 100) + '%';
}
const q = $('qualitySelect'), sq = $('stgQuality');
if (sq && q && q.options.length) sq.textContent = q.options[q.selectedIndex] ? q.options[q.selectedIndex].text : 'Auto';
const ao = !!data.settings.audioOnly;
@@ -7385,6 +7400,7 @@ function setListPaneHidden(hidden) {
}
function renderList() {
{ const ls = $('listSub'); if (ls) ls.textContent = ''; const ps = $('p2pStrip'); if (ps) ps.classList.add('hidden'); }
setListPaneHidden(view.type === 'search' && !searchResults.length && !searchAttempted);
if (view.type === 'settings') { renderSettings(); return; }
if (view.type === 'saved') { renderSaved(); return; }
@@ -7460,6 +7476,10 @@ function renderList() {
const pl = data.playlists.find((p) => p.id === view.id);
els.listTitle.textContent = pl ? pl.name : 'Playlist';
if (pl) {
{ const secs = (pl.videos || []).reduce((a, v) => a + (Number(v.duration) || 0), 0);
const h = Math.floor(secs / 3600), m = Math.round((secs % 3600) / 60);
$('listSub').textContent = `${pl.videos.length} track${pl.videos.length === 1 ? '' : 's'}${secs ? ' · ' + (h ? h + 'h ' : '') + m + 'm' : ''}`; }
renderP2pStrip();
const playAll = document.createElement('button');
playAll.textContent = '▶ Play all';
playAll.className = 'la-primary';
@@ -7467,6 +7487,7 @@ function renderList() {
const playFull = document.createElement('button');
playFull.textContent = '▶ Play in full';
playFull.className = 'la-main';
playFull.dataset.sub = 'Start to finish';
playFull.title = 'Play every video start to finish — ignores saved positions and A-B loops, stops after the last one';
playFull.onclick = () => {
if (!pl.videos.length) return;
@@ -7476,6 +7497,7 @@ function renderList() {
const serviceBtn = document.createElement('button');
serviceBtn.textContent = '🎤 Service mode';
serviceBtn.className = 'la-main';
serviceBtn.dataset.sub = 'Full lyrics';
serviceBtn.title = 'Set-list view with screen wake lock';
serviceBtn.onclick = () => {
if (!pl.videos || !pl.videos.length) { toast('Playlist is empty'); return; }
@@ -7484,6 +7506,7 @@ function renderList() {
const queueAll = document.createElement('button');
queueAll.textContent = '+ Queue';
queueAll.className = 'la-main';
queueAll.dataset.sub = `+${pl.videos.length} items`;
queueAll.onclick = () => { pl.videos.forEach((v) => addToQueue(v, { quiet: true })); toast('Added playlist to queue'); };
const rename = document.createElement('button');
rename.textContent = 'Rename';
@@ -7714,6 +7737,36 @@ async function exportToDevice(video) {
// ============================================================================
// "On other devices" — videos held by sharing devices (P2P), as a playlist
// ============================================================================
// Glass Stage: a compact "On other devices" card under a playlist (same data as the 📡 view).
let p2pStripAt = 0, p2pStripData = null;
async function renderP2pStrip() {
const box = $('p2pStrip');
if (!box || document.documentElement.dataset.layout !== 'glass-stage' || !WEB || data.settings.p2pReceive === false) return;
if (!p2pStripData || Date.now() - p2pStripAt > 60000) {
try { const r = await fetch('/api/p2p/available?limit=3'); p2pStripData = r.ok ? await r.json() : null; } catch { p2pStripData = null; }
p2pStripAt = Date.now();
}
if (view.type !== 'playlist') return;
const items = (p2pStripData && p2pStripData.ok && p2pStripData.items) || [];
if (!items.length) { box.classList.add('hidden'); return; }
const online = items.filter((i) => i.online).length;
box.innerHTML = `<div class="ps-head"><span>On other devices</span><i class="ps-dot${online ? ' on' : ''}"></i></div>
<p class="ps-sub">${items.length} video${items.length === 1 ? '' : 's'} held by other devices · ${online ? online + ' online now' : 'none online'}</p>`;
items.forEach((it) => {
const v = { id: it.id, title: it.title || videoTitleById(it.id) || it.id, channel: it.channel || '', duration: it.duration || 0, thumbnail: `https://i.ytimg.com/vi/${it.id}/mqdefault.jpg` };
const row = document.createElement('div');
row.className = 'ps-row';
row.innerHTML = '<span class="ps-ic">📡</span><span class="ps-txt"><b></b><small></small></span><button type="button" class="ps-dl" aria-label="Save to this device">⤓</button>';
row.querySelector('b').textContent = v.title;
row.querySelector('small').textContent = `${fmtBytes(it.size)}${it.height ? ' · ' + it.height + 'p' : ''} · ${it.online ? 'Online' : 'Offline'}`;
row.querySelector('.ps-dl').onclick = () => exportToDevice(v);
row.querySelector('.ps-txt').onclick = () => playFromList([v], 0, 'p2p');
box.appendChild(row);
});
box.classList.remove('hidden');
}
async function renderP2pList() {
els.listTitle.textContent = '📡 On other devices';
els.listActions.innerHTML = '';
@@ -10008,8 +10061,8 @@ function updateAbUI() {
const aSet = abA !== null, bSet = abB !== null;
const aBtn = $('abABtn'), bBtn = $('abBBtn'), clrBtn = $('abClearBtn');
const ind = $('abIndicator');
if (aBtn) aBtn.classList.toggle('active', aSet);
if (bBtn) bBtn.classList.toggle('active', bSet);
if (aBtn) { aBtn.classList.toggle('active', aSet); if (aSet) aBtn.dataset.t = fmtTime(abA); else delete aBtn.dataset.t; }
if (bBtn) { bBtn.classList.toggle('active', bSet); if (bSet) bBtn.dataset.t = fmtTime(abB); else delete bBtn.dataset.t; }
if (clrBtn) clrBtn.classList.toggle('hidden', !(aSet && bSet));
if (ind) ind.classList.toggle('hidden', !aSet && !bSet);
const q = document.querySelector('.dk-quick');

View File

@@ -164,6 +164,7 @@
<button id="stgPlay" class="stg-play" type="button" aria-label="Play or pause"><svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><path d="M9 5.5v13l10-6.5z"/></svg></button>
<button id="stgFwd" class="stg-skip" type="button" aria-label="Forward 10 seconds"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 12a8 8 0 1 1-3-6.2M20 4v4.5h-4.5"/><text x="12" y="15.5" text-anchor="middle" font-size="7.5" font-weight="700" fill="currentColor" stroke="none">10</text></svg></button>
</div>
<div class="stg-bot"><span class="stg-chip"><b id="stgZoom">Pinch 1.0×</b></span><span class="stg-chip"><i class="stg-dot"></i><b id="stgBuf">Buffered</b></span></div>
</div>
</div>
@@ -183,6 +184,7 @@
</div>
</div>
<span id="durTime" class="time">0:00</span>
<span id="chBadge" class="ch-badge" hidden></span>
<button id="fsMini" class="fs-mini" type="button" title="Fullscreen" aria-label="Fullscreen"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5"/></svg></button>
</div>
<button id="waveLoud" class="wave-loud hidden" type="button">⚡ Jump to the loudest part</button>
@@ -235,7 +237,8 @@
</div>
<div id="sleepStatus" class="sleep-status hidden">
<span id="sleepCountdown" class="sleep-countdown"></span>
<span class="sl-ic" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M20 14.5A8.5 8.5 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5z"/></svg></span>
<span class="sl-txt"><span id="sleepCountdown" class="sleep-countdown"></span><small class="sl-sub">Audio fades out smoothly</small></span>
<button id="sleepCancelBtn" class="sleep-cancel">✕ Cancel</button>
</div>
@@ -335,6 +338,7 @@
<div class="upnext-header">
<span>Up next</span>
<span id="upnextCount" class="upnext-count">0</span>
<button id="upnextClear" class="upnext-clear" type="button">Clear</button>
</div>
<div id="upnextList" class="upnext-list"></div>
</div>
@@ -353,6 +357,7 @@
<section id="listPane" class="list-pane">
<div class="list-header">
<h2 id="listTitle">Search</h2>
<p id="listSub" class="list-sub"></p>
<div id="listActions" class="list-actions"></div>
</div>
<div id="listFilterBar" class="list-filter-bar hidden">
@@ -367,6 +372,7 @@
</div>
<div id="status" class="status hidden"></div>
<div id="cards" class="cards"></div>
<div id="p2pStrip" class="p2p-strip hidden"></div>
</section>
</div>
</main>

View File

@@ -207,3 +207,131 @@ html[data-layout="glass-stage"] #controls .deck-main #nextBtn { order: 5; }
html[data-layout="glass-stage"] #nowPlayingMeta .np-actions .np-more-host .np-btn { flex: 0 0 auto !important; width: auto !important; min-width: 0 !important; height: 34px !important; }
html[data-layout="glass-stage"] .stg-overlay { justify-content: flex-start; }
html[data-layout="glass-stage"] .stg-mid { margin: auto 0; }
/* =========================== fidelity pass (matches the approved mockup) =========================== */
.stg-bot, .ch-badge, .upnext-clear, .list-sub, .p2p-strip { display: none; }
.sleep-status .sl-ic, .sleep-status .sl-sub { display: none; }
/* stage HUD */
html[data-layout="glass-stage"] .stg-bot { display: flex; position: absolute; left: 12px; right: 12px; bottom: 12px; justify-content: space-between; pointer-events: none; }
html[data-layout="glass-stage"] .stg-bot .stg-chip { height: 26px; font: 500 10px var(--mono); text-transform: none; letter-spacing: 0; color: var(--text-2); }
html[data-layout="glass-stage"] .stg-overlay { justify-content: flex-start; }
html[data-layout="glass-stage"] .player-pane.empty .stg-bot { display: none; }
/* sleep timer card */
html[data-layout="glass-stage"] .sleep-status {
display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-radius: 18px; margin-top: 12px;
background: rgba(13, 14, 18, 0.6); border: 1px solid var(--glass-line); -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);
}
html[data-layout="glass-stage"] .sleep-status.hidden { display: none; }
html[data-layout="glass-stage"] .sleep-status .sl-ic { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 12px; background: rgba(255, 85, 60, 0.12); border: 1px solid rgba(255, 85, 60, 0.25); color: var(--accent-bright); flex: none; }
html[data-layout="glass-stage"] .sleep-status .sl-ic svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
html[data-layout="glass-stage"] .sleep-status .sl-txt { flex: 1; min-width: 0; display: grid; }
html[data-layout="glass-stage"] .sleep-status .sleep-countdown { font: 600 12px var(--ui); color: var(--text); }
html[data-layout="glass-stage"] .sleep-status .sl-sub { display: block; font: 400 11px var(--ui); color: var(--text-dim); }
html[data-layout="glass-stage"] .sleep-cancel { height: 30px; padding: 0 14px; border-radius: 99px; background: rgba(255, 255, 255, 0.06); border: 1px solid var(--glass-line); color: var(--text-2); font: 600 11px var(--ui); }
/* seek row: thicker gradient track, chapter badge */
html[data-layout="glass-stage"] #controls .seek { height: 6px; }
html[data-layout="glass-stage"] #controls .ch-badge { display: inline-block; padding: 3px 10px; border-radius: 99px; background: rgba(255, 255, 255, 0.06); border: 1px solid var(--glass-line); color: var(--accent-bright); font: 600 10px var(--mono); letter-spacing: 0.04em; white-space: nowrap; }
html[data-layout="glass-stage"] #controls .ch-badge[hidden] { display: none; }
html[data-layout="glass-stage"] #controls #waveLoud {
width: 100%; padding: 8px 14px; border-radius: 14px; text-align: left; font: 500 12px var(--ui); color: var(--accent-bright);
background: linear-gradient(90deg, rgba(255, 85, 60, 0.16), transparent); border: 1px solid rgba(255, 85, 60, 0.22);
}
/* quick row: A-B group · Fullscreen · More */
html[data-layout="glass-stage"] #controls .dk-quick { flex-wrap: nowrap; }
html[data-layout="glass-stage"] #controls .dk-quick #abABtn,
html[data-layout="glass-stage"] #controls .dk-quick #abBBtn,
html[data-layout="glass-stage"] #controls .dk-quick #abClearBtn:not(.hidden) {
display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: auto; height: 40px; padding: 0 10px; border-radius: 0; border: 0; background: transparent;
font: 600 11px var(--mono); color: var(--text-dim);
}
html[data-layout="glass-stage"] #controls .dk-quick #abABtn { order: 1; border-radius: 14px 0 0 14px; border: 1px solid var(--glass-line); border-right: 0; background: var(--tile); }
html[data-layout="glass-stage"] #controls .dk-quick #abBBtn { order: 2; border-top: 1px solid var(--glass-line); border-bottom: 1px solid var(--glass-line); background: var(--tile); }
html[data-layout="glass-stage"] #controls .dk-quick #abClearBtn { order: 3; border-radius: 0 14px 14px 0; border: 1px solid var(--glass-line); border-left: 0; background: var(--tile); }
html[data-layout="glass-stage"] #controls .dk-quick #abClearBtn.hidden { display: none; }
html[data-layout="glass-stage"] #controls .dk-quick #abABtn::after, html[data-layout="glass-stage"] #controls .dk-quick #abBBtn::after { content: attr(data-t); margin-left: 4px; }
html[data-layout="glass-stage"] #controls .dk-quick #abABtn[data-t]::after, html[data-layout="glass-stage"] #controls .dk-quick #abBBtn[data-t]::after { content: "[" attr(data-t) "]"; }
html[data-layout="glass-stage"] #controls .dk-quick .ctrl.active { color: var(--accent-bright); background: rgba(255, 255, 255, 0.1); }
html[data-layout="glass-stage"] #controls .dk-quick #fsBtn { order: 4; width: auto; padding: 0 12px; margin-left: auto; }
html[data-layout="glass-stage"] #controls .dk-quick.ab-on #fsBtn { width: auto; padding: 0 12px; }
html[data-layout="glass-stage"] #controls .dk-quick.ab-on .fs-label { display: inline; }
html[data-layout="glass-stage"] #controls .dk-quick #ctlMoreBtn { order: 5; flex: 0 0 auto; padding: 0 12px; }
html[data-layout="glass-stage"] #controls .dk-quick.ab-on .mc-label { display: inline; }
html[data-layout="glass-stage"] #controls .dk-more .mc-rest { display: none; }
html[data-layout="glass-stage"] #controls .dk-quick .dk-more span { font: 600 11px var(--ui); }
html[data-layout="glass-stage"] #controls .deck-vol #modeBtn::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 6px var(--accent); flex: none; order: 3; }
html[data-layout="glass-stage"] #controls .deck-vol #modeBtn { justify-content: flex-start; }
html[data-layout="glass-stage"] #controls .deck-vol #modeBtn svg { margin-left: auto; order: -1; color: var(--text-dim); }
html[data-layout="glass-stage"] #controls .deck-vol #modeBtn .mode-name { order: 4; }
/* now playing: verified-style channel line */
html[data-layout="glass-stage"] #nowPlayingMeta .np-channel { font-weight: 500; }
/* lyrics: compact single-row toolbar, bracketed section labels */
html[data-layout="glass-stage"] #lyricsPane .lyrics-toolbar { display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 6px; scrollbar-width: none; }
html[data-layout="glass-stage"] #lyricsPane .lyrics-toolbar > * { flex: none; }
html[data-layout="glass-stage"] #lyricsPane .lyrics-toolbar .np-btn { height: 30px; padding: 0 10px; font-size: 11px; border-radius: 10px; }
html[data-layout="glass-stage"] #lyricsPane .lyrics-toolbar .lyr-offset { height: 30px; border-radius: 10px; padding: 0 4px; }
html[data-layout="glass-stage"] #lyricsPane .lyr-off-btn { height: 24px; padding: 0 6px; background: transparent; color: var(--accent-bright); font-size: 11px; }
html[data-layout="glass-stage"] #lyricsPane .lyr-off-val { font-size: 11px; flex: none; }
html[data-layout="glass-stage"] #lyricsPane .lyr-auto { height: 30px; border: 0; background: transparent; grid-column: auto; }
html[data-layout="glass-stage"] #lyricsPane .lyr-flag { grid-column: auto; height: 30px; padding: 0 10px; font-size: 11px; border-radius: 10px; white-space: nowrap; }
html[data-layout="glass-stage"] #lyricsPane .lyr-flag span { font-size: 11px; }
html[data-layout="glass-stage"] .lyr-section::before { content: "[ "; }
html[data-layout="glass-stage"] .lyr-section::after { content: " ]"; }
/* up next */
html[data-layout="glass-stage"] .upnext { padding: 16px; }
html[data-layout="glass-stage"] .upnext-header { display: flex; align-items: center; gap: 8px; font: 700 16px var(--display); color: var(--text); }
html[data-layout="glass-stage"] .upnext-count { background: rgba(255, 255, 255, 0.06); border: 1px solid var(--glass-line); color: var(--accent-bright); font: 600 10px var(--mono); border-radius: 99px; padding: 2px 10px; }
html[data-layout="glass-stage"] .upnext-clear { display: inline; margin-left: auto; background: none; border: 0; color: var(--accent-bright); font: 500 12px var(--ui); cursor: pointer; }
html[data-layout="glass-stage"] .upnext-item { background: var(--tile); border: 1px solid var(--glass-line); border-radius: 18px; padding: 10px; }
html[data-layout="glass-stage"] .upnext-item img { border-radius: 12px; }
html[data-layout="glass-stage"] .related-panel { padding: 0; overflow: hidden; }
/* list header banner: eyebrow, title + Play all, three tiles with sub-labels */
html[data-layout="glass-stage"] .list-header { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; padding: 0 0 12px; align-items: start; }
html[data-layout="glass-stage"] .list-header::before { grid-column: 1 / -1; content: "Current list"; }
html[data-layout="glass-stage"] .list-header h2 { grid-column: 1 / 5; font: 700 20px var(--display); margin: 0; }
html[data-layout="glass-stage"] .list-sub { display: block; grid-column: 1 / 5; margin: -6px 0 0; font: 400 11px var(--ui); color: var(--text-dim); }
html[data-layout="glass-stage"] .list-sub:empty { display: none; }
html[data-layout="glass-stage"] #listActions { display: contents; }
html[data-layout="glass-stage"] #listActions button.la-primary { grid-column: 5 / -1; grid-row: 2 / span 2; align-self: start; flex: none; height: 44px; padding: 0 14px; border-radius: 16px; font-size: 13px; white-space: nowrap; }
html[data-layout="glass-stage"] #listActions button.la-primary.la-inline { grid-row: 2; }
html[data-layout="glass-stage"] #listActions button.la-main { grid-column: span 2; flex: none; height: auto; min-height: 56px; padding: 8px 4px; border-radius: 18px; flex-direction: column; gap: 2px; font: 600 11px var(--ui); }
html[data-layout="glass-stage"] #listActions button.la-main::after { content: attr(data-sub); font: 500 9px var(--mono); color: var(--text-dim); }
html[data-layout="glass-stage"] #listActions button:not(.la-primary):not(.la-main):not(.la-icon) { grid-column: span 2; flex: none; height: 34px; border-radius: 12px; font-size: 11px; padding: 0 6px; }
html[data-layout="glass-stage"] #listActions button.la-icon { grid-column: span 1; }
html[data-layout="glass-stage"] .list-filter-bar input { border-radius: 14px; background: var(--tile); border: 1px solid var(--glass-line); height: 38px; }
/* P2P strip under a playlist */
html[data-layout="glass-stage"] .p2p-strip { display: block; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--glass-line); }
html[data-layout="glass-stage"] .p2p-strip.hidden { display: none; }
html[data-layout="glass-stage"] .ps-head { display: flex; align-items: center; justify-content: space-between; font: 700 13px var(--ui); color: var(--text); }
html[data-layout="glass-stage"] .ps-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-dim); }
html[data-layout="glass-stage"] .ps-dot.on { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
html[data-layout="glass-stage"] .ps-sub { margin: 4px 0 10px; font: 400 11px var(--ui); color: var(--text-dim); }
html[data-layout="glass-stage"] .ps-row { display: flex; align-items: center; gap: 10px; padding: 10px 12px; margin-bottom: 8px; border-radius: 18px; background: var(--tile); border: 1px solid var(--glass-line); }
html[data-layout="glass-stage"] .ps-ic { width: 32px; height: 32px; border-radius: 12px; display: grid; place-items: center; background: rgba(255, 255, 255, 0.06); flex: none; font-size: 15px; }
html[data-layout="glass-stage"] .ps-txt { flex: 1; min-width: 0; display: grid; cursor: pointer; }
html[data-layout="glass-stage"] .ps-txt b { font: 500 12px var(--ui); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
html[data-layout="glass-stage"] .ps-txt small { font: 400 10px var(--mono); color: var(--text-dim); }
html[data-layout="glass-stage"] .ps-dl { width: 34px; height: 34px; border-radius: 12px; border: 0; background: rgba(255, 255, 255, 0.07); color: var(--accent-bright); font-size: 16px; cursor: pointer; }
html[data-layout="glass-stage"] #controls .dk-quick { align-items: center; }
html[data-layout="glass-stage"] #controls .dk-quick .ctrl, html[data-layout="glass-stage"] #controls .dk-quick .dk-more { height: 40px !important; box-shadow: none !important; white-space: nowrap; }
html[data-layout="glass-stage"] #controls .dk-quick #fsBtn { flex-direction: row; gap: 6px; }
html[data-layout="glass-stage"] #controls .dk-quick #fsBtn svg { width: 16px; height: 16px; }
html[data-layout="glass-stage"] #controls .dk-quick .fs-label { font: 600 11px var(--ui); }
html[data-layout="glass-stage"] #controls .dk-quick #abBBtn, html[data-layout="glass-stage"] #controls .dk-quick #abClearBtn { margin-left: -8px; }
html[data-layout="glass-stage"] #controls .dk-quick .ctrl.active { background: rgba(255, 255, 255, 0.12) !important; color: var(--accent-bright); }
html[data-layout="glass-stage"] #controls .dk-quick #fsBtn { min-width: 112px; padding: 0 12px; }
html[data-layout="glass-stage"] #controls .dk-quick #fsBtn svg { flex: none; display: block; }
html[data-layout="glass-stage"] #controls .dk-quick { gap: 6px; }
html[data-layout="glass-stage"] #controls .dk-quick #abABtn, html[data-layout="glass-stage"] #controls .dk-quick #abBBtn { padding: 0 7px !important; font-size: 10px; }
html[data-layout="glass-stage"] #controls .dk-quick #abClearBtn:not(.hidden) { padding: 0 8px !important; }
html[data-layout="glass-stage"] #controls .dk-quick #fsBtn { min-width: 0; margin-left: auto; padding: 0 10px; }
html[data-layout="glass-stage"] #controls .dk-quick #ctlMoreBtn { flex: 0 1 auto; min-width: 0; padding: 0 10px; }
html[data-layout="glass-stage"] #controls .dk-quick #fsBtn { width: auto !important; flex: 0 0 auto !important; }