1// Scroll-triggered animations using Intersection Observer 2(function() { 3 'use strict'; 4 5 const observer = new IntersectionObserver((entries) => { 6 entries.forEach(entry => { 7 if (entry.isIntersecting) { 8 entry.target.classList.add('animated'); 9 } 10 }); 11 }, { 12 threshold: 0.1, 13 rootMargin: '0px 0px -50px 0px' 14 }); 15 16 function initAnimations() { 17 document.querySelectorAll('[data-animate]:not(.observed)').forEach(el => { 18 el.classList.add('observed'); 19 observer.observe(el); 20 }); 21 } 22 23 // Counter animation for metrics section. 24 // Guard with [data-counted] to prevent re-initialization from MutationObserver. 25 function animateCounters() { 26 document.querySelectorAll('[data-counter]:not([data-counted])').forEach(el => { 27 el.setAttribute('data-counted', ''); 28 29 const target = el.getAttribute('data-counter'); 30 const suffix = el.getAttribute('data-suffix') || ''; 31 const prefix = el.getAttribute('data-prefix') || ''; 32 const num = parseInt(target, 10); 33 34 if (isNaN(num)) { 35 el.textContent = prefix + target + suffix; 36 return; 37 } 38 39 const counterObserver = new IntersectionObserver((entries) => { 40 entries.forEach(entry => { 41 if (entry.isIntersecting) { 42 let current = 0; 43 const step = Math.max(1, Math.floor(num / 40)); 44 const timer = setInterval(() => { 45 current += step; 46 if (current >= num) { 47 current = num; 48 clearInterval(timer); 49 } 50 el.textContent = prefix + current + suffix; 51 }, 30); 52 counterObserver.unobserve(entry.target); 53 } 54 }); 55 }, { threshold: 0.5 }); 56 57 counterObserver.observe(el); 58 }); 59 } 60 61 // Init on DOM ready and after Leptos hydration 62 if (document.readyState === 'loading') { 63 document.addEventListener('DOMContentLoaded', () => { initAnimations(); animateCounters(); }); 64 } else { 65 initAnimations(); 66 animateCounters(); 67 } 68 69 // Re-init after Leptos client-side navigation (debounced) 70 let reinitTimer = null; 71 const bodyObserver = new MutationObserver(() => { 72 if (reinitTimer) return; 73 reinitTimer = setTimeout(() => { 74 reinitTimer = null; 75 initAnimations(); 76 animateCounters(); 77 }, 100); 78 }); 79 bodyObserver.observe(document.body, { childList: true, subtree: true }); 80})();
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.