1/** 2 * OSC design-system motion engine â scroll reveals, stat counters, bar draws. 3 * 4 * Deliberately follows the proven osc-cinematic.js pattern for THIS site: 5 * NO IntersectionObserver, NO requestAnimationFrame (both observed dead in the 6 * driven-browser env on live OSC pages) â a single passive native `scroll` 7 * listener + getBoundingClientRect, throttled with a dirty-flag + setTimeout. 8 * 9 * Contract with motion.css: 10 * - Base markup is 100% visible. This script ADDS .osc-anim-prep (hide) only to 11 * [data-osc-anim] elements fully below the fold at boot, then .osc-anim-in 12 * (reveal transition) as they cross 88% of the viewport. 13 * - Left under WP Rocket delay-JS ON PURPOSE: absent/late JS costs nothing; 14 * delay-JS releases on first scroll â exactly when reveals become relevant. 15 * - prefers-reduced-motion: engine no-ops entirely (CSS also hard-overrides). 16 * - Counters ([data-osc-count]): the final value is real HTML text (AEO truth). 17 * We snapshot the string, tick numerically, and restore the EXACT original. 18 * No innerHTML anywhere. No layout writes (transform/opacity only) â zero CLS. 19 */ 20(function () { 21 "use strict"; 22 if (window.__oscDsMotion) return; // idempotent under double-enqueue 23 window.__oscDsMotion = 1; 24 var mq = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)"); 25 if (mq && mq.matches) return; 26 27 var THRESH = 0.88; // reveal when element top crosses 88% of viewport height 28 var items = []; // { el, counted } 29 var dirty = false, timer = null, bound = false; 30 31 function collect() { 32 var nodes = document.querySelectorAll("[data-osc-anim]"); 33 var vh = window.innerHeight || 0; 34 for (var i = 0; i < nodes.length; i++) { 35 var el = nodes[i]; 36 if (el.__oscDs) continue; 37 el.__oscDs = 1; 38 var top = el.getBoundingClientRect().top; 39 // Only elements FULLY below the fold ever get hidden â above-fold and 40 // partially visible content is never touched (no boot flash, no CLS). 41 if (top >= vh) { 42 var d = parseInt(el.getAttribute("data-osc-anim-delay") || "0", 10); 43 if (d > 0) el.style.transitionDelay = d + "ms"; 44 el.classList.add("osc-anim-prep"); 45 items.push(el); 46 } else if (el.hasAttribute("data-osc-count") || el.querySelector("[data-osc-count]")) { 47 // Above-fold counters still tick once, from their visible state. 48 runCounters(el); 49 } 50 } 51 } 52 53 function parseNum(s) { 54 var m = String(s).match(/-?\d[\d,]*\.?\d*/); 55 if (!m) return null; 56 return { raw: m[0], idx: m.index, val: parseFloat(m[0].replace(/,/g, "")) }; 57 } 58 59 function runCounters(scope) { 60 var list = scope.hasAttribute && scope.hasAttribute("data-osc-count") ? [scope] : []; 61 var found = scope.querySelectorAll ? scope.querySelectorAll("[data-osc-count]") : []; 62 for (var i = 0; i < found.length; i++) list.push(found[i]); 63 for (var j = 0; j < list.length; j++) (function (el) { 64 if (el.__oscDsCounted) return; 65 el.__oscDsCounted = 1; 66 var original = el.textContent; // exact restore target (AEO truth) 67 var n = parseNum(original); 68 if (!n || !isFinite(n.val)) return; 69 var decimals = (n.raw.split(".")[1] || "").length; 70 var hasComma = n.raw.indexOf(",") !== -1; 71 var steps = 18, step = 0; 72 var iv = setInterval(function () { 73 step++; 74 if (step >= steps) { clearInterval(iv); el.textContent = original; return; } 75 var eased = 1 - Math.pow(1 - step / steps, 3); 76 var v = (n.val * eased).toFixed(decimals); 77 if (hasComma) v = v.replace(/\B(?=(\d{3})+(?!\d))/g, ","); 78 el.textContent = original.slice(0, n.idx) + v + original.slice(n.idx + n.raw.length); 79 }, 700 / steps); 80 })(list[j]); 81 } 82 83 function check() { 84 var vh = window.innerHeight || 0; 85 var remaining = []; 86 for (var i = 0; i < items.length; i++) { 87 var el = items[i]; 88 if (el.getBoundingClientRect().top < vh * THRESH) { 89 el.classList.add("osc-anim-in"); 90 runCounters(el); 91 } else { 92 remaining.push(el); 93 } 94 } 95 items = remaining; 96 if (!items.length && bound) { 97 window.removeEventListener("scroll", onScroll); 98 window.removeEventListener("resize", onScroll); 99 bound = false;
100 } 101 } 102 103 function onScroll() { 104 if (dirty) return; 105 dirty = true; 106 timer = setTimeout(function () { dirty = false; check(); }, 80); 107 } 108 109 function boot() { 110 collect(); 111 if (!items.length) return; 112 window.addEventListener("scroll", onScroll, { passive: true }); 113 window.addEventListener("resize", onScroll, { passive: true }); 114 bound = true; 115 check(); // settle anything already near the threshold at boot 116 } 117 118 if (document.readyState === "loading") { 119 document.addEventListener("DOMContentLoaded", boot); 120 } else { 121 boot(); 122 } 123})();
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.