1/** 2 * iBuyer KPI Cards â block-local behavior. 3 * 4 * Progressive enhancement only. The server renders the final figures, so the 5 * card is complete and correct without this script. When the card scrolls into 6 * view, any metric value that is a plainly formatted thousands number counts up 7 * to the figure already in the markup. A value the script cannot reproduce 8 * character for character is left exactly as the editor typed it. 9 * 10 * Skipped entirely for readers who prefer reduced motion. Initializes once per 11 * block root and releases its observer when the root leaves its document. 12 * 13 * Frontend only: block.json registers this as viewScript, so ACF editor 14 * previews show the static server-rendered values. The clvr-tooltip element in 15 * the highlight row is driven by the shared runtime, not by this file. 16 */ 17(function () { 18 'use strict'; 19 20 var ROOT_SELECTOR = '.ibuyer-kpi-cards-block.clvr-scope[data-ibuyer-kpi-cards]'; 21 var DURATION = 1200; 22 var instances = new WeakMap(); 23 24 /** 25 * Split "$313,500" into its prefix, numeric value and suffix, but only when 26 * the number can be re-rendered to exactly the original digits. Anything 27 * else returns null so the value is never silently reformatted. 28 */ 29 function parseValue(text) { 30 var match = text.match(/^(\D*)(\d[\d,]*)(\D*)$/); 31 if (!match) { 32 return null; 33 } 34 var digits = match[2]; 35 var amount = parseInt(digits.replace(/,/g, ''), 10); 36 if (!isFinite(amount) || amount < 1000) { 37 return null; 38 } 39 if (amount.toLocaleString('en-US') !== digits) { 40 return null; 41 } 42 return { prefix: match[1], amount: amount, suffix: match[3] }; 43 } 44 45 function init(root) { 46 if (!root || !root.matches || !root.matches(ROOT_SELECTOR) || !root.isConnected) { 47 return null; 48 } 49 var doc = root.ownerDocument; 50 var win = doc.defaultView; 51 if (!win) { 52 return null; 53 } 54 var previous = instances.get(root); 55 if (previous && previous.document === doc) { 56 return previous; 57 } 58 if (previous) { 59 previous.destroy(); 60 } 61 62 var reduceMotion = win.matchMedia && win.matchMedia('(prefers-reduced-motion: reduce)').matches; 63 var frames = []; 64 var observer = null; 65 66 var controller = { 67 document: doc, 68 destroy: function () { 69 if (observer) { 70 observer.disconnect(); 71 observer = null; 72 } 73 frames.forEach(function (id) { win.cancelAnimationFrame(id); }); 74 frames = []; 75 instances.delete(root); 76 } 77 }; 78 instances.set(root, controller); 79 80 if (reduceMotion || typeof win.IntersectionObserver !== 'function') { 81 return controller; 82 } 83 84 // Only values inside this instance, never a nested independent scope. 85 var targets = Array.prototype.filter.call( 86 root.querySelectorAll('[data-kpi-count]'), 87 function (el) { return el.closest('.clvr-scope') === root; } 88 ); 89 90 var animatable = targets.map(function (el) { 91 var parsed = parseValue(el.textContent.trim()); 92 return parsed ? { el: el, parsed: parsed } : null; 93 }).filter(Boolean); 94 95 if (animatable.length === 0) { 96 return controller; 97 } 98 99 function animate(entry) { 100 var el = entry.el; 101 var parsed = entry.parsed; 102 var started = 0; 103 104 function step(now) { 105 if (!el.isConnected || el.ownerDocument !== doc) { 106 return; 107 } 108 if (!started) { 109 started = now; 110 } 111 var progress = Math.min((now - started) / DURATION, 1); 112 var eased = 1 - Math.pow(1 - progress, 3); 113 var current = Math.round(parsed.amount * eased); 114 el.textContent = parsed.prefix + current.toLocaleStr
114ing('en-US') + parsed.suffix; 115 if (progress < 1) { 116 frames.push(win.requestAnimationFrame(step)); 117 } 118 } 119 120 frames.push(win.requestAnimationFrame(step)); 121 } 122 123 observer = new win.IntersectionObserver(function (entries) { 124 entries.forEach(function (observed) { 125 if (!observed.isIntersecting) { 126 return; 127 } 128 observer.unobserve(observed.target); 129 var entry = animatable.filter(function (candidate) { 130 return candidate.el === observed.target; 131 })[0]; 132 if (entry) { 133 animate(entry); 134 } 135 }); 136 }, { threshold: 0.3 }); 137 138 animatable.forEach(function (entry) { observer.observe(entry.el); }); 139 140 return controller; 141 } 142 143 function initWithin(container) { 144 if (!container || !container.querySelectorAll) { 145 return; 146 } 147 if (container.matches && container.matches(ROOT_SELECTOR)) { 148 init(container); 149 } 150 Array.prototype.forEach.call(container.querySelectorAll(ROOT_SELECTOR), init); 151 } 152 153 // Frontend: the script is deferred, so the DOM is parsed. 154 if (document.readyState === 'loading') { 155 document.addEventListener('DOMContentLoaded', function () { initWithin(document); }, { once: true }); 156 } else { 157 initWithin(document); 158 } 159})();
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.