Index: src/assets/js/mod/read.js
===================================================================
--- src/assets/js/mod/read.js	(revision 5e724138a7411a573c73ec9c9d90db23ba7c650e)
+++ src/assets/js/mod/read.js	(revision fbacddc02f08c6a7ed8e09f98fdd30b318feb63c)
@@ -448,4 +448,50 @@
     if (punten[vooruit].value - nu <= vangZone()) echtGaNaar(vooruit);
   };
+
+  // OP TOUCH DOET DE MODULE-EIGEN TRIGGER HET NIET, en dat is meetbaar in de
+  // vendor-bron, geen vermoeden. Drie feiten op een rij:
+  //
+  //   1. lenis geeft 'virtual-scroll' door met de RUWE vingerdelta, VOOR hij
+  //      de uitloop uitrekent (teken * |snelheid|^touchInertiaExponent).
+  //   2. lenis-snap negeert elke touchmove en beoordeelt dus EEN keer per
+  //      gebaar, op touchend, met `scroll + rawDelta` -- de plek waar je
+  //      vinger LOSLIET.
+  //   3. de uitloop draagt daarna nog honderden pixels verder.
+  //
+  // De snapbeslissing valt dus op een positie die de scroll meteen verlaat.
+  // Op desktop klopt dezelfde som wel: wieldelta's zijn klein en de
+  // smoothWheel-uitloop is kort, dus voorspelling en landing liggen bijeen.
+  // Vandaar: op touch de module-trigger eraf en zelf beoordelen tegen
+  // lenis.targetScroll -- die wordt synchroon bij touchend gezet en IS het
+  // exacte landingspunt van de uitloop.
+  if (OP_TOUCH.matches) {
+    lenis.off('virtual-scroll', snap.onSnapDebounced);
+    let raakTimer = null;
+    let vertrek = 0;   // scrollpositie bij loslaten: punten daarachter zijn
+                       // de bovenkant van het HUIDIGE bericht, nooit grijpen
+    lenis.on('virtual-scroll', (e) => {
+      const soort = e.event && e.event.type;
+      if (soort === 'touchstart' || soort === 'touchmove') { clearTimeout(raakTimer); return; }
+      if (soort !== 'touchend') return;
+      vertrek = lenis.scroll;
+      clearTimeout(raakTimer);
+      raakTimer = setTimeout(() => {
+        if (!laatsteRichtingOmlaag) return;
+        const doel = lenis.targetScroll;   // de echte landing, niet de raakdelta
+        const punten = snap.computeSnaps();
+        let best = -1;
+        punten.forEach((punt, i) => {
+          if (punt.value <= vertrek + 2) return;
+          if (Math.abs(doel - punt.value) > vangZone()) return;
+          if (best < 0 || Math.abs(doel - punt.value) < Math.abs(doel - punten[best].value)) best = i;
+        });
+        // Landt de uitloop net VOORBIJ een bovenkant, dan is een klein stukje
+        // terug naar die lijn precies de gevraagde snap -- geen terugtrekking,
+        // want punten achter het loslaatpunt zijn hierboven al uitgesloten.
+        if (best >= 0) echtGaNaar(best);
+      }, 150);
+    });
+  }
+
   // Native snappen uit: twee mechanismen op dezelfde scroller vechten.
   document.documentElement.style.scrollSnapType = 'none';
Index: src/views/shell.ejs
===================================================================
--- src/views/shell.ejs	(revision 5e724138a7411a573c73ec9c9d90db23ba7c650e)
+++ src/views/shell.ejs	(revision fbacddc02f08c6a7ed8e09f98fdd30b318feb63c)
@@ -505,5 +505,5 @@
   // Eén nummer voor de hele map. Te vaak bumpen kost één download; te weinig
   // bumpen kost een bugfix die nooit aankomt.
-  var MOD_V = 55;
+  var MOD_V = 56;
 
   // name -> 1 (aan het laden) of de module-namespace (geladen). Een module
