Build a stats bar with Tailwind CSS: four evenly spaced metrics, each a large bold number and a muted label below. Separate them with vertical dividers on desktop. Center within a max-width wrapper and collapse to a 2x2 grid on mobile. Give each number data attributes describing its target value, decimal places, and any prefix or suffix, then add JavaScript that counts each metric up from zero when the row first scrolls into view, using an IntersectionObserver to trigger once and requestAnimationFrame with an ease-out curve to animate. Respect prefers-reduced-motion by snapping straight to the final values.
Paste it into Claude, Cursor, v0 or any AI coding tool. Prefer the
finished markup? .
Build a stats bar with Tailwind CSS: four evenly spaced metrics, each a large bold number and a muted label below. Separate them with vertical dividers on desktop. Center within a max-width wrapper and collapse to a 2x2 grid on mobile. Give each number data attributes describing its target value, decimal places, and any prefix or suffix, then add JavaScript that counts each metric up from zero when the row first scrolls into view, using an IntersectionObserver to trigger once and requestAnimationFrame with an ease-out curve to animate. Respect prefers-reduced-motion by snapping straight to the final values.
var DURATION = 1400;
var row = document.getElementById("stats");
var numbers = [].slice.call(row.querySelectorAll("[data-to]"));
var reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
function format(el, value) {
var decimals = Number(el.getAttribute("data-decimals") || 0);
var prefix = el.getAttribute("data-prefix") || "";
var suffix = el.getAttribute("data-suffix") || "";
return prefix + value.toFixed(decimals) + suffix;
}
function run() {
numbers.forEach(function (el) {
var target = Number(el.getAttribute("data-to"));
if (reduced) {
el.textContent = format(el, target);
return;
}
var start = 0;
var step = function (now) {
if (!start) start = now;
var t = Math.min(1, (now - start) / DURATION);
var eased = 1 - Math.pow(1 - t, 3); // ease-out cubic
el.textContent = format(el, target * eased);
if (t < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
});
}
// Count up once, the first time the row is actually on screen.
var seen = false;
var observer = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting || seen) return;
seen = true;
observer.disconnect();
run();
});
},
{ threshold: 0.4 }
);
observer.observe(row);