Files
hyperframes/docs/catalog/blocks/us-map.mdx
T
Miguel Ángel 9734578e60 feat(registry): bring back the video-primitive moves (#3169)
Restores the 208 catalog items reverted after their previews 404'd in
production, this time on the payload mechanism rather than the .html files
that caused the outage.

The generator no longer writes a preview document to docs/public. That writer,
and the machinery under it, existed only to produce files the docs host
discards, so it is gone rather than bypassed. Items now embed the composition
itself via a payload, which is what the previous change already does for the
items that were already in the catalog.

The variables explorer is parked, not restored: it drove its preview through
the same unpublished .html path, so it would have shown an empty frame. Items
that declare variables get the live player plus the static variables table, and
reconnecting the explorer to payloads is a follow-up.
2026-08-10 18:46:03 -04:00

449 lines
15 KiB
Plaintext

---
title: "US Map"
description: "Animated US choropleth map with staggered state reveals, value labels, and gradient legend — pure inline SVG with GSAP"
---
import { InstallCommand } from "/snippets/install-command.jsx";
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="us-map preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@0.7/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/us-map.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/us-map.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Install
<InstallCommand command="npx hyperframes add us-map" />
That writes one file: `compositions/us-map.html`.
## Source
<Accordion title={`us-map.html`}>
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>US Map</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=block"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/topojson-client@3.1.0/dist/topojson-client.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background-color: #0f172a;
}
</style>
</head>
<body>
<div
id="us-map"
data-composition-id="us-map"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="12"
>
<div class="map-container">
<div class="header">
<h1 class="headline">Population Density by State</h1>
<p class="subtitle">Residents per square mile, 2024 Census estimates</p>
</div>
<svg class="map-svg" viewBox="0 0 1400 820" preserveAspectRatio="xMidYMid meet">
<g class="states-group"></g>
<g class="labels-group"></g>
</svg>
<div class="legend">
<span class="legend-label legend-low">Low</span>
<div class="legend-bar"></div>
<span class="legend-label legend-high">High</span>
</div>
<div class="source">Source: U.S. Census Bureau</div>
</div>
<style>
#us-map {
width: 1920px;
height: 1080px;
background: linear-gradient(145deg, #0f172a 0%, #1e293b 100%);
font-family: "Inter", sans-serif;
color: #e2e8f0;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
#us-map .map-container {
width: 1700px;
height: 950px;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
#us-map .header {
text-align: center;
margin-bottom: 20px;
}
#us-map .headline {
font-size: 52px;
font-weight: 700;
letter-spacing: -0.02em;
color: #f8fafc;
clip-path: inset(0 100% 0 0);
}
#us-map .subtitle {
font-size: 20px;
font-weight: 300;
color: #94a3b8;
margin-top: 8px;
opacity: 0;
}
#us-map .map-svg {
width: 1500px;
height: 750px;
overflow: visible;
}
#us-map .state-path {
stroke: #1e293b;
stroke-width: 1.2;
opacity: 0;
cursor: default;
}
#us-map .state-label {
font-size: 10px;
font-weight: 500;
fill: #f8fafc;
text-anchor: middle;
dominant-baseline: central;
opacity: 0;
pointer-events: none;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
#us-map .legend {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
opacity: 0;
}
#us-map .legend-bar {
width: 300px;
height: 12px;
border-radius: 6px;
background: linear-gradient(90deg, #1e3a5f, #2563eb, #7c3aed, #ec4899);
}
#us-map .legend-label {
font-size: 14px;
font-weight: 500;
color: #94a3b8;
}
#us-map .source {
position: absolute;
bottom: 0;
right: 40px;
font-size: 12px;
color: #475569;
opacity: 0;
}
</style>
<script>
(function () {
const US_TOPO_URL = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json";
const stateData = {
"01": { name: "Alabama", abbr: "AL", density: 99.2 },
"02": { name: "Alaska", abbr: "AK", density: 1.3 },
"04": { name: "Arizona", abbr: "AZ", density: 63.5 },
"05": { name: "Arkansas", abbr: "AR", density: 58.1 },
"06": { name: "California", abbr: "CA", density: 253.9 },
"08": { name: "Colorado", abbr: "CO", density: 56.5 },
"09": { name: "Connecticut", abbr: "CT", density: 738.1 },
10: { name: "Delaware", abbr: "DE", density: 508.0 },
12: { name: "Florida", abbr: "FL", density: 410.5 },
13: { name: "Georgia", abbr: "GA", density: 185.6 },
15: { name: "Hawaii", abbr: "HI", density: 226.6 },
16: { name: "Idaho", abbr: "ID", density: 22.8 },
17: { name: "Illinois", abbr: "IL", density: 228.2 },
18: { name: "Indiana", abbr: "IN", density: 186.7 },
19: { name: "Iowa", abbr: "IA", density: 57.1 },
20: { name: "Kansas", abbr: "KS", density: 35.9 },
21: { name: "Kentucky", abbr: "KY", density: 113.2 },
22: { name: "Louisiana", abbr: "LA", density: 107.7 },
23: { name: "Maine", abbr: "ME", density: 43.8 },
24: { name: "Maryland", abbr: "MD", density: 636.1 },
25: { name: "Massachusetts", abbr: "MA", density: 901.2 },
26: { name: "Michigan", abbr: "MI", density: 176.4 },
27: { name: "Minnesota", abbr: "MN", density: 71.7 },
28: { name: "Mississippi", abbr: "MS", density: 63.5 },
29: { name: "Missouri", abbr: "MO", density: 89.5 },
30: { name: "Montana", abbr: "MT", density: 7.6 },
31: { name: "Nebraska", abbr: "NE", density: 25.3 },
32: { name: "Nevada", abbr: "NV", density: 28.3 },
33: { name: "New Hampshire", abbr: "NH", density: 153.2 },
34: { name: "New Jersey", abbr: "NJ", density: 1263.0 },
35: { name: "New Mexico", abbr: "NM", density: 17.4 },
36: { name: "New York", abbr: "NY", density: 415.3 },
37: { name: "North Carolina", abbr: "NC", density: 214.7 },
38: { name: "North Dakota", abbr: "ND", density: 11.3 },
39: { name: "Ohio", abbr: "OH", density: 288.8 },
40: { name: "Oklahoma", abbr: "OK", density: 57.7 },
41: { name: "Oregon", abbr: "OR", density: 43.9 },
42: { name: "Pennsylvania", abbr: "PA", density: 286.1 },
44: { name: "Rhode Island", abbr: "RI", density: 1061.4 },
45: { name: "South Carolina", abbr: "SC", density: 170.2 },
46: { name: "South Dakota", abbr: "SD", density: 12.2 },
47: { name: "Tennessee", abbr: "TN", density: 167.0 },
48: { name: "Texas", abbr: "TX", density: 112.8 },
49: { name: "Utah", abbr: "UT", density: 40.3 },
50: { name: "Vermont", abbr: "VT", density: 68.0 },
51: { name: "Virginia", abbr: "VA", density: 218.6 },
53: { name: "Washington", abbr: "WA", density: 115.9 },
54: { name: "West Virginia", abbr: "WV", density: 74.6 },
55: { name: "Wisconsin", abbr: "WI", density: 108.8 },
56: { name: "Wyoming", abbr: "WY", density: 5.8 },
};
const maxDensity = 500;
function densityToColor(d) {
const t = Math.min(d / maxDensity, 1);
if (t < 0.33) {
const s = t / 0.33;
return lerpColor([30, 58, 95], [37, 99, 235], s);
} else if (t < 0.66) {
const s = (t - 0.33) / 0.33;
return lerpColor([37, 99, 235], [124, 58, 237], s);
} else {
const s = (t - 0.66) / 0.34;
return lerpColor([124, 58, 237], [236, 72, 153], s);
}
}
function lerpColor(a, b, t) {
const r = Math.round(a[0] + (b[0] - a[0]) * t);
const g = Math.round(a[1] + (b[1] - a[1]) * t);
const bl = Math.round(a[2] + (b[2] - a[2]) * t);
return "rgb(" + r + "," + g + "," + bl + ")";
}
const svg = document.querySelector("#us-map .map-svg");
const statesGroup = svg.querySelector(".states-group");
const labelsGroup = svg.querySelector(".labels-group");
const tl = gsap.timeline({ paused: true });
fetch(US_TOPO_URL)
.then((r) => r.json())
.then((us) => {
const states = topojson.feature(us, us.objects.states).features;
const projection = d3
.geoAlbersUsa()
.fitSize([1380, 800], { type: "FeatureCollection", features: states });
const path = d3.geoPath().projection(projection);
const sortedStates = states
.filter((f) => stateData[String(f.id).padStart(2, "0")])
.sort((a, b) => {
const aData = stateData[String(a.id).padStart(2, "0")];
const bData = stateData[String(b.id).padStart(2, "0")];
return aData.density - bData.density;
});
sortedStates.forEach((feature) => {
const fips = String(feature.id).padStart(2, "0");
const info = stateData[fips];
if (!info) return;
const d = path(feature);
if (!d) return;
const el = document.createElementNS("http://www.w3.org/2000/svg", "path");
el.setAttribute("d", d);
el.setAttribute("class", "state-path");
el.setAttribute("data-state", info.abbr);
el.setAttribute("data-density", info.density);
el.style.fill = densityToColor(info.density);
statesGroup.appendChild(el);
const centroid = path.centroid(feature);
if (centroid[0] && centroid[1]) {
const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
label.setAttribute("x", centroid[0]);
label.setAttribute("y", centroid[1]);
label.setAttribute("class", "state-label");
label.textContent = info.abbr;
labelsGroup.appendChild(label);
}
});
buildTimeline();
});
function buildTimeline() {
const statePaths = statesGroup.querySelectorAll(".state-path");
const stateLabels = labelsGroup.querySelectorAll(".state-label");
tl.to(
"#us-map .headline",
{
clipPath: "inset(0 0% 0 0)",
duration: 1.0,
ease: "power2.inOut",
},
0,
);
tl.to(
"#us-map .subtitle",
{
opacity: 1,
duration: 0.6,
ease: "power2.out",
},
0.4,
);
tl.to(
statePaths,
{
opacity: 1,
scale: 1,
duration: 0.4,
stagger: {
each: 0.06,
from: "center",
},
ease: "back.out(1.4)",
},
1.0,
);
tl.to(
stateLabels,
{
opacity: 1,
duration: 0.3,
stagger: {
each: 0.04,
from: "center",
},
ease: "power2.out",
},
3.5,
);
tl.to(
"#us-map .legend",
{
opacity: 1,
y: 0,
duration: 0.6,
ease: "power2.out",
},
5.0,
);
tl.to(
"#us-map .source",
{
opacity: 1,
duration: 0.5,
ease: "power2.out",
},
5.5,
);
const highlightStates = ["CA", "NY", "TX", "FL", "NJ"];
highlightStates.forEach((abbr, i) => {
const el = statesGroup.querySelector('[data-state="' + abbr + '"]');
if (!el) return;
tl.to(
el,
{
filter: "brightness(1.6) drop-shadow(0 0 8px rgba(255,255,255,0.4))",
stroke: "#f8fafc",
strokeWidth: 2.5,
duration: 0.5,
ease: "power2.out",
},
6.5 + i * 0.8,
);
tl.to(
el,
{
filter: "brightness(1) drop-shadow(0 0 0px rgba(255,255,255,0))",
stroke: "#1e293b",
strokeWidth: 1.2,
duration: 0.5,
ease: "power2.in",
},
7.0 + i * 0.8,
);
});
window.__timelines = window.__timelines || {};
window.__timelines["us-map"] = tl;
}
})();
</script>
</div>
</body>
</html>
```
</Accordion>
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="us-map" data-composition-src="compositions/us-map.html" data-start="0" data-duration="12" data-track-index="1" data-width="1920" data-height="1080"></div>
```
{/* hf:generated-footer */}
Tagged `data` `map` `geography` `usa` `choropleth`.
## Related topics
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)