1/** 2 * Impact Stats Counter Animation 3 * 4 * Animates numbers counting up when they come into view 5 */ 6 7(function (Drupal) { 8 'use strict'; 9 10 Drupal.behaviors.impactStatsCounter = { 11 attach: function (context, settings) { 12 const counters = context.querySelectorAll('.milestone-number'); 13 14 // Check if IntersectionObserver is supported 15 if (!('IntersectionObserver' in window)) { 16 return; 17 } 18 19 const observer = new IntersectionObserver((entries) => { 20 entries.forEach(entry => { 21 if (entry.isIntersecting && !entry.target.classList.contains('counted')) { 22 animateCounter(entry.target); 23 entry.target.classList.add('counted'); 24 } 25 }); 26 }, { 27 threshold: 0.5 28 }); 29 30 counters.forEach(counter => { 31 observer.observe(counter); 32 }); 33 34 function animateCounter(element) { 35 const target = element.getAttribute('data-count'); 36 if (!target) return; 37 38 // Extract number and suffix (e.g., "14M" -> 14 and "M") 39 const match = target.match(/^([\d,.]+)(.*)$/); 40 if (!match) { 41 return; 42 } 43 44 const numberPart = match[1].replace(/,/g, ''); 45 const suffix = match[2]; 46 const targetValue = parseFloat(numberPart); 47 48 if (isNaN(targetValue)) { 49 return; 50 } 51 52 const duration = 2000; // 2 seconds 53 const steps = 60; 54 const increment = targetValue / steps; 55 const stepDuration = duration / steps; 56 let current = 0; 57 let step = 0; 58 59 const timer = setInterval(() => { 60 step++; 61 current += increment; 62 63 if (step >= steps) { 64 current = targetValue; 65 clearInterval(timer); 66 } 67 68 // Format the number WITHOUT thousands separators 69 let displayValue = Math.floor(current); 70 71 element.textContent = displayValue + suffix; 72 }, stepDuration); 73 } 74 } 75 }; 76 77})(Drupal);
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.