psrc2 / script.js
stat2025's picture
Upload 3 files
587e3b6 verified
Raw
History Blame Contribute Delete
5.16 kB
(() => {
const $ = (s, root = document) => root.querySelector(s);
const $$ = (s, root = document) => [...root.querySelectorAll(s)];
const header = $('#siteHeader');
const progress = $('#scrollProgress');
const bottomCta = $('#bottomCta');
const toTop = $('#toTop');
const menu = $('#mobileMenu');
const menuToggle = $('#menuToggle');
const accessPanel = $('#accessPanel');
const root = document.documentElement;
const setLock = (locked) => document.body.classList.toggle('menu-lock', locked);
function onScroll(){
const y = window.scrollY;
const h = Math.max(1, document.documentElement.scrollHeight - window.innerHeight);
header?.classList.toggle('scrolled', y > 18);
if(progress) progress.style.width = `${Math.min(100, (y / h) * 100)}%`;
const ctaSection = $('#cta');
const footer = $('#contact');
const ctaTop = ctaSection ? ctaSection.getBoundingClientRect().top + y : Infinity;
const footerTop = footer ? footer.getBoundingClientRect().top + y : Infinity;
const shouldShow = y > window.innerHeight * .78 && y + window.innerHeight < Math.min(ctaTop + 120, footerTop);
bottomCta?.classList.toggle('show', shouldShow);
toTop?.classList.toggle('show', y > 650);
}
addEventListener('scroll', onScroll, {passive:true});
addEventListener('resize', onScroll, {passive:true});
onScroll();
if('IntersectionObserver' in window){
const revealObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if(entry.isIntersecting){
entry.target.classList.add('in');
revealObserver.unobserve(entry.target);
}
});
}, {threshold:.12, rootMargin:'0px 0px -30px 0px'});
$$('.reveal').forEach(el => revealObserver.observe(el));
const sections = $$('main section[id]');
const navLinks = $$('.nav-links a');
const activeObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if(entry.isIntersecting){
navLinks.forEach(a => a.classList.toggle('active', a.getAttribute('href') === `#${entry.target.id}`));
}
});
}, {rootMargin:'-40% 0px -50% 0px'});
sections.forEach(section => activeObserver.observe(section));
} else {
$$('.reveal').forEach(el => el.classList.add('in'));
}
function openMenu(){
menu?.classList.add('open');
menu?.setAttribute('aria-hidden','false');
menuToggle?.setAttribute('aria-expanded','true');
setLock(true);
}
function closeMenu(){
menu?.classList.remove('open');
menu?.setAttribute('aria-hidden','true');
menuToggle?.setAttribute('aria-expanded','false');
if(!accessPanel?.classList.contains('open')) setLock(false);
}
menuToggle?.addEventListener('click', openMenu);
$('#closeMenu')?.addEventListener('click', closeMenu);
menu?.addEventListener('click', e => { if(e.target === menu || e.target.closest('a')) closeMenu(); });
function openAccess(){
accessPanel?.classList.add('open');
accessPanel?.setAttribute('aria-hidden','false');
setLock(true);
}
function closeAccess(){
accessPanel?.classList.remove('open');
accessPanel?.setAttribute('aria-hidden','true');
if(!menu?.classList.contains('open')) setLock(false);
}
$$('.access-open').forEach(btn => btn.addEventListener('click', openAccess));
$('#closeAccess')?.addEventListener('click', closeAccess);
accessPanel?.addEventListener('click', e => { if(e.target === accessPanel) closeAccess(); });
let saved = {};
try { saved = JSON.parse(localStorage.getItem('psrcAccess') || '{}'); } catch(_) {}
if(saved.scale) root.style.setProperty('--font-scale', saved.scale);
if(saved.contrast) document.body.classList.add('high-contrast');
if(saved.motion) document.body.classList.add('reduce-motion');
function saveAccess(){
localStorage.setItem('psrcAccess', JSON.stringify({
scale: getComputedStyle(root).getPropertyValue('--font-scale').trim() || '1',
contrast: document.body.classList.contains('high-contrast'),
motion: document.body.classList.contains('reduce-motion')
}));
}
accessPanel?.addEventListener('click', e => {
const action = e.target.dataset.access;
if(!action) return;
const scale = parseFloat(getComputedStyle(root).getPropertyValue('--font-scale')) || 1;
if(action === 'fontPlus') root.style.setProperty('--font-scale', Math.min(1.18, scale + .06));
if(action === 'fontMinus') root.style.setProperty('--font-scale', Math.max(.92, scale - .06));
if(action === 'contrast') document.body.classList.toggle('high-contrast');
if(action === 'motion') document.body.classList.toggle('reduce-motion');
if(action === 'reset'){
root.style.setProperty('--font-scale', 1);
document.body.classList.remove('high-contrast','reduce-motion');
localStorage.removeItem('psrcAccess');
return;
}
saveAccess();
});
toTop?.addEventListener('click', () => window.scrollTo({top:0, behavior: document.body.classList.contains('reduce-motion') ? 'auto' : 'smooth'}));
addEventListener('keydown', e => { if(e.key === 'Escape'){ closeMenu(); closeAccess(); } });
})();