Map Stat
Original · freestats with a dotted world map
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/statx-map.json"use client";
import { type KeyboardEvent, useMemo, useRef, useState } from "react";
import { motion, useReducedMotion } from "motion/react";
type MetricId = "users" | "requests" | "growth";
type Region = {
id: string;
name: string;
hub: string;
c: number;
r: number;
users: number;
requestsM: number;
growth: number;
latency: number;
};
type Ellipse = { cx: number; cy: number; rx: number; ry: number };
const COLS = 60;
const ROWS = 28;
const STEP = 10;
const PAD = 10;
const VB_W = (COLS - 1) * STEP + PAD * 2;
const VB_H = (ROWS - 1) * STEP + PAD * 2;
const LAND: Ellipse[] = [
{ cx: 10, cy: 5, rx: 7, ry: 3 },
{ cx: 9, cy: 8, rx: 5.5, ry: 3 },
{ cx: 13.5, cy: 11.5, rx: 2.6, ry: 2.2 },
{ cx: 24, cy: 2.5, rx: 2.2, ry: 2 },
{ cx: 19, cy: 17.5, rx: 3.4, ry: 3 },
{ cx: 18.5, cy: 22, rx: 2.3, ry: 4 },
{ cx: 30, cy: 6, rx: 3, ry: 2.4 },
{ cx: 31.5, cy: 12.5, rx: 3.6, ry: 2.6 },
{ cx: 32.5, cy: 17.5, rx: 3, ry: 3 },
{ cx: 44, cy: 6, rx: 10, ry: 4 },
{ cx: 39.5, cy: 9.5, rx: 3, ry: 2 },
{ cx: 46.5, cy: 11.5, rx: 3.6, ry: 2.4 },
{ cx: 51.5, cy: 9, rx: 3.2, ry: 2.6 },
{ cx: 49.5, cy: 14, rx: 2.4, ry: 1.6 },
{ cx: 52.5, cy: 21, rx: 3.4, ry: 2.4 },
];
function inLand(c: number, r: number): boolean {
for (const e of LAND) {
const dx = (c - e.cx) / e.rx;
const dy = (r - e.cy) / e.ry;
if (dx * dx + dy * dy <= 1) return true;
}
return false;
}
const DOTS: { x: number; y: number }[] = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (inLand(c, r)) DOTS.push({ x: c * STEP, y: r * STEP });
}
}
const REGIONS: Region[] = [
{ id: "na", name: "North America", hub: "Ashburn · Toronto", c: 10, r: 8, users: 942000, requestsM: 3100, growth: 14.2, latency: 28 },
{ id: "eu", name: "Europe", hub: "Frankfurt · Dublin", c: 30, r: 6, users: 655000, requestsM: 2400, growth: 11.4, latency: 22 },
{ id: "apac", name: "Asia Pacific", hub: "Singapore · Tokyo", c: 47, r: 10, users: 806000, requestsM: 2600, growth: 29.7, latency: 39 },
{ id: "latam", name: "Latin America", hub: "São Paulo · Bogotá", c: 19, r: 19, users: 318000, requestsM: 690, growth: 38.6, latency: 46 },
{ id: "mea", name: "Middle East & Africa", hub: "Nairobi · Dubai", c: 32, r: 15, users: 174000, requestsM: 410, growth: 52.3, latency: 61 },
{ id: "oce", name: "Oceania", hub: "Sydney · Auckland", c: 52, r: 21, users: 96000, requestsM: 240, growth: 19.8, latency: 44 },
];
const METRICS: { id: MetricId; label: string }[] = [
{ id: "users", label: "Active devs" },
{ id: "requests", label: "Requests" },
{ id: "growth", label: "Growth" },
];
function formatUsers(n: number): string {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
if (n >= 1_000) return `${Math.round(n / 1_000)}K`;
return `${n}`;
}
function formatReq(m: number): string {
if (m >= 1000) return `${(m / 1000).toFixed(1).replace(/\.0$/, "")}B`;
return `${m}M`;
}
function metricValue(rg: Region, m: MetricId): number {
return m === "users" ? rg.users : m === "requests" ? rg.requestsM : rg.growth;
}
function metricLabel(v: number, m: MetricId): string {
return m === "users" ? formatUsers(v) : m === "requests" ? formatReq(v) : `+${v.toFixed(1)}%`;
}
function nodePct(c: number, r: number): { left: number; top: number } {
return {
left: ((c * STEP + PAD) / VB_W) * 100,
top: ((r * STEP + PAD) / VB_H) * 100,
};
}
export default function StatxMap() {
const reduce = useReducedMotion();
const [selectedId, setSelectedId] = useState<string>("na");
const [metric, setMetric] = useState<MetricId>("users");
const [hovered, setHovered] = useState<string | null>(null);
const optionRefs = useRef<(HTMLButtonElement | null)[]>([]);
const metricRefs = useRef<(HTMLButtonElement | null)[]>([]);
const selected = REGIONS.find((rg) => rg.id === selectedId) ?? REGIONS[0];
const stats = useMemo(() => {
const totalUsers = REGIONS.reduce((a, rg) => a + rg.users, 0);
const totalReq = REGIONS.reduce((a, rg) => a + rg.requestsM, 0);
const weighted = REGIONS.reduce((a, rg) => a + rg.users * rg.growth, 0) / totalUsers;
const sortedLat = REGIONS.map((rg) => rg.latency).sort((a, b) => a - b);
const mid = sortedLat.length / 2;
const median = Math.round((sortedLat[mid - 1] + sortedLat[mid]) / 2);
return { totalUsers, totalReq, weighted, median };
}, []);
const barMax = useMemo(
() => Math.max(...REGIONS.map((rg) => metricValue(rg, metric))),
[metric],
);
const onListKey = (e: KeyboardEvent<HTMLDivElement>) => {
const idx = REGIONS.findIndex((rg) => rg.id === selectedId);
let next = idx;
switch (e.key) {
case "ArrowRight":
case "ArrowDown":
next = (idx + 1) % REGIONS.length;
break;
case "ArrowLeft":
case "ArrowUp":
next = (idx - 1 + REGIONS.length) % REGIONS.length;
break;
case "Home":
next = 0;
break;
case "End":
next = REGIONS.length - 1;
break;
default:
return;
}
e.preventDefault();
optionRefs.current[next]?.focus();
};
const onMetricKey = (e: KeyboardEvent<HTMLDivElement>) => {
const idx = METRICS.findIndex((m) => m.id === metric);
let next = idx;
switch (e.key) {
case "ArrowRight":
case "ArrowDown":
next = (idx + 1) % METRICS.length;
break;
case "ArrowLeft":
case "ArrowUp":
next = (idx - 1 + METRICS.length) % METRICS.length;
break;
default:
return;
}
e.preventDefault();
setMetric(METRICS[next].id);
metricRefs.current[next]?.focus();
};
const kpis = [
{ label: "Monthly active developers", value: formatUsers(stats.totalUsers), delta: `+${stats.weighted.toFixed(1)}% QoQ`, up: true },
{ label: "Requests served / day", value: formatReq(stats.totalReq), delta: "38 edge locations", up: false },
{ label: "Regions online", value: `${REGIONS.length} / ${REGIONS.length}`, delta: "99.98% SLA", up: true },
{ label: "Median p95 latency", value: `${stats.median} ms`, delta: "−6 ms MoM", up: true },
];
return (
<section className="relative w-full overflow-hidden bg-white py-20 text-slate-900 sm:py-28 dark:bg-slate-950 dark:text-slate-100">
<style>{`
@keyframes statxmap-pulse {
0% { transform: scale(1); opacity: 0.5; }
70% { transform: scale(2.8); opacity: 0; }
100% { transform: scale(2.8); opacity: 0; }
}
@keyframes statxmap-flow {
to { stroke-dashoffset: -18; }
}
@keyframes statxmap-fade {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
.statxmap-fade { animation: statxmap-fade 0.45s ease both; }
@media (prefers-reduced-motion: reduce) {
.statxmap-pulse-ring,
.statxmap-flow-line,
.statxmap-fade { animation: none !important; }
}
`}</style>
<div
aria-hidden
className="pointer-events-none absolute inset-0"
style={{
background:
"radial-gradient(60% 50% at 50% -8%, rgba(99,102,241,0.16), transparent 70%)",
}}
/>
<div className="relative mx-auto max-w-6xl px-6">
<header className="max-w-2xl">
<span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-medium tracking-wide text-slate-600 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-400">
<span className="relative flex h-2 w-2">
<span
className="absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-70 statxmap-pulse-ring"
style={{ animation: reduce ? "none" : "statxmap-pulse 2.4s ease-out infinite", transformOrigin: "center" }}
/>
<span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
</span>
Global edge network · live
</span>
<h2 className="mt-5 text-3xl font-semibold tracking-tight sm:text-4xl">
Ship once. Serve every region in under 60 ms.
</h2>
<p className="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-400">
Real-time telemetry from the Meridian API platform — 2.99M active developers
routed through 38 points of presence across six regions. Select a region to
inspect its traffic.
</p>
</header>
<dl className="mt-10 grid grid-cols-2 gap-3 lg:grid-cols-4">
{kpis.map((k) => (
<div
key={k.label}
className="rounded-2xl border border-slate-200 bg-slate-50/60 p-4 dark:border-slate-800 dark:bg-slate-900/50"
>
<dt className="text-xs font-medium text-slate-500 dark:text-slate-400">{k.label}</dt>
<dd className="mt-2 text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl">
{k.value}
</dd>
<dd
className={`mt-1 text-xs font-medium ${
k.up ? "text-emerald-600 dark:text-emerald-400" : "text-slate-500 dark:text-slate-400"
}`}
>
{k.delta}
</dd>
</div>
))}
</dl>
<div className="mt-6 grid gap-6 lg:grid-cols-5">
{/* Map */}
<div className="lg:col-span-3">
<div className="relative overflow-hidden rounded-3xl border border-slate-200 bg-gradient-to-b from-slate-50 to-white p-5 dark:border-slate-800 dark:from-slate-900/60 dark:to-slate-950">
<div className="relative">
<svg
aria-hidden
viewBox={`${-PAD} ${-PAD} ${VB_W} ${VB_H}`}
className="block h-auto w-full"
>
{DOTS.map((d, i) => (
<circle
key={i}
cx={d.x}
cy={d.y}
r={1.5}
className="text-slate-300 dark:text-slate-700"
fill="currentColor"
/>
))}
{REGIONS.filter((rg) => rg.id !== selectedId).map((rg) => (
<line
key={rg.id}
x1={selected.c * STEP}
y1={selected.r * STEP}
x2={rg.c * STEP}
y2={rg.r * STEP}
className="statxmap-flow-line text-indigo-400 dark:text-indigo-500"
stroke="currentColor"
strokeWidth={0.9}
strokeOpacity={0.5}
strokeDasharray="1.5 4"
strokeLinecap="round"
style={{ animation: reduce ? "none" : "statxmap-flow 1.1s linear infinite" }}
/>
))}
</svg>
<div
role="listbox"
aria-label="Select a region to inspect"
onKeyDown={onListKey}
className="absolute inset-0"
>
{REGIONS.map((rg, i) => {
const active = rg.id === selectedId;
const p = nodePct(rg.c, rg.r);
const showChip = active || hovered === rg.id;
return (
<button
key={rg.id}
ref={(el) => {
optionRefs.current[i] = el;
}}
role="option"
aria-selected={active}
tabIndex={active ? 0 : -1}
onClick={() => setSelectedId(rg.id)}
onFocus={() => setSelectedId(rg.id)}
onMouseEnter={() => setHovered(rg.id)}
onMouseLeave={() => setHovered(null)}
style={{ left: `${p.left}%`, top: `${p.top}%` }}
className="group absolute h-6 w-6 -translate-x-1/2 -translate-y-1/2 rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
>
<span className="sr-only">
{rg.name}. {formatUsers(rg.users)} active developers,{" "}
{formatReq(rg.requestsM)} requests per day, up {rg.growth.toFixed(1)} percent,
p95 latency {rg.latency} milliseconds.
</span>
{active && (
<span
aria-hidden
className="statxmap-pulse-ring absolute inset-0 rounded-full bg-indigo-500/40"
style={{
animation: reduce ? "none" : "statxmap-pulse 2.2s ease-out infinite",
transformOrigin: "center",
}}
/>
)}
<span
aria-hidden
className={`absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full ring-2 transition-all ${
active
? "h-3.5 w-3.5 bg-indigo-500 ring-white dark:ring-slate-950"
: "h-2.5 w-2.5 bg-slate-400 ring-transparent group-hover:bg-indigo-400 dark:bg-slate-500"
}`}
/>
<span
aria-hidden
className={`pointer-events-none absolute bottom-full left-1/2 z-10 mb-2 -translate-x-1/2 whitespace-nowrap rounded-md border px-2 py-1 text-[11px] font-medium shadow-sm transition-opacity ${
showChip ? "opacity-100" : "opacity-0"
} border-slate-200 bg-white text-slate-700 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200`}
>
{rg.name} · {formatUsers(rg.users)}
</span>
</button>
);
})}
</div>
</div>
<div className="mt-4 flex items-center justify-between border-t border-slate-200 pt-4 text-xs text-slate-500 dark:border-slate-800 dark:text-slate-400">
<span className="inline-flex items-center gap-1.5">
<svg viewBox="0 0 16 16" className="h-3.5 w-3.5" fill="none" aria-hidden>
<circle cx="8" cy="8" r="6.5" stroke="currentColor" strokeWidth="1.4" />
<path d="M8 4.5v3.5l2.2 1.3" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
</svg>
Updated 2 min ago
</span>
<span className="tabular-nums">Use arrow keys to move between regions</span>
</div>
</div>
</div>
{/* Detail panel */}
<div className="lg:col-span-2">
<div className="flex h-full flex-col rounded-3xl border border-slate-200 bg-slate-50/60 p-6 dark:border-slate-800 dark:bg-slate-900/50">
<div key={selected.id} className="statxmap-fade">
<div className="flex items-baseline justify-between gap-3">
<h3 className="text-lg font-semibold tracking-tight">{selected.name}</h3>
<span className="rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] font-semibold text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400">
+{selected.growth.toFixed(1)}%
</span>
</div>
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">
Primary hubs · {selected.hub}
</p>
<div className="mt-5 grid grid-cols-2 gap-3">
<PanelStat label="Active developers" value={formatUsers(selected.users)} />
<PanelStat label="Requests / day" value={formatReq(selected.requestsM)} />
<PanelStat label="QoQ growth" value={`+${selected.growth.toFixed(1)}%`} accent />
<PanelStat label="p95 latency" value={`${selected.latency} ms`} />
</div>
</div>
<div className="mt-6 border-t border-slate-200 pt-5 dark:border-slate-800">
<div className="flex items-center justify-between">
<p className="text-xs font-medium text-slate-500 dark:text-slate-400">
Compare regions by
</p>
<div
role="radiogroup"
aria-label="Comparison metric"
onKeyDown={onMetricKey}
className="inline-flex rounded-lg border border-slate-200 bg-white p-0.5 dark:border-slate-700 dark:bg-slate-950"
>
{METRICS.map((m, i) => {
const on = m.id === metric;
return (
<button
key={m.id}
ref={(el) => {
metricRefs.current[i] = el;
}}
role="radio"
aria-checked={on}
tabIndex={on ? 0 : -1}
onClick={() => setMetric(m.id)}
className={`rounded-md px-2.5 py-1 text-[11px] font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 ${
on
? "bg-indigo-600 text-white"
: "text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-200"
}`}
>
{m.label}
</button>
);
})}
</div>
</div>
<ul className="mt-4 space-y-2.5">
{REGIONS.map((rg) => {
const v = metricValue(rg, metric);
const on = rg.id === selectedId;
return (
<li key={rg.id}>
<button
onClick={() => setSelectedId(rg.id)}
className="group grid w-full grid-cols-[7.5rem_1fr_auto] items-center gap-3 rounded-lg px-1 py-1 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
>
<span
className={`truncate text-xs font-medium ${
on ? "text-slate-900 dark:text-slate-100" : "text-slate-500 dark:text-slate-400"
}`}
>
{rg.name}
</span>
<span className="h-2 overflow-hidden rounded-full bg-slate-200 dark:bg-slate-800">
<motion.span
className={`block h-full rounded-full ${
on ? "bg-indigo-500" : "bg-slate-400 group-hover:bg-indigo-400 dark:bg-slate-600"
}`}
initial={false}
animate={{ width: `${(v / barMax) * 100}%` }}
transition={reduce ? { duration: 0 } : { type: "spring", stiffness: 90, damping: 18 }}
/>
</span>
<span
className={`w-14 text-right text-xs font-semibold tabular-nums ${
on ? "text-slate-900 dark:text-slate-100" : "text-slate-500 dark:text-slate-400"
}`}
>
{metricLabel(v, metric)}
</span>
</button>
</li>
);
})}
</ul>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
function PanelStat({ label, value, accent }: { label: string; value: string; accent?: boolean }) {
return (
<div className="rounded-xl border border-slate-200 bg-white p-3 dark:border-slate-800 dark:bg-slate-950">
<p className="text-[11px] font-medium text-slate-500 dark:text-slate-400">{label}</p>
<p
className={`mt-1 text-lg font-semibold tracking-tight tabular-nums ${
accent ? "text-emerald-600 dark:text-emerald-400" : ""
}`}
>
{value}
</p>
</div>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Four Stat Metric Band
OriginalA four-column metric band with gradient figures inside a hairline-divided card, ideal for headline numbers like uptime, latency and growth.

Labelled Stats With Dividers
OriginalThree centred stats separated by responsive dividers, each pairing a headline figure with an uppercase label and supporting detail.

Trusted By Logo Cloud
OriginalA responsive six-up logo strip using simple inline-SVG marks and wordmarks with a grayscale-to-colour hover, for a trusted-by section.

Hero Stat With Supporting Copy
OriginalA two-column layout pairing one oversized hero statistic with a supporting paragraph and a checklist card explaining the number.

Count-up stats reveal
OriginalA four-card stat band whose gradient numbers count up from zero as the section scrolls into view, with staggered blur-in cards, a pulsing eyebrow, floating aurora blobs and a shimmering underline sweep.

Animated SVG progress rings
OriginalFour circular SVG progress rings that draw their gradient arcs and count up to their target percentage the moment they enter view, over a slowly rotating conic halo with a soft glow pulse behind each ring.

Staggered stat band with marquee ticker
OriginalA dark stat band where 3D-tilt cards stagger into view with count-up figures over an animated gradient mesh, finished by an infinite marquee ticker of highlight chips that pauses on hover.

Counter Row Stat
Originalanimated counter stats row

Cards Stat
Originalstat cards with icons

Gradient Stat
Originalgradient stats band

Split Stat
Originalstats beside copy
Icons Stat
Originalicon stat grid

