From c3befb79b6393e2cf6c3744484699707817fea28 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 11 Sep 2026 23:18:38 -0700 Subject: The carousel's tap guard watches the finger, not only the scroll. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A tap is "the finger didn't move AND the strip didn't scroll", the same pair the mouse path has always used. Scroll position alone would have been enough for a finger (native panning moves the strip while it's down, so a real drag never looks like a tap), but only because the snap-back happens after the finger lifts — which is a timing detail to be relying on. The touchmove flag says it outright. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx --- src/app.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/app.js b/src/app.js index 273eb37..0e306bd 100644 --- a/src/app.js +++ b/src/app.js @@ -3711,9 +3711,17 @@ function balanceCard() { onTouchstart: (e) => { const el = e.currentTarget; el._dragging = true; _carDragging = true; el._skipMorph = true; - el._x0 = el.scrollLeft; // where the finger found it — a tap leaves this alone + // where the finger landed, and where it found the strip — a tap + // moves neither + el._x0 = el.scrollLeft; + el._tx = (e.touches[0] || {}).clientX || 0; + el._moved = false; clearTimeout(el._settle); }, + onTouchmove: (e) => { + const el = e.currentTarget; + if (!el._moved && Math.abs(((e.touches[0] || {}).clientX || 0) - (el._tx || 0)) > 4) el._moved = true; + }, onTouchend: (e) => { const el = e.currentTarget; el._dragging = false; // onScroll's debounce settles it @@ -3724,7 +3732,7 @@ function balanceCard() { // the unit tag on a phone took most of a second to change the number // while the same tap on a laptop (the mouse path has always had this // guard) was instant. - if (Math.abs(el.scrollLeft - (el._x0 || 0)) < 2) { endCarouselDrag(); return; } + if (!el._moved && Math.abs(el.scrollLeft - (el._x0 || 0)) < 2) { endCarouselDrag(); return; } // The finger is up: lift the render hold (the strip stays frozen via // _skipMorph) and commit the predicted landing NOW, so the history // below loads under the glide instead of after it. -- cgit v1.2.3