1// language toggle 2const buttons = document.querySelectorAll('.lang-toggle button'); 3buttons.forEach(btn => { 4 btn.addEventListener('click', () => { 5 const lang = btn.dataset.setLang; 6 document.body.setAttribute('data-lang', lang); 7 buttons.forEach(b => { 8 b.classList.toggle('active', b === btn); 9 b.setAttribute('aria-pressed', b === btn ? 'true' : 'false'); 10 }); 11 }); 12}); 13 14// nav scroll state + logo swap (white on hero, dark once scrolled) 15const nav = document.getElementById('siteNav'); 16const navLogo = document.getElementById('navLogo'); 17const LOGO_DARK = 'assets/logo_nav_light.png'; 18const LOGO_LIGHT = 'assets/logo_nav_dark.png'; 19window.addEventListener('scroll', () => { 20 const scrolled = window.scrollY > 60; 21 nav.classList.toggle('scrolled', scrolled); 22 nav.classList.toggle('on-dark', !scrolled); 23 navLogo.src = scrolled ? LOGO_DARK : LOGO_LIGHT; 24}, { passive: true }); 25 26// reveal on scroll 27const revealEls = document.querySelectorAll('.reveal'); 28const io = new IntersectionObserver((entries) => { 29 entries.forEach(e => { 30 if(e.isIntersecting){ e.target.classList.add('in'); io.unobserve(e.target); } 31 }); 32}, { threshold: 0.12 }); 33revealEls.forEach(el => io.observe(el)); 34 35// animated counters 36const counters = document.querySelectorAll('[data-count]'); 37const counterIO = new IntersectionObserver((entries) => { 38 entries.forEach(entry => { 39 if(!entry.isIntersecting) return; 40 const el = entry.target; 41 const target = parseInt(el.dataset.count, 10); 42 const duration = 1400; 43 const start = performance.now(); 44 function tick(now){ 45 const progress = Math.min((now - start) / duration, 1); 46 const eased = 1 - Math.pow(1 - progress, 3); 47 const value = Math.floor(eased * target); 48 el.textContent = value.toLocaleString('pt-PT'); 49 if(progress < 1){ requestAnimationFrame(tick); } else { el.textContent = target.toLocaleString('pt-PT'); } 50 } 51 requestAnimationFrame(tick); 52 counterIO.unobserve(el); 53 }); 54}, { threshold: 0.4 }); 55counters.forEach(el => counterIO.observe(el)); 56 57// Video modal 58function openVideoModal() { 59 const modal = document.getElementById('videoModal'); 60 const video = document.getElementById('modalVideo'); 61 modal.classList.add('open'); 62 document.body.style.overflow = 'hidden'; 63 video.play().catch(() => {}); 64} 65 66function closeVideoModal(e) { 67 // close only if clicking the backdrop (not the inner panel), or the close button 68 if (e && e.target !== document.getElementById('videoModal') && !e.target.closest('.video-modal-close')) return; 69 const modal = document.getElementById('videoModal'); 70 const video = document.getElementById('modalVideo'); 71 modal.classList.remove('open'); 72 document.body.style.overflow = ''; 73 video.pause(); 74 video.currentTime = 0; 75} 76 77// Close modal with Escape key 78document.addEventListener('keydown', function(e) { 79 if (e.key === 'Escape') { 80 const modal = document.getElementById('videoModal'); 81 if (modal && modal.classList.contains('open')) { 82 closeVideoModal({ target: modal }); 83 } 84 } 85});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.