Studio hours booked per city as towers, each with its share on a rooftop sign. Switch the year and the towers grow or shrink to it.
registry/demos/skyline-stats-demo.tsx
"use client";
import * as React from "react";
import { Skyline, type SkylineTower } from "@/components/ui/skyline";
// Hours of studio time booked through Roomtone, by city.
const YEARS = {
"2025": [
{ label: "Lisbon", value: 1420 },
{ label: "Berlin", value: 2980 },
{ label: "Lagos", value: 860 },
{ label: "Seoul", value: 3310 },
{ label: "Mexico City", value: 1890 },
{ label: "Toronto", value: 2240 },
{ label: "Melbourne", value: 1310 },
],
"2026": [
{ label: "Lisbon", value: 2160 },
{ label: "Berlin", value: 3120 },
{ label: "Lagos", value: 1940 },
{ label: "Seoul", value: 4050 },
{ label: "Mexico City", value: 2460 },
{ label: "Toronto", value: 2380 },
{ label: "Melbourne", value: 1720 },
],
} satisfies Record<string, { label: string; value: number }[]>;
type Year = keyof typeof YEARS;
export function SkylineStatsDemo() {
const [year, setYear] = React.useState<Year>("2026");
const total = YEARS[year].reduce((sum, c) => sum + c.value, 0);
const towers: SkylineTower[] = YEARS[year].map((c) => ({
...c,
display: `${c.value.toLocaleString("en-US")} h · ${Math.round((c.value / total) * 100)}%`,
}));
return (
<div className="w-full max-w-3xl rounded-2xl border border-zinc-950/10 bg-white p-5 text-zinc-900 shadow-sm sm:p-7 dark:border-white/10 dark:bg-zinc-950 dark:text-zinc-100">
<div className="flex flex-wrap items-end justify-between gap-4">
<div>
<h3 className="font-serif text-2xl tracking-[-0.015em] sm:text-[1.75rem]">
Where the studios were <em className="text-[#2747ff] dark:text-[#8292ff]">busiest</em>
</h3>
<p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
Hours booked on Roomtone in {year},{" "}
<span className="font-medium text-zinc-900 tabular-nums dark:text-zinc-100">
{total.toLocaleString("en-US")} h
</span>{" "}
in all. Hover a city for its share.
</p>
</div>
<div
role="radiogroup"
aria-label="Year"
className="flex rounded-full border border-zinc-950/10 p-0.5 text-sm dark:border-white/10"
>
{(Object.keys(YEARS) as Year[]).map((y) => (
<button
key={y}
type="button"
role="radio"
aria-checked={year === y}
onClick={() => setYear(y)}
className="h-8 rounded-full px-3.5 font-medium tabular-nums text-zinc-500 transition-colors duration-150 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-900 aria-checked:bg-zinc-900 aria-checked:text-white dark:text-zinc-400 dark:hover:text-zinc-100 dark:focus-visible:outline-zinc-100 dark:aria-checked:bg-zinc-100 dark:aria-checked:text-zinc-900"
>
{y}
</button>
))}
</div>
</div>
<Skyline
towers={towers}
seed={4}
sun={0.86}
trees={false}
label={`Studio hours booked by city in ${year}`}
className="mt-6 h-72 text-zinc-900 [--skyline-sun:#ff5a1f] [--skyline-tint:#bcc7ff] sm:h-80 dark:text-zinc-100 dark:[--skyline-sun:#ff6a2b] dark:[--skyline-tint:#202c78]"
/>
</div>
);
}
Live preview, examples and props: https://keyframeui.com/components/skyline#skyline-stats-demo