1/** 2 * Stats Bar Count-Up Animation 3 * Triggers when section scrolls into viewport 4 * Respects prefers-reduced-motion accessibility setting 5 */ 6(function() { 7 'use strict'; 8 9 const CONFIG = { 10 duration: 2000, // 2 seconds 11 threshold: 0.5, // 50% visible before triggering 12 rootMargin: '0px' 13 }; 14 15 function initStatsCountUp() { 16 // Skip animation if user prefers reduced motion 17 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { 18 return; 19 } 20 21 const statElements = document.querySelectorAll('[data-countup="true"]'); 22 if (!statElements.length) return; 23 24 const observer = new IntersectionObserver(handleIntersection, { 25 threshold: CONFIG.threshold, 26 rootMargin: CONFIG.rootMargin 27 }); 28 29 statElements.forEach(el => observer.observe(el)); 30 } 31 32 function handleIntersection(entries, observer) { 33 entries.forEach(entry => { 34 if (entry.isIntersecting && !entry.target.dataset.animated) { 35 animateCounter(entry.target); 36 entry.target.dataset.animated = 'true'; 37 // Unobserve after animation to free memory 38 observer.unobserve(entry.target); 39 } 40 }); 41 } 42 43 function animateCounter(element) { 44 const targetStr = element.dataset.target || '0'; 45 const suffix = element.dataset.suffix || ''; 46 47 // Parse number (remove commas) 48 const cleanNum = targetStr.replace(/,/g, ''); 49 const target = parseFloat(cleanNum); 50 51 if (isNaN(target) || target === 0) { 52 element.textContent = targetStr + suffix; 53 return; 54 } 55 56 const startTime = performance.now(); 57 const hasDecimal = targetStr.includes('.'); 58 59 function update(currentTime) { 60 const elapsed = currentTime - startTime; 61 const progress = Math.min(elapsed / CONFIG.duration, 1); 62 63 // Cubic ease-out: smooth deceleration 64 const easeOut = 1 - Math.pow(1 - progress, 3); 65 const current = target * easeOut; 66 67 // Format based on original number type 68 const formatted = hasDecimal 69 ? current.toFixed(1) 70 : Math.floor(current).toLocaleString(); 71 72 element.textContent = formatted + suffix; 73 74 if (progress < 1) { 75 requestAnimationFrame(update); 76 } else { 77 // Set final value with original formatting 78 element.textContent = targetStr + suffix; 79 } 80 } 81 82 requestAnimationFrame(update); 83 } 84 85 // Initialize on DOM ready 86 if (document.readyState === 'loading') { 87 document.addEventListener('DOMContentLoaded', initStatsCountUp); 88 } else { 89 initStatsCountUp(); 90 } 91})();
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.