Index: src/assets/css/style.css
===================================================================
--- src/assets/css/style.css	(revision 4fbe2c1f0fd6e892ba64f0b6b6b9d72393a93573)
+++ src/assets/css/style.css	(revision c0e463b1f870f3f742bb65ca7ee345c081ff782b)
@@ -5235,2 +5235,26 @@
     background: color-mix(in srgb, var(--accent) 12%, transparent);
 }
+
+
+/* ── De speler in bandmodus ────────────────────────────────────────────
+   Zelfde speler, ander gedrag: op een cassette kies je geen nummer. De lijst
+   blijft staan zodat je ziet wat erop staat, maar zonder de opmaak die belooft
+   dat je erin kunt prikken. */
+.audio-sheet-queue-list.is-tape .audio-sheet-queue-item {
+    cursor: default;
+    pointer-events: none;
+}
+.audio-sheet-queue-list.is-tape .audio-sheet-queue-item:hover {
+    background: none;
+}
+/* Het lopende nummer blijft wel gemarkeerd -- dat is de enige plek waar je op
+   een bandje leest waar je bent. */
+.audio-sheet-queue-list.is-tape .audio-sheet-queue-item.is-current {
+    opacity: 1;
+}
+/* Spoelknoppen mogen niet als "volgende" voelen: vasthouden hoort geen tekst te
+   selecteren of de pagina te laten zoomen. */
+.audio-btn.is-wind {
+    touch-action: manipulation;
+    user-select: none;
+}
Index: src/assets/js/audio-player.js
===================================================================
--- src/assets/js/audio-player.js	(revision 4fbe2c1f0fd6e892ba64f0b6b6b9d72393a93573)
+++ src/assets/js/audio-player.js	(revision c0e463b1f870f3f742bb65ca7ee345c081ff782b)
@@ -27,4 +27,7 @@
     vol:   '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 9v6h4l5 5V4L7 9H3zM16 8a5 5 0 010 8M19 5a9 9 0 010 14" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>',
     mute:  '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 9v6h4l5 5V4L7 9H3zM17 9l5 5M22 9l-5 5" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>',
+    // Dubbele driehoeken: het teken voor spoelen, niet voor overslaan.
+    rew:   '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M11 12l9-7v14zM2 12l9-7v14z" fill="currentColor"/></svg>',
+    ff:    '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13 12L4 5v14zM22 12l-9-7v14z" fill="currentColor"/></svg>',
     musicNote: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 17V5l12-2v12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle cx="6" cy="17" r="3" fill="currentColor"/><circle cx="18" cy="15" r="3" fill="currentColor"/></svg>',
   };
@@ -128,4 +131,8 @@
   const prevBtn = $('audio-prev');
   const nextBtn = $('audio-next');
+  // De vier knoppen die in bandmodus van betekenis veranderen: vorige/volgende
+  // worden terugspoelen/vooruitspoelen. Ze staan hier bij elkaar zodat het
+  // omzetten op EEN plek gebeurt.
+  const SPOELKNOPPEN = ['audio-prev', 'audio-next', 'audio-sheet-prev', 'audio-sheet-next'];
   const muteBtn = $('audio-mute');
   const volumeSlider = $('audio-volume');
@@ -491,10 +498,38 @@
   }
 
+  // De lengte van de hele band, uit de bekende trackduren. Null zodra er van
+  // een nummer geen duur bekend is: een som met gaten is een verzonnen getal,
+  // en dan valt de teller liever terug op wat hij wel zeker weet.
+  function bandDuur() {
+    if (!queue.length) return null;
+    let som = 0;
+    for (const t of queue) {
+      const d = Number(t && t.duration) || 0;
+      if (d <= 0) return null;
+      som += d;
+    }
+    return som;
+  }
+
+  /** Hoeveel band ligt er voor nummer `i`. */
+  function bandOffset(i) {
+    let som = 0;
+    for (let n = 0; n < i && n < queue.length; n++) som += Number(queue[n].duration) || 0;
+    return som;
+  }
+
   function displayTimes() {
-    // Een bandje heeft een teller, geen nummerpositie: hij telt door over de
-    // kant heen. Dat is letterlijk de ketentijdlijn, dus hier juist NIET
-    // terugrekenen naar trackcoordinaten.
-    if (tapeMode && useMse() && chain.length) {
-      return { cur: audio.currentTime || 0, dur: chain[chain.length - 1].end };
+    // EEN BANDJE HEEFT EEN TELLER, geen nummerpositie. Hij telt door over de
+    // kant heen.
+    //
+    // Uit de trackduren en niet uit de keten, en dat verschil is zichtbaar: de
+    // keten bevat alleen wat gebufferd is, dus het totaal groeide mee tijdens
+    // het luisteren (2:05 met een nummer geladen, 4:07 met drie). En sprong je
+    // naar nummer drie, dan begon de keten daar opnieuw op nul en stond de
+    // teller weer aan het begin van de band.
+    if (tapeMode) {
+      const totaal = bandDuur();
+      if (totaal) return { cur: bandOffset(currentIndex) + (trackTijd().cur || 0), dur: totaal };
+      if (useMse() && chain.length) return { cur: audio.currentTime || 0, dur: chain[chain.length - 1].end };
     }
     return trackTijd();
@@ -621,4 +656,5 @@
     albumName = (opts && opts.albumName) || '';
     tapeMode = !!(opts && opts.asTape);
+    zetKnopStanden();
     if (!queue.length) return;
     loadTrack(typeof startIdx === 'number' ? Math.max(0, Math.min(startIdx, queue.length - 1)) : 0, true);
@@ -681,4 +717,5 @@
     albumName = '';
     tapeMode = false;
+    zetKnopStanden();
     loadSeq++;  // cancel any in-flight load
     dropPreload();
@@ -700,4 +737,9 @@
       sheetQueueList.appendChild(li);
     });
+    // OP EEN BANDJE KIES JE NIET. De lijst blijft staan -- je mag zien wat
+    // erop staat -- maar zonder klik en zonder de opmaak die belooft dat het
+    // kan. Dat is hetzelfde onderscheid als bij de cassette in de post.
+    sheetQueueList.classList.toggle('is-tape', tapeMode);
+    if (tapeMode) return;
     sheetQueueList.querySelectorAll('.audio-sheet-queue-item').forEach((li) => {
       li.addEventListener('click', () => {
@@ -883,9 +925,41 @@
   // ============================================================
   playBtn.addEventListener('click', togglePlay);
-  prevBtn.addEventListener('click', prev);
-  nextBtn.addEventListener('click', next);
+  /**
+   * De vier knoppen in de juiste stand zetten. In bandmodus zijn het
+   * spoelknoppen: ander teken, ander woord, en vasthouden spoelt door.
+   */
+  function zetKnopStanden() {
+    for (const id of SPOELKNOPPEN) {
+      const b = $(id);
+      if (!b) continue;
+      const vooruit = id.endsWith('next');
+      b.innerHTML = tapeMode ? (vooruit ? SVG.ff : SVG.rew) : (vooruit ? SVG.next : SVG.prev);
+      const label = tapeMode ? (vooruit ? 'Vooruitspoelen' : 'Terugspoelen') : (vooruit ? 'Volgende' : 'Vorige');
+      b.setAttribute('aria-label', label);
+      b.setAttribute('title', label);
+      b.classList.toggle('is-wind', tapeMode);
+    }
+  }
+
+  // Een KLIK: een nummer verder, of in bandmodus een stukje spoelen. Dat laatste
+  // is er voor toetsenbord en schermlezer, want vasthouden is met een
+  // spatiebalk geen gebaar.
+  const TIK_SPOEL_S = 5;
+  prevBtn.addEventListener('click', () => (tapeMode ? seekBy(-TIK_SPOEL_S) : prev()));
+  nextBtn.addEventListener('click', () => (tapeMode ? seekBy(TIK_SPOEL_S) : next()));
   sheetPlay.addEventListener('click', togglePlay);
-  sheetPrev.addEventListener('click', prev);
-  sheetNext.addEventListener('click', next);
+  sheetPrev.addEventListener('click', () => (tapeMode ? seekBy(-TIK_SPOEL_S) : prev()));
+  sheetNext.addEventListener('click', () => (tapeMode ? seekBy(TIK_SPOEL_S) : next()));
+
+  // VASTHOUDEN spoelt door. Op de knop zelf indrukken, maar loslaten op
+  // document: glijdt je vinger van de knop af, dan hoort de band te stoppen en
+  // niet door te blijven spoelen.
+  for (const id of SPOELKNOPPEN) {
+    const b = $(id);
+    if (!b) continue;
+    b.addEventListener('pointerdown', () => { if (tapeMode) startWind(id.endsWith('next') ? 1 : -1); });
+  }
+  document.addEventListener('pointerup', stopWind);
+  document.addEventListener('pointercancel', stopWind);
 
   // ============================================================
@@ -1199,4 +1273,43 @@
   }
 
+  /**
+   * SPOELEN, met de knop ingedrukt.
+   *
+   * Stond eerst in assets/js/mod/tape.js, want daar zat de cassette. Nu de
+   * speler zelf ook spoelknoppen krijgt zou dezelfde lus op twee plekken staan,
+   * en dan lopen ze uit elkaar zodra iemand aan de versnelling draait. Hier is
+   * de enige plek; de cassette op de pagina roept deze aan.
+   */
+  const WIND_START = 4;        // maal de normale snelheid bij het indrukken
+  const WIND_MAX = 16;
+  const WIND_VERSNELLING = 1.35;
+  const WIND_STAP_MS = 120;
+  let winder = null;
+
+  function stopWind() {
+    if (winder) { clearInterval(winder.timer); winder = null; }
+  }
+
+  function startWind(richting) {
+    stopWind();
+    const r = richting < 0 ? -1 : 1;
+    let snelheid = WIND_START;
+    let vorige = null;
+    let stil = 0;
+    const timer = setInterval(() => {
+      snelheid = Math.min(WIND_MAX, snelheid * WIND_VERSNELLING);
+      seekBy(r * snelheid * (WIND_STAP_MS / 1000));
+      // AAN DE KOP EN DE STAART STOPPEN. De positie wordt afgeklemd, dus daar
+      // gebeurt niets meer -- maar zonder deze controle blijft de knop malen en
+      // draaien de spoelen door alsof er nog band is.
+      const nu = displayTimes().cur;
+      if (vorige !== null && Math.abs(nu - vorige) < 0.05) {
+        if (++stil >= 2) { stopWind(); return; }
+      } else stil = 0;
+      vorige = nu;
+    }, WIND_STAP_MS);
+    winder = { timer, richting: r };
+  }
+
   /** Waar staat de teller, over het hele bandje. */
   function tapeTijden() { return displayTimes(); }
@@ -1204,5 +1317,7 @@
   window.pcmsAudioPlayer = {
     setQueue, play, pause, next, prev, close, openSheet, closeSheet, seekBy, tapeTijden,
+    startWind, stopWind,
     isTape: () => tapeMode,
+    isWinding: () => !!winder,
     isPlaying: () => isPlaying,
     currentTrack: () => queue[currentIndex] || null,
@@ -1252,4 +1367,5 @@
     albumName = s.albumName || '';
     tapeMode = !!s.tapeMode;
+    zetKnopStanden();
     pendingSeek = s.time || 0;
     const idx = Math.max(0, Math.min(s.currentIndex || 0, queue.length - 1));
Index: src/assets/js/mod/tape.js
===================================================================
--- src/assets/js/mod/tape.js	(revision 4fbe2c1f0fd6e892ba64f0b6b6b9d72393a93573)
+++ src/assets/js/mod/tape.js	(revision c0e463b1f870f3f742bb65ca7ee345c081ff782b)
@@ -14,4 +14,8 @@
  * die tijdlijn. Vandaar seekBy() en niet next()/prev() -- springen per nummer
  * is een playlistgebaar, en dan is dit een lijst met een cassetteplaatje.
+ *
+ * DE SPOELLUS STAAT IN DE SPELER, niet hier: sinds de speler onderin dezelfde
+ * spoelknoppen heeft zou dezelfde versnelling op twee plekken staan. Deze
+ * module bedient hem en tekent het jasje.
  *
  * WAAROM ER GEPOLLD WORDT. audio-player.js zendt geen gebeurtenissen uit -- het
@@ -56,4 +60,9 @@
   tape.classList.toggle('is-playing', draait);
   tape.classList.toggle('is-loaded', i >= 0);
+  // Spoelt de SPELER (bijvoorbeeld via zijn eigen knoppen onderin), dan hoort
+  // deze cassette dat ook te laten zien.
+  const windt = i >= 0 && !!p && p.isWinding && p.isWinding();
+  if (!windt) tape.classList.remove('is-winding', 'is-winding-back');
+  else tape.classList.add('is-winding');
 
   const knop = tape.querySelector('[data-tape-play]');
@@ -91,14 +100,9 @@
  * schermlezer, want vasthouden is met een spatiebalk geen gebaar.
  */
-const START_SNELHEID = 4;    // maal de normale snelheid bij het indrukken
-const MAX_SNELHEID = 16;
-const VERSNELLING = 1.35;    // per stap
-const STAP_MS = 120;
 const TIK_STAP_S = 5;        // een losse klik
 
-let winder = null;
-
 function stopWinden(tape) {
-  if (winder) { clearInterval(winder.timer); winder = null; }
+  const p = speler();
+  if (p && p.stopWind) p.stopWind();
   if (tape) tape.classList.remove('is-winding', 'is-winding-back');
 }
@@ -106,26 +110,12 @@
 function startWinden(tape, richting) {
   const p = speler();
-  if (!p || huidigeIndex(tape) < 0) return;
-  stopWinden(tape);
-  let snelheid = START_SNELHEID;
-  let vorigePositie = null;
-  let stil = 0;
+  if (!p || !p.startWind || huidigeIndex(tape) < 0) return;
+  // DE LUS STAAT IN DE SPELER. Hij stond hier, en toen kreeg de speler zelf ook
+  // spoelknoppen -- twee kopieen van dezelfde versnelling die uit elkaar lopen
+  // zodra iemand aan een getal draait. Deze module zet alleen nog het jasje:
+  // welke spoelen draaien en hoe hard.
+  p.startWind(richting);
   tape.classList.add('is-winding');
   if (richting < 0) tape.classList.add('is-winding-back');
-  const timer = setInterval(() => {
-    snelheid = Math.min(MAX_SNELHEID, snelheid * VERSNELLING);
-    p.seekBy(richting * snelheid * (STAP_MS / 1000));
-    tekenBand(tape);
-    // AAN HET EIND VAN DE BAND STOPPEN MET SPOELEN. De speler klemt de positie
-    // af, dus daar gebeurt niets meer -- maar de spoelen bleven wel doordraaien
-    // en de knop bleef oplichten, alsof er nog band was. Beweegt de teller twee
-    // rondes niet, dan zijn we bij de kop of de staart.
-    const nu = p.tapeTijden ? p.tapeTijden().cur : null;
-    if (nu !== null && vorigePositie !== null && Math.abs(nu - vorigePositie) < 0.05) {
-      if (++stil >= 2) { stopWinden(tape); return; }
-    } else stil = 0;
-    vorigePositie = nu;
-  }, STAP_MS);
-  winder = { timer, tape };
 }
 
