Reso / dashboard /script.js
sal
Add nav scrollspy to highlight active section link
3661e4a
Raw
History Blame Contribute Delete
6.78 kB
// header scroll
const hdr = document.getElementById('hdr');
const onScroll = () => { hdr.classList.toggle('scrolled', window.scrollY > 12); };
onScroll(); window.addEventListener('scroll', onScroll, { passive: true });
// nav scrollspy: highlight the nav link for the section currently in view
const navLinkMap = new Map();
document.querySelectorAll('.nav-links a[href^="#"]').forEach((a) => {
navLinkMap.set(a.getAttribute('href').slice(1), a);
});
const spySections = [...navLinkMap.keys()].map((id) => document.getElementById(id)).filter(Boolean);
if (spySections.length) {
const spy = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const link = navLinkMap.get(entry.target.id);
if (!link) return;
navLinkMap.forEach((a) => a.classList.remove('active'));
link.classList.add('active');
});
}, { rootMargin: '-40% 0px -50% 0px', threshold: 0 });
spySections.forEach((sec) => spy.observe(sec));
}
// reveal on scroll
const io = new IntersectionObserver((es) => {
es.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } });
}, { threshold: 0.12, rootMargin: '0px 0px -6% 0px' });
document.querySelectorAll('.reveal').forEach(el => io.observe(el));
// marquee logos
const logos = [
['Stellar', 'hgi-global'],
['Soroban', 'hgi-source-code'],
['Paillier FHE', 'hgi-lock-01'],
['Nova IVC', 'hgi-layers-01'],
['Shamir SSS', 'hgi-user-multiple-02'],
['Merkle Trees', 'hgi-git-fork'],
['SEP-8', 'hgi-checkmark-circle-01'],
['Groth16', 'hgi-shield-01'],
];
const mk = (name, icon) =>
`<div class="logo-item"><i class="hgi-stroke ${icon}"></i>${name}</div>`;
const html = logos.map(l => mk(l[0], l[1])).join('');
const mq = document.getElementById('mq');
if (mq) mq.innerHTML = html + html;
// console proof animation
const rows = [...document.querySelectorAll('.proof-row')];
const bar = document.getElementById('pbar');
function runProofs() {
rows.forEach(r => r.classList.remove('show'));
if (bar) { bar.style.transition = 'none'; bar.style.width = '0%'; }
rows.forEach((r, i) => setTimeout(() => r.classList.add('show'), 400 + i * 600));
setTimeout(() => {
if (bar) { bar.style.transition = 'width 1.8s cubic-bezier(.22,.61,.36,1)'; bar.style.width = '100%'; }
}, 600);
}
const cons = document.querySelector('.console');
if (cons) {
let interval;
const co = new IntersectionObserver((es) => {
es.forEach(e => {
if (e.isIntersecting) { runProofs(); interval = setInterval(runProofs, 5000); }
else { clearInterval(interval); }
});
}, { threshold: 0.3 });
co.observe(cons);
}
// count-up stats
const animateCount = (el) => {
const to = +el.dataset.to; const dur = 1100; const t0 = performance.now();
const tick = (t) => {
const p = Math.min((t - t0) / dur, 1);
const e = 1 - Math.pow(1 - p, 3);
el.textContent = Math.round(to * e);
if (p < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
};
const so = new IntersectionObserver((es) => {
es.forEach(e => { if (e.isIntersecting) { animateCount(e.target); so.unobserve(e.target); } });
}, { threshold: 0.6 });
document.querySelectorAll('.count').forEach(el => so.observe(el));
// mobile nav toggle
const toggle = document.querySelector('.nav-toggle');
const navLinks = document.querySelector('.nav-links');
if (toggle && navLinks) {
toggle.addEventListener('click', () => {
const open = navLinks.style.display === 'flex';
navLinks.style.display = open ? '' : 'flex';
navLinks.style.flexDirection = 'column';
navLinks.style.position = 'absolute';
navLinks.style.top = '68px';
navLinks.style.left = '12px';
navLinks.style.right = '12px';
navLinks.style.background = '#fff';
navLinks.style.border = '1px solid #E4E4E7';
navLinks.style.borderRadius = '12px';
navLinks.style.padding = '8px';
navLinks.style.boxShadow = '0 8px 24px rgba(0,0,0,.1)';
navLinks.style.zIndex = '300';
});
}
// scroll progress bar
const progressBar = document.createElement('div');
progressBar.className = 'scroll-progress';
document.body.appendChild(progressBar);
const updateProgress = () => {
const h = document.documentElement;
const scrolled = (h.scrollTop) / (h.scrollHeight - h.clientHeight || 1) * 100;
progressBar.style.width = scrolled + '%';
};
updateProgress();
window.addEventListener('scroll', updateProgress, { passive: true });
// cursor spotlight glow
const spotlight = document.createElement('div');
spotlight.className = 'spotlight';
document.body.appendChild(spotlight);
window.addEventListener('mousemove', (e) => {
spotlight.style.opacity = '1';
spotlight.style.left = e.clientX + 'px';
spotlight.style.top = e.clientY + 'px';
}, { passive: true });
window.addEventListener('mouseleave', () => { spotlight.style.opacity = '0'; });
// magnetic buttons + 3D tilt cards (desktop pointer only, respects reduced motion)
(function () {
const canHover = window.matchMedia('(hover: hover) and (pointer: fine)').matches;
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!canHover || reduced) return;
document.querySelectorAll('.btn-lg').forEach((btn) => {
let raf = null;
let lastEvent = null;
const apply = () => {
if (!lastEvent) return;
const r = btn.getBoundingClientRect();
const x = lastEvent.clientX - (r.left + r.width / 2);
const y = lastEvent.clientY - (r.top + r.height / 2);
btn.style.transform = `translate(${(x * 0.18).toFixed(2)}px, ${(y * 0.35).toFixed(2)}px)`;
raf = null;
};
btn.addEventListener('pointermove', (e) => {
lastEvent = e;
if (raf === null) raf = requestAnimationFrame(apply);
});
btn.addEventListener('pointerleave', () => { btn.style.transform = ''; });
});
const tiltEls = document.querySelectorAll('.card, .feat, .step, .uc, .integ, .quote, .gstat');
tiltEls.forEach((el) => {
let raf = null;
let lastEvent = null;
const apply = () => {
if (!lastEvent) return;
const r = el.getBoundingClientRect();
const px = (lastEvent.clientX - r.left) / r.width - 0.5;
const py = (lastEvent.clientY - r.top) / r.height - 0.5;
el.style.transform = `perspective(700px) rotateX(${(-py * 7).toFixed(2)}deg) rotateY(${(px * 9).toFixed(2)}deg) translateY(-4px)`;
raf = null;
};
el.addEventListener('pointerenter', () => { el.style.willChange = 'transform'; });
el.addEventListener('pointermove', (e) => {
lastEvent = e;
if (raf === null) raf = requestAnimationFrame(apply);
});
el.addEventListener('pointerleave', () => {
el.style.transform = '';
el.style.willChange = 'auto';
});
});
})();