"use client"; /** * METRICS scoreboard. * - The four headline metrics count up on view (Motion) and are now the focus: * each cell is an interactive tile with a hover lift, an accent edge that * wipes in, and a tiny per-metric sparkline that animates. * - The previously-meaningless decorative bars are replaced with a MEANINGFUL, * LABELLED growth trend: a 6-month "client revenue" area+line chart with real * month ticks, a value axis, and interactive data points that enlarge + show * a tooltip on hover/focus. It communicates the compounding-growth story * instead of being filler. * Reduced-motion: numbers rest at final value, chart renders fully drawn, no * count-up, no draw animation. */ import { useEffect, useRef, useState } from "react"; import { gsap } from "./gsap"; import CountUp from "./CountUp"; import { metrics } from "../content"; /* 6-month sample "client revenue generated" trend (index, $M). Compounding curve that mirrors the "$40M+ generated / 3.8x ROAS" headline story. */ const TREND = [ { m: "Jan", v: 2.1 }, { m: "Feb", v: 3.4 }, { m: "Mar", v: 5.0 }, { m: "Apr", v: 7.8 }, { m: "May", v: 11.2 }, { m: "Jun", v: 16.4 }, ]; // tiny per-metric sparkline shapes (each different, matching the metric's story) const SPARKS: string[] = [ "0,18 20,16 40,12 60,11 80,6 100,2", // revenue — steady climb "0,16 20,14 40,15 60,9 80,7 100,3", // ROAS — climbing with a dip "0,19 20,17 40,12 60,10 80,5 100,1", // organic — strong ramp "0,8 20,7 40,8 60,6 80,7 100,5", // retention — high + stable ]; const W = 560; const H = 200; const PAD = { l: 38, r: 16, t: 18, b: 30 }; const maxV = Math.max(...TREND.map((d) => d.v)); function pt(i: number, v: number) { const x = PAD.l + (i / (TREND.length - 1)) * (W - PAD.l - PAD.r); const y = PAD.t + (1 - v / maxV) * (H - PAD.t - PAD.b); return { x, y }; } export default function Scoreboard() { const root = useRef(null); const [hover, setHover] = useState(null); const pts = TREND.map((d, i) => pt(i, d.v)); const linePath = pts.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" "); const areaPath = `${linePath} L${pts[pts.length - 1].x},${H - PAD.b} L${pts[0].x},${H - PAD.b} Z`; // y-axis gridlines at 0 / 50% / 100% of max const yTicks = [0, 0.5, 1].map((f) => ({ f, y: PAD.t + (1 - f) * (H - PAD.t - PAD.b), label: `$${(maxV * f).toFixed(0)}M`, })); 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 }, }); // gridlines fade, the area wipes up, the line draws itself, points pop in, // and each metric sparkline draws — a coherent "data resolving" moment. tl.from(".score__grid line", { autoAlpha: 0, duration: 0.5, stagger: 0.08 }) .from(".score__area", { autoAlpha: 0, yPercent: 8, duration: 0.8, ease: "power2.out" }, "-=0.2") .from(".score__line", { drawSVG: "0%", duration: 1.4, ease: "power2.inOut" }, "-=0.7") .from( ".score__pt", { scale: 0, transformOrigin: "center", duration: 0.5, ease: "back.out(2.2)", stagger: 0.08 }, "-=0.6" ) .from( ".score__mlabel", { autoAlpha: 0, y: 6, duration: 0.4, stagger: 0.06 }, "-=0.8" ); gsap.from(".score__spark polyline", { drawSVG: "0%", duration: 1, ease: "power2.out", stagger: 0.12, scrollTrigger: { trigger: ".score__grid-stats", start: "top 85%", once: true }, }); }, el); return () => ctx.revert(); }, []); return (

The scoreboard — sample data

Numbers we report on

{/* four headline metrics — the focus; interactive tiles with sparklines */}
{metrics.map((m, i) => (
{m.label}
))}
{/* meaningful labelled growth trend — replaces the decorative bars */}
Client revenue generated — cumulative, sample 6-month engagement
{/* y gridlines + value labels */} {yTicks.map((t) => ( ))} {/* interactive data points + month labels */} {TREND.map((d, i) => { const p = pts[i]; const on = hover === i; return ( setHover(i)} onMouseLeave={() => setHover(null)} onFocus={() => setHover(i)} onBlur={() => setHover(null)} tabIndex={0} role="img" aria-label={`${d.m}: $${d.v}M`} > {/* invisible larger hit area */} {d.m} {on && ( ${d.v}M )} ); })}

Sample data — your numbers, reported monthly.

); }