// snap.js // Full working gesture-based snap-scrolling with wheel, touch and keyboard. // The trick: group wheel events into GESTURES, not deltas. (function () { const GESTURE_TIMEOUT = 180; // ms of silence means gesture finished const ANIMATION_LOCK = 600; // ms lock to prevent double scrollIntoView let wheelTimeout = null; // end-of-gesture timer let cumulativeDelta = 0; // total movement in current gesture let animating = false; // lock while animation runs const sections = Array.from(document.querySelectorAll(".section")); if (!sections.length) return; const ids = sections.map(s => s.id); function clamp(i) { if (i < 0) return 0; if (i >= ids.length) return ids.length - 1; return i; } function getCurrentIndex() { let idx = 0; let best = Infinity; sections.forEach((sec, i) => { const dist = Math.abs(sec.getBoundingClientRect().top); if (dist < best) { best = dist; idx = i; } }); return idx; } function goToSection(index) { index = clamp(index); animating = true; const target = document.getElementById(ids[index]); if (!target) return; target.scrollIntoView({ behavior: "smooth", block: "start" }); history.replaceState(null, "", "#" + ids[index]); // unlock after animation ends setTimeout(() => { animating = false }, ANIMATION_LOCK); } // Wheel handling with gesture grouping function handleWheel(e) { e.preventDefault(); if (animating) return; cumulativeDelta += e.deltaY; // keep accumulating in this gesture if (wheelTimeout) clearTimeout(wheelTimeout); // When wheel stops firing, gesture is complete wheelTimeout = setTimeout(() => { const current = getCurrentIndex(); const direction = cumulativeDelta > 0 ? 1 : -1; goToSection(current + direction); // reset gesture state cumulativeDelta = 0; wheelTimeout = null; }, GESTURE_TIMEOUT); } // Touch gestures let startY = 0; function handleTouchStart(e) { startY = e.touches[0].clientY; } function handleTouchEnd(e) { if (animating) return; const endY = e.changedTouches[0].clientY; const diff = startY - endY; const threshold = 30; if (Math.abs(diff) < threshold) return; const current = getCurrentIndex(); const direction = diff > 0 ? 1 : -1; goToSection(current + direction); } // Keyboard input function handleKey(e) { if (animating) return; const current = getCurrentIndex(); if (e.key === "ArrowDown" || e.key === "PageDown") goToSection(current + 1); else if (e.key === "ArrowUp" || e.key === "PageUp") goToSection(current - 1); else if (e.key === "Home") goToSection(0); else if (e.key === "End") goToSection(ids.length - 1); } const container = document.querySelector(".sections") || document; container.addEventListener("wheel", handleWheel, { passive: false }); container.addEventListener("touchstart", handleTouchStart, { passive: true }); container.addEventListener("touchend", handleTouchEnd, { passive: true }); window.addEventListener("keydown", handleKey); })();