91 lines
3.2 KiB
JavaScript
91 lines
3.2 KiB
JavaScript
/* ============================================================
|
|
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();
|
|
})();
|