diff --git a/frontend/app.js b/frontend/app.js
index c9a8f32..0ff11d3 100755
--- a/frontend/app.js
+++ b/frontend/app.js
@@ -6717,7 +6717,7 @@ function applyShuffle() {
queue = queue.slice(0, queueIndex + 1).concat(upcoming);
}
-function toggleShuffle() {
+function toggleShuffle({ quiet = false } = {}) {
data.settings.shuffle = !data.settings.shuffle;
if (data.settings.shuffle) {
applyShuffle();
@@ -6731,7 +6731,7 @@ function toggleShuffle() {
persist();
updateLoopRepeatButtons();
renderUpNext();
- toast(data.settings.shuffle ? 'Shuffle on' : 'Shuffle off');
+ if (!quiet) toast(data.settings.shuffle ? 'Shuffle on' : 'Shuffle off');
}
// ---------- Temporary queue ----------
@@ -6854,10 +6854,51 @@ function toggleRepeat() {
updateLoopRepeatButtons();
toast(data.settings.repeatMode === 'all' ? 'Repeating list' : 'Repeat off');
}
+// One button for shuffle / repeat list / loop one. Cycle:
+// off → repeat list → shuffle → shuffle + repeat → loop this video → off.
+const PLAY_MODES = [
+ { name: 'In order', sub: 'Plays the list once, then stops' },
+ { name: 'Repeat list', sub: 'Starts over when the list ends' },
+ { name: 'Shuffle', sub: 'Random order' },
+ { name: 'Shuffle + repeat', sub: 'Random order, forever' },
+ { name: 'Loop this video', sub: 'Plays the same video again and again' },
+];
+const MODE_ICONS = [
+ '',
+ '',
+ '',
+ '',
+ '',
+];
+function playModeIndex() {
+ const st = data.settings;
+ if (st.loopOne) return 4;
+ const rep = st.repeatMode === 'all';
+ return st.shuffle ? (rep ? 3 : 2) : (rep ? 1 : 0);
+}
+function cyclePlayMode() {
+ const next = (playModeIndex() + 1) % PLAY_MODES.length;
+ const wantShuffle = next === 2 || next === 3;
+ data.settings.loopOne = next === 4;
+ data.settings.repeatMode = next === 1 || next === 3 ? 'all' : 'off';
+ if (!!data.settings.shuffle !== wantShuffle) toggleShuffle({ quiet: true });
+ persist();
+ updateLoopRepeatButtons();
+ toast(PLAY_MODES[next].name, { duration: 1800 });
+}
function updateLoopRepeatButtons() {
if (els.loopBtn) els.loopBtn.classList.toggle('active', !!data.settings.loopOne);
if (els.repeatBtn) els.repeatBtn.classList.toggle('active', data.settings.repeatMode === 'all');
if (els.shuffleBtn) els.shuffleBtn.classList.toggle('active', !!data.settings.shuffle);
+ const mb = $('modeBtn');
+ if (mb) {
+ const i = playModeIndex();
+ mb.innerHTML = ``;
+ mb.classList.toggle('active', i > 0);
+ mb.dataset.badge = i === 3 ? '∞' : '';
+ mb.title = `${PLAY_MODES[i].name} — ${PLAY_MODES[i].sub}. Tap to change.`;
+ mb.setAttribute('aria-label', 'Playback mode: ' + PLAY_MODES[i].name);
+ }
}
function playPrev() {
if (Party.intercept('prev')) return;
@@ -9467,7 +9508,8 @@ document.querySelectorAll('.chip').forEach((c) => {
els.playBtn.addEventListener('click', () => Player.toggle());
els.nextBtn.addEventListener('click', playNext);
els.prevBtn.addEventListener('click', playPrev);
- els.shuffleBtn.addEventListener('click', toggleShuffle);
+ els.shuffleBtn.addEventListener('click', () => toggleShuffle());
+ if ($('modeBtn')) $('modeBtn').addEventListener('click', cyclePlayMode);
els.loopBtn.addEventListener('click', toggleLoopOne);
els.repeatBtn.addEventListener('click', toggleRepeat);
els.fsBtn.addEventListener('click', () => {
diff --git a/frontend/index.html b/frontend/index.html
index 145808f..7da8be8 100755
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -171,20 +171,19 @@
-
-
-
+
diff --git a/frontend/refresh.css b/frontend/refresh.css
index 1897ed8..dc7d62f 100644
--- a/frontend/refresh.css
+++ b/frontend/refresh.css
@@ -471,3 +471,18 @@
#controls .deck-main .ctrl.play svg { width: 34px; height: 34px; }
#controls .deck-main .ctrl { display: grid; place-items: center; }
#controls .ctrl.active svg { filter: drop-shadow(0 0 6px var(--accent-glow)); }
+
+/* One playback-mode button + mute in the transport row */
+#controls .deck-main { grid-template-columns: 1fr 1fr 1.5fr 1fr 1fr; }
+#controls #dkModeHost { display: none; }
+#controls #modeBtn { position: relative; }
+#controls #modeBtn:not(.active) { color: var(--text-dim); }
+#controls #modeBtn[data-badge]:not([data-badge=""])::after {
+ content: attr(data-badge); position: absolute; right: 4px; top: 4px;
+ font: 800 12px/1 var(--ui); color: var(--accent-bright);
+}
+#controls .deck-main #muteBtn { font-size: 20px; }
+#controls .dk-quick .ctrl { display: none; }
+#controls .dk-quick #fsBtn { display: grid; }
+#controls .deck-main #modeBtn { order: 1; }
+#controls .deck-main #muteBtn { order: 5; }