/* ============================================================ Global Presence — fullpage screen controller One screen per wheel gesture / swipe / arrow; dot nav jumps. Footer (after the last screen) remains freely reachable. ============================================================ */ (function () { var screens = Array.prototype.slice.call(document.querySelectorAll('.gp-screen')); var links = Array.prototype.slice.call(document.querySelectorAll('.gp-nav-list a')); var n = screens.length; if (!n) return; var current = 0; var animating = false; function topOf(i) { return screens[i].offsetTop; } function clamp(i) { return Math.max(0, Math.min(n - 1, i)); } function setActive(i) { current = i; links.forEach(function (a) { a.classList.toggle('active', parseInt(a.getAttribute('data-i'), 10) === i); }); } function go(i) { i = clamp(i); if (animating || i === current) return; animating = true; window.scrollTo({ top: topOf(i), behavior: 'smooth' }); setTimeout(function () { animating = false; }, 650); } function syncActive() { var y = window.scrollY + window.innerHeight * 0.4; var i = 0; for (var k = 0; k < n; k++) { if (y >= topOf(k)) i = k; } if (i !== current) setActive(i); } function inFooterZone() { return window.scrollY > topOf(n - 1) + 20; } // ---- wheel: one screen per gesture; release to footer at the last screen ---- window.addEventListener('wheel', function (e) { if (animating) { e.preventDefault(); return; } var d = e.deltaY; if (inFooterZone()) { // free scroll inside footer; snap back up when crossing the boundary if (d < 0 && window.scrollY <= topOf(n - 1) + 80) { e.preventDefault(); go(n - 1); } return; } if (current === n - 1 && d > 0) return; // allow scrolling down into the footer e.preventDefault(); if (Math.abs(d) < 5) return; go(current + (d > 0 ? 1 : -1)); }, { passive: false }); // ---- keyboard ---- window.addEventListener('keydown', function (e) { var k = e.key; if (k === 'ArrowDown' || k === 'PageDown') { e.preventDefault(); go(current + 1); } else if (k === 'ArrowUp' || k === 'PageUp') { e.preventDefault(); go(current - 1); } else if (k === 'Home') { e.preventDefault(); go(0); } else if (k === 'End') { e.preventDefault(); go(n - 1); } }); // ---- touch swipe ---- var ty = null; window.addEventListener('touchstart', function (e) { ty = e.touches[0].clientY; }, { passive: true }); window.addEventListener('touchend', function (e) { if (ty === null) return; var dy = ty - e.changedTouches[0].clientY; if (Math.abs(dy) > 50) go(current + (dy > 0 ? 1 : -1)); ty = null; }, { passive: true }); // ---- region nav links (overview screen) ---- links.forEach(function (a) { a.addEventListener('click', function (e) { e.preventDefault(); var i = parseInt(a.getAttribute('data-i'), 10); if (!isNaN(i)) go(i); }); }); // ---- sync + resize ---- window.addEventListener('scroll', syncActive, { passive: true }); window.addEventListener('resize', function () { window.scrollTo({ top: topOf(current), behavior: 'auto' }); }); syncActive(); })();