Index: src/assets/js/audio-player.js
===================================================================
--- src/assets/js/audio-player.js	(revision e679bce2a7d2484f5395220f40517ea38e74c0da)
+++ src/assets/js/audio-player.js	(revision 4fbe2c1f0fd6e892ba64f0b6b6b9d72393a93573)
@@ -279,4 +279,5 @@
   function preloadNext() {
     if (queue.length < 2) return;
+    if (tapeMode && currentIndex >= queue.length - 1) return;   // einde band
     const ni = (currentIndex + 1) % queue.length;
     const t = queue[ni];
@@ -361,7 +362,15 @@
     const start = chain.length ? chain[chain.length - 1].end : (buffered.length ? buffered.start(0) : 0);
     chain.push({ qIndex, start, end: chainEnd });
-    if (queue.length === 1 && ms && ms.readyState === 'open') {
+    // DE STREAM MOET DICHT ALS ER NIETS MEER KOMT, anders vuurt `ended` nooit.
+    // Bij een wachtrij van een was dat al zo. Een BANDJE heeft nu hetzelfde
+    // nodig: sinds hij niet meer rondloopt haakt de keten na het laatste nummer
+    // niets meer aan, en dan bleef de band aan het eind hangen -- de teller
+    // stilstaand op de laatste seconde, isPlaying() waar, en de spoelen
+    // draaiend. Gemeten op dev (22-8): 125,6 van 125,7 en daar bleef hij.
+    const laatsteVanDeBand = tapeMode && qIndex >= queue.length - 1;
+    if ((queue.length === 1 || laatsteVanDeBand) && ms && ms.readyState === 'open') {
       // Single-track queue: close the stream so `ended` fires (which replays
-      // it, matching the old engine's behaviour).
+      // it, matching the old engine's behaviour). Bij een bandje stopt `ended`
+      // hem juist, want next() pauzeert daar aan het eind.
       try { ms.endOfStream(); } catch (e) {}
     }
@@ -374,5 +383,11 @@
     const seg = currentSegment();
     if (!seg || chain.length - 1 - chain.indexOf(seg) >= 1) return;  // already one ahead
-    const nextIdx = (chain[chain.length - 1].qIndex + 1) % queue.length;
+    // EEN BANDJE LOOPT NIET ROND (Robins eis, 22-8). Deze modulo is precies wat
+    // een cassette eindeloos maakte: na het laatste nummer hing hij nummer een
+    // er weer achter, en omdat het een doorlopende keten is merk je dat niet
+    // eens als een trackwissel -- de band gaat gewoon door.
+    const volgendeInRij = chain[chain.length - 1].qIndex + 1;
+    if (tapeMode && volgendeInRij > queue.length - 1) return;   // einde band
+    const nextIdx = volgendeInRij % queue.length;
     const t = queue[nextIdx];
     if (!t || !t.url) return;
@@ -642,8 +657,17 @@
   function next() {
     if (!queue.length) return;
+    // Aan het eind van een bandje: stoppen. `ended` roept deze functie aan, dus
+    // zonder deze tak begint de band na het laatste nummer weer vooraan.
+    if (tapeMode && currentIndex >= queue.length - 1) { pause(); return; }
     loadTrack((currentIndex + 1) % queue.length, true);
   }
   function prev() {
     if (!queue.length) return;
+    // En aan het begin ook niet omlopen. Terugspoelen voorbij het begin levert
+    // de kop van de band op, niet het laatste nummer.
+    if (tapeMode && currentIndex === 0) {
+      try { audio.currentTime = 0; } catch (e) { /* nog niets geladen */ }
+      return;
+    }
     loadTrack(currentIndex === 0 ? queue.length - 1 : currentIndex - 1, true);
   }
@@ -1157,6 +1181,19 @@
     const duur = audio.duration || 0;
     const nu = audio.currentTime || 0;
-    if (duur && nu + d >= duur) { next(); return; }
-    if (nu + d < 0) { prev(); return; }
+    if (duur && nu + d >= duur) {
+      // Vooruit voorbij het eind. In bandmodus stopt next() aan het eind van de
+      // band; daarbuiten loopt hij door naar het volgende nummer.
+      next();
+      return;
+    }
+    if (nu + d < 0) {
+      // TERUGSPOELEN VOORBIJ HET BEGIN moet in het vorige nummer landen aan het
+      // EIND, niet aan het begin -- dat is wat terugspoelen doet. Zonder dit
+      // sprong je bij elke druk naar de kop van het vorige nummer en kwam je
+      // nooit ergens in het midden uit.
+      if (!(tapeMode && currentIndex === 0)) pendingSeek = Number.MAX_SAFE_INTEGER;
+      prev();
+      return;
+    }
     try { audio.currentTime = Math.max(0, nu + d); } catch (e) {}
   }
