1(() => { 2 const scgHelper = window.sedgwickAnimationHelper || {}; 3 const scgCountUp = typeof scgHelper.countUp === 'function' ? scgHelper.countUp : null; 4 const scgEaseOutCubic = typeof scgHelper.easeOutCubic === 'function' ? scgHelper.easeOutCubic : (t) => 1 - Math.pow(1 - t, 3); 5 6 document.addEventListener('DOMContentLoaded', function () { 7 const grids = document.querySelectorAll('.readiness-gap-animation-block'); 8 if (!grids.length) { 9 return; 10 } 11 12 const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 13 const isMobile = window.matchMedia('(max-width: 767px)').matches; 14 15 grids.forEach((grid) => { 16 const cards = Array.from(grid.querySelectorAll('.stat-card-block')); 17 if (!cards.length) { 18 return; 19 } 20 21 cards.forEach((card) => prepareCard(card, prefersReducedMotion)); 22 23 if (prefersReducedMotion) { 24 return; 25 } 26 27 if (isMobile) { 28 const cardObserver = new IntersectionObserver( 29 (entries, obs) => { 30 entries.forEach((entry) => { 31 if (entry.isIntersecting) { 32 const card = entry.target; 33 animateCard(card); 34 obs.unobserve(card); 35 } 36 }); 37 }, 38 { 39 root: null, 40 threshold: 0.25, 41 }, 42 ); 43 44 cards.forEach((card) => cardObserver.observe(card)); 45 return; 46 } 47 48 let hasAnimated = false; 49 50 const observer = new IntersectionObserver( 51 (entries, obs) => { 52 entries.forEach((entry) => { 53 if (entry.isIntersecting && !hasAnimated) { 54 hasAnimated = true; 55 obs.unobserve(entry.target); 56 animateCardsSequentially(cards); 57 } 58 }); 59 }, 60 { 61 root: null, 62 threshold: 0.25, 63 }, 64 ); 65 66 observer.observe(grid); 67 }); 68 }); 69 70 function prepareCard(card, prefersReducedMotion) { 71 const heading = card.querySelector('.stat-card-heading span, .stat-card-heading'); 72 const parsed = heading ? parseNumberValue(heading.textContent) : null; 73 74 if (heading && parsed) { 75 heading.dataset.statPrefix = parsed.prefix; 76 heading.dataset.statSuffix = parsed.suffix; 77 heading.dataset.statNumber = parsed.number; 78 heading.dataset.statDecimals = parsed.decimals; 79 } 80 81 if (!prefersReducedMotion) { 82 if (heading && parsed) { 83 heading.textContent = `${parsed.prefix}0${parsed.suffix}`; 84 } 85 card.classList.add('stat-card-hidden'); 86 } else { 87 card.style.transition = 'none'; 88 card.classList.add('stat-card-visible'); 89 card.classList.remove('stat-card-hidden'); 90 91 if (heading && parsed) { 92 const prefix = parsed.prefix || ''; 93 const suffix = parsed.suffix || ''; 94 const decimals = Number.isFinite(parsed.decimals) ? parsed.decimals : 0; 95 heading.textContent = `${prefix}${parsed.number.toFixed(decimals)}${suffix}`; 96 } 97 } 98 } 99 100 function animateCardsSequentially(cards) { 101 const delayBetweenCards = 750; 102 103 cards.forEach((card, index) => { 104 const delay = index * delayBetweenCards; 105 106 setTimeout(() => { 107 animateCard(card); 108 }, delay); 109 }); 110 } 111 112 function animateCard(card) { 113 const heading = card.querySelector('.stat-card-heading span, .stat-card-heading'); 114 115 card.classList.add('stat-card-visible'); 116 card.classList.remove('stat-card-hidden'); 117 118 if (heading && heading.dataset.statNumber) { 119 const targetNumber = parseFloat(heading.dataset.statNumber); 120 121 if (Number.isNaN(targetNumber)) { 122 return; 123 } 124 125 const prefix = heading.dataset.statPrefix || ''; 126 const suffix = heading.dataset.statSuffix || ''; 127 const decimals = parseInt(heading.dataset.statDecimals || '0', 10); 128 129 if (scgCountUp) { 130 scgCountUp(heading, { 131 end: targetNumber, 132 duration: 1500, 133 prefix, 134 suffix, 135 decimals, 136 easing: scgEaseOutCubic, 137 }); 138 } else { 139 animateCountUpFallback(heading, targetNumber, prefix, suffix, decimals); 140 } 141 } 142 } 143 144 function animateCountUpFallback(heading, targetNumber, prefix, suffix, decimals) { 145 const decimalPlaces = Number.isFinite(decimals) ? decimals : 0; 146 const duration = 1500; 147 148 let startTime = null; 149 150 const step = (timestamp) => { 151 if (!startTime) { 152 startTime = timestamp; 153 } 154 155 const progress = Math.min((timestamp - startTime) / duration, 1); 156 const easedProgress = scgEaseOutCubic(progress); 157 const currentValue = targetNumber * easedProgress; 158 159 heading.textContent = `${prefix}${currentValue.toFixed(decimalPlaces)}${suffix}`; 160 161 if (progress < 1) { 162 requestAnimationFrame(step); 163 } 164 }; 165 166 requestAnimationFrame(step); 167 } 168 169 function parseNumberValue(rawValue) { 170 if (!rawValue) { 171 return null; 172 } 173 174 const trimmed = rawValue.trim(); 175 const match = trimmed.match(/(-?[\d.,]+(?:\.\d+)?)/); 176 177 if (!match) { 178 return null; 179 } 180 181 const numberPortion = match[1].replace(/,/g, ''); 182 const number = parseFloat(numberPortion); 183 184 if (Number.isNaN(number)) { 185 return null; 186 } 187 188 const decimals = (numberPortion.split('.')[1] || '').length; 189 const prefix = trimmed.slice(0, match.index); 190 const suffix = trimmed.slice(match.index + match[1].length); 191 192 return { number, prefix, suffix, decimals }; 193 } 194})();
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.