diff --git a/public/player.js b/public/player.js
index 1dd51d6..01a46c7 100644
--- a/public/player.js
+++ b/public/player.js
@@ -136,7 +136,28 @@ function restorePlayer() {
pl.a.addEventListener('loadedmetadata', () => { if (st.pos) pl.a.currentTime = st.pos; }, { once: true });
renderMini(); syncUI();
}
+// On a wide screen the queue docks down the right side of the page and stays open while you browse; the Queue button turns it on and off.
+const qWide = () => typeof matchMedia === 'function' && matchMedia('(min-width: 1100px)').matches;
+function qDock() {
+ if (!qWide() && !document.body?.classList.contains('qdock')) return; // (narrow screens use the sheet)
+ let el = document.getElementById('qdock'), on = store.get('q_dock', false) && qWide() && pl.q.length && !$('#player').hidden;
+ if (!on) { if (el) el.remove(); document.body.classList.remove('qdock'); return; }
+ if (!el) { el = document.createElement('aside'); el.id = 'qdock'; el.setAttribute('aria-label', 'Up next'); $('#main').appendChild(el); }
+ document.body.classList.add('qdock'); el.style.bottom = ($('#player').offsetHeight || 0) + 'px';
+ const keep = el.querySelector('.qlist')?.scrollTop || 0;
+ el.innerHTML = `
Up next${pl.q.length} tracks${pl.radio ? ' · radio' : ''}
+ ${pl.q.map((t, i) => `
${i === pl.i ? '' : ``}
`).join('')}
+ `;
+ const list = el.querySelector('.qlist'); list.scrollTop = keep || Math.max(0, (list.querySelector('.now')?.offsetTop || 0) - 60);
+ el.querySelector('#qdx').onclick = () => { store.set('q_dock', false); qDock(); };
+ el.querySelectorAll('[data-j]').forEach(b => b.onclick = () => { pl.i = +b.dataset.j; loadCur(0); });
+ el.querySelectorAll('[data-x]').forEach(b => b.onclick = () => { const k = +b.dataset.x; pl.q.splice(k, 1); if (k < pl.i) pl.i--; saveState(); tcKick(); updateNow(); });
+ el.querySelector('#qdc').onclick = () => { pl.q.length = pl.i + 1; if (pl.orig) pl.orig = null, pl.shuffle = false; saveState(); tcKick(); updateNow(); };
+ el.querySelector('#qds').onclick = () => addToPlaylist(pl.q.slice(), 'Queue ' + new Date().toLocaleDateString([], { month: 'short', day: 'numeric' }));
+}
+if (typeof matchMedia === 'function') matchMedia('(min-width: 1100px)').addEventListener('change', () => qDock());
function openQueue() {
+ if (qWide()) { store.set('q_dock', !store.get('q_dock', false)); return qDock(); }
const upcoming = pl.q.map((t, i) => ({ t, i }));
const el = openSheet('Up next', pl.q.length + ' tracks' + (pl.radio ? ' · library radio is on' : ''), `${upcoming.map(({ t, i }) => `
@@ -204,8 +225,8 @@ function markPlaying() {
}
function renderMini() {
if (typeof videoBarOn === 'function' && videoBarOn()) return; // the bottom bar is showing the video's controls
- const t = cur(), el = $('#player'); if (!t) return (el.hidden = true);
- el.hidden = false;
+ const t = cur(), el = $('#player'); if (!t) { el.hidden = true; return qDock(); }
+ el.hidden = false; setTimeout(qDock);
el.innerHTML = `
@@ -287,6 +308,7 @@ function renderNpCard() {
}
// The Now Playing sheet stays open while tracks change, so refresh what it shows.
function updateNow() {
+ qDock();
const t = cur(), sk = $('#npk'); if (!t || !sk) return;
$('#npti').textContent = t.title; $('#npart').src = px.art(t.thumb, 600);
const q = $('#npq span'); if (q) q.textContent = 'Queue · ' + (pl.q.length - pl.i - 1);
diff --git a/public/style.css b/public/style.css
index 83fa39f..cff729e 100644
--- a/public/style.css
+++ b/public/style.css
@@ -1177,3 +1177,14 @@ body.vfullwin:has(.vstage.vhide) .vstage.vfull { bottom: 0; } body.vfullwin:has(
.mini .bar2:active i::after { transform: scale(1.35); }
.mini .bar2::before { top: -14px; bottom: -12px; }
.mini .bar2[hidden] i::after { display: none; }
+
+/* the queue docked down the right side on wide screens */
+#qdock { position: fixed; right: 0; top: 0; width: 340px; display: flex; flex-direction: column; background: var(--panel); border-left: 1px solid var(--line); z-index: 40; }
+body.qdock #view { padding-right: 340px; }
+#qdock .qhead { display: flex; align-items: center; gap: 8px; padding: 14px 8px 10px 16px; } #qdock .qhead b { font-size: 16px; } #qdock .qhead small { color: var(--muted); flex: 1; }
+#qdock .qlist { flex: 1; overflow-y: auto; padding: 0 8px; }
+#qdock .qrow { display: flex; align-items: center; border-radius: 10px; } #qdock .qrow:hover { background: var(--panel2); } #qdock .qrow.now { background: var(--panel2); }
+#qdock .qmain { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; padding: 8px 6px; background: none; border: 0; color: inherit; font: inherit; text-align: left; cursor: pointer; }
+#qdock .num { width: 22px; text-align: center; color: var(--muted); font-size: 13px; flex: none; } #qdock .qrow.now .num, #qdock .qrow.now b { color: var(--accent); }
+#qdock .grow { min-width: 0; } #qdock b, #qdock small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #qdock .qmain small { color: var(--muted); font-size: 12px; }
+#qdock .qfoot { display: flex; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--line); } #qdock .qfoot .btn { flex: 1; justify-content: center; }