"use client"; /** * METRICS scoreboard. * - CountUp numbers animate on view (Motion). * - A second ANIMATED-SVG moment: a bar chart whose bars DRAW/grow + a baseline * that draws itself (GSAP DrawSVG) when the section scrolls in. * Reduced-motion: numbers jump to final, bars render at full height (CSS). */ import { useEffect, useRef } from "react"; import { gsap } from "./gsap"; import CountUp from "./CountUp"; import { metrics } from "../content"; const BARS = [38, 56, 72, 64, 88, 96]; // decorative growth bars (0..100) export default function Scoreboard() { const root = useRef(null); useEffect(() => { const el = root.current; if (!el) return; const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (reduce) return; const ctx = gsap.context(() => { const tl = gsap.timeline({ scrollTrigger: { trigger: ".score__chart", start: "top 82%", once: true }, }); // Baseline draws first, then bars grow up off it with a tight stagger and // a soft landing (back ease => a hair of overshoot so they feel physical). tl.from(".score__baseline", { drawSVG: "0%", duration: 0.8, ease: "power2.out", }) .from( ".score-bar", { scaleY: 0, transformOrigin: "bottom", duration: 1, ease: "back.out(1.4)", stagger: 0.07, }, "-=0.45" ) .from( ".score-bar__cap", { scale: 0, autoAlpha: 0, transformOrigin: "center", duration: 0.5, ease: "back.out(2.4)", stagger: 0.07, }, "-=0.9" ); }, el); return () => ctx.revert(); }, []); return (

The scoreboard — sample data

Numbers we report on

{metrics.map((m) => (
{m.label}
))}
{/* decorative animated bar chart */}
); }