diff --git a/docs/catalog/blocks/spain-map.mdx b/docs/catalog/blocks/spain-map.mdx
new file mode 100644
index 000000000..5184aaacf
--- /dev/null
+++ b/docs/catalog/blocks/spain-map.mdx
@@ -0,0 +1,44 @@
+---
+title: "Spain Map"
+description: "Animated Spain choropleth by autonomous community with staggered reveals and gradient legend"
+---
+
+# Spain Map
+
+Animated Spain choropleth by autonomous community with staggered reveals and gradient legend — D3 conic conformal projection with GSAP timeline. GDP per capita data with warm red-to-amber color scale.
+
+`data` `map` `geography` `spain` `europe` `choropleth`
+
+
+
+## Install
+
+
+
+```bash Terminal
+npx hyperframes add spain-map
+```
+
+
+
+## Details
+
+| Property | Value |
+| --- | --- |
+| Type | Block |
+| Dimensions | 1920×1080 |
+| Duration | 12s |
+
+## Files
+
+| File | Target | Type |
+| --- | --- | --- |
+| `spain-map.html` | `compositions/spain-map.html` | hyperframes:composition |
+
+## Usage
+
+After installing, add the block to your host composition:
+
+```html
+
+```
diff --git a/docs/catalog/blocks/us-map-bubble.mdx b/docs/catalog/blocks/us-map-bubble.mdx
new file mode 100644
index 000000000..d2815bbec
--- /dev/null
+++ b/docs/catalog/blocks/us-map-bubble.mdx
@@ -0,0 +1,44 @@
+---
+title: "US Bubble Map"
+description: "Animated US bubble map with proportional city markers, value callouts, and connection lines"
+---
+
+# US Bubble Map
+
+Animated US bubble map with proportional city markers, value callouts, and connection lines — composable with `us-map` for layered data visualizations.
+
+`data` `map` `geography` `usa` `bubble` `cities`
+
+
+
+## Install
+
+
+
+```bash Terminal
+npx hyperframes add us-map-bubble
+```
+
+
+
+## Details
+
+| Property | Value |
+| --- | --- |
+| Type | Block |
+| Dimensions | 1920×1080 |
+| Duration | 12s |
+
+## Files
+
+| File | Target | Type |
+| --- | --- | --- |
+| `us-map-bubble.html` | `compositions/us-map-bubble.html` | hyperframes:composition |
+
+## Usage
+
+After installing, add the block to your host composition:
+
+```html
+
+```
diff --git a/docs/catalog/blocks/us-map-flow.mdx b/docs/catalog/blocks/us-map-flow.mdx
new file mode 100644
index 000000000..55db2c216
--- /dev/null
+++ b/docs/catalog/blocks/us-map-flow.mdx
@@ -0,0 +1,44 @@
+---
+title: "US Flow Map"
+description: "Animated connection arcs between US cities over a base map — composable origin-destination flow visualization"
+---
+
+# US Flow Map
+
+Animated connection arcs between US cities over a base map — composable origin-destination flow visualization with traveling particles and GSAP-drawn Bezier arcs.
+
+`data` `map` `geography` `usa` `flow` `connections` `arcs`
+
+
+
+## Install
+
+
+
+```bash Terminal
+npx hyperframes add us-map-flow
+```
+
+
+
+## Details
+
+| Property | Value |
+| --- | --- |
+| Type | Block |
+| Dimensions | 1920×1080 |
+| Duration | 12s |
+
+## Files
+
+| File | Target | Type |
+| --- | --- | --- |
+| `us-map-flow.html` | `compositions/us-map-flow.html` | hyperframes:composition |
+
+## Usage
+
+After installing, add the block to your host composition:
+
+```html
+
+```
diff --git a/docs/catalog/blocks/us-map-hex.mdx b/docs/catalog/blocks/us-map-hex.mdx
new file mode 100644
index 000000000..bbff7409f
--- /dev/null
+++ b/docs/catalog/blocks/us-map-hex.mdx
@@ -0,0 +1,44 @@
+---
+title: "US Hex Grid Map"
+description: "Animated hexagonal tile grid map — each state as an equal-weight hex with data fill and abbreviation label"
+---
+
+# US Hex Grid Map
+
+Animated hexagonal tile grid map — each state as an equal-weight hex with data fill and abbreviation label. No external dependencies beyond GSAP.
+
+`data` `map` `geography` `usa` `hexgrid` `tilegrid`
+
+
+
+## Install
+
+
+
+```bash Terminal
+npx hyperframes add us-map-hex
+```
+
+
+
+## Details
+
+| Property | Value |
+| --- | --- |
+| Type | Block |
+| Dimensions | 1920×1080 |
+| Duration | 10s |
+
+## Files
+
+| File | Target | Type |
+| --- | --- | --- |
+| `us-map-hex.html` | `compositions/us-map-hex.html` | hyperframes:composition |
+
+## Usage
+
+After installing, add the block to your host composition:
+
+```html
+
+```
diff --git a/docs/catalog/blocks/us-map.mdx b/docs/catalog/blocks/us-map.mdx
new file mode 100644
index 000000000..26ff07d02
--- /dev/null
+++ b/docs/catalog/blocks/us-map.mdx
@@ -0,0 +1,53 @@
+---
+title: "US Map"
+description: "Animated US choropleth map with staggered state reveals, value labels, and gradient legend"
+---
+
+# US Map
+
+Animated US choropleth map with staggered state reveals, value labels, and gradient legend — D3 geoAlbersUsa projection with GSAP timeline.
+
+`data` `map` `geography` `usa` `choropleth`
+
+
+
+## Install
+
+
+
+```bash Terminal
+npx hyperframes add us-map
+```
+
+
+
+## Details
+
+| Property | Value |
+| --- | --- |
+| Type | Block |
+| Dimensions | 1920×1080 |
+| Duration | 12s |
+
+## Files
+
+| File | Target | Type |
+| --- | --- | --- |
+| `us-map.html` | `compositions/us-map.html` | hyperframes:composition |
+
+## Usage
+
+After installing, add the block to your host composition:
+
+```html
+
+```
+
+## Composability
+
+Layer `us-map-bubble` or `us-map-flow` on top of this block as a higher track index to combine choropleth + bubble or flow visualizations:
+
+```html
+
+
+```
diff --git a/docs/catalog/blocks/world-map.mdx b/docs/catalog/blocks/world-map.mdx
new file mode 100644
index 000000000..a3ba2d5e4
--- /dev/null
+++ b/docs/catalog/blocks/world-map.mdx
@@ -0,0 +1,44 @@
+---
+title: "World Map"
+description: "Animated world choropleth with country-by-country reveal — D3 Natural Earth projection"
+---
+
+# World Map
+
+Animated world choropleth with country-by-country reveal, gradient legend, and highlight pulses on top GDP countries — D3 Natural Earth projection with GSAP timeline.
+
+`data` `map` `geography` `world` `choropleth`
+
+
+
+## Install
+
+
+
+```bash Terminal
+npx hyperframes add world-map
+```
+
+
+
+## Details
+
+| Property | Value |
+| --- | --- |
+| Type | Block |
+| Dimensions | 1920×1080 |
+| Duration | 14s |
+
+## Files
+
+| File | Target | Type |
+| --- | --- | --- |
+| `world-map.html` | `compositions/world-map.html` | hyperframes:composition |
+
+## Usage
+
+After installing, add the block to your host composition:
+
+```html
+
+```
diff --git a/docs/docs.json b/docs/docs.json
index 563b51b3d..30d555412 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -188,6 +188,17 @@
"catalog/blocks/vpn-youtube-spot"
]
},
+ {
+ "group": "Maps",
+ "pages": [
+ "catalog/blocks/us-map",
+ "catalog/blocks/us-map-bubble",
+ "catalog/blocks/us-map-hex",
+ "catalog/blocks/us-map-flow",
+ "catalog/blocks/world-map",
+ "catalog/blocks/spain-map"
+ ]
+ },
{
"group": "Data",
"pages": [
diff --git a/docs/public/catalog-index.json b/docs/public/catalog-index.json
index 764193bcf..fa4d31679 100644
--- a/docs/public/catalog-index.json
+++ b/docs/public/catalog-index.json
@@ -544,6 +544,22 @@
"href": "/catalog/components/shimmer-sweep",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/shimmer-sweep.png"
},
+ {
+ "name": "spain-map",
+ "type": "block",
+ "title": "Spain Map",
+ "description": "Animated Spain choropleth by autonomous community with staggered reveals and gradient legend — D3 conic conformal projection",
+ "tags": [
+ "data",
+ "map",
+ "geography",
+ "spain",
+ "europe",
+ "choropleth"
+ ],
+ "href": "/catalog/blocks/spain-map",
+ "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/spain-map.png"
+ },
{
"name": "spotify-card",
"type": "block",
@@ -777,6 +793,70 @@
"href": "/catalog/blocks/ui-3d-reveal",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/ui-3d-reveal.png"
},
+ {
+ "name": "us-map",
+ "type": "block",
+ "title": "US Map",
+ "description": "Animated US choropleth map with staggered state reveals, value labels, and gradient legend — pure inline SVG with GSAP",
+ "tags": [
+ "data",
+ "map",
+ "geography",
+ "usa",
+ "choropleth"
+ ],
+ "href": "/catalog/blocks/us-map",
+ "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/us-map.png"
+ },
+ {
+ "name": "us-map-bubble",
+ "type": "block",
+ "title": "US Bubble Map",
+ "description": "Animated US bubble map with proportional city markers, value callouts, and connection lines — composable with us-map",
+ "tags": [
+ "data",
+ "map",
+ "geography",
+ "usa",
+ "bubble",
+ "cities"
+ ],
+ "href": "/catalog/blocks/us-map-bubble",
+ "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/us-map-bubble.png"
+ },
+ {
+ "name": "us-map-flow",
+ "type": "block",
+ "title": "US Flow Map",
+ "description": "Animated connection arcs between US cities over a base map — composable origin-destination flow visualization",
+ "tags": [
+ "data",
+ "map",
+ "geography",
+ "usa",
+ "flow",
+ "connections",
+ "arcs"
+ ],
+ "href": "/catalog/blocks/us-map-flow",
+ "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/us-map-flow.png"
+ },
+ {
+ "name": "us-map-hex",
+ "type": "block",
+ "title": "US Hex Grid Map",
+ "description": "Animated hexagonal tile grid map — each state as an equal-weight hex with data fill and abbreviation label",
+ "tags": [
+ "data",
+ "map",
+ "geography",
+ "usa",
+ "hexgrid",
+ "tilegrid"
+ ],
+ "href": "/catalog/blocks/us-map-hex",
+ "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/us-map-hex.png"
+ },
{
"name": "vfx-iphone-device",
"type": "block",
@@ -911,6 +991,21 @@
"href": "/catalog/blocks/whip-pan",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/whip-pan.png"
},
+ {
+ "name": "world-map",
+ "type": "block",
+ "title": "World Map",
+ "description": "Animated world choropleth with country-by-country reveal, tooltip labels, and rotating globe inset — D3 Natural Earth projection",
+ "tags": [
+ "data",
+ "map",
+ "geography",
+ "world",
+ "choropleth"
+ ],
+ "href": "/catalog/blocks/world-map",
+ "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/world-map.png"
+ },
{
"name": "x-post",
"type": "block",
diff --git a/packages/core/src/compiler/htmlBundler.ts b/packages/core/src/compiler/htmlBundler.ts
index a17050dc9..3bae137bf 100644
--- a/packages/core/src/compiler/htmlBundler.ts
+++ b/packages/core/src/compiler/htmlBundler.ts
@@ -368,7 +368,7 @@ function parseHostVariableValues(host: Element): Record {
return parsed as Record;
}
-const FLATTENED_INNER_ROOT_STRIP_ATTRS = [
+export const FLATTENED_INNER_ROOT_STRIP_ATTRS = [
"data-composition-id",
"data-composition-file",
"data-start",
@@ -381,7 +381,7 @@ const FLATTENED_INNER_ROOT_STRIP_ATTRS = [
"data-hf-authored-end",
];
-function prepareFlattenedInnerRoot(innerRoot: Element): Element {
+export function prepareFlattenedInnerRoot(innerRoot: Element): Element {
const prepared = innerRoot.cloneNode(true) as Element;
const authoredRootId = prepared.getAttribute("id")?.trim();
for (const attrName of FLATTENED_INNER_ROOT_STRIP_ATTRS) {
diff --git a/packages/core/src/compiler/index.ts b/packages/core/src/compiler/index.ts
index 85d66afea..e173ba890 100644
--- a/packages/core/src/compiler/index.ts
+++ b/packages/core/src/compiler/index.ts
@@ -15,7 +15,12 @@ export {
export { compileHtml, type MediaDurationProber } from "./htmlCompiler";
// HTML bundler (Node.js — requires fs, linkedom, esbuild)
-export { bundleToSingleHtml, type BundleOptions } from "./htmlBundler";
+export {
+ bundleToSingleHtml,
+ type BundleOptions,
+ prepareFlattenedInnerRoot,
+ FLATTENED_INNER_ROOT_STRIP_ATTRS,
+} from "./htmlBundler";
export {
RUNTIME_BOOTSTRAP_ATTR,
diff --git a/packages/core/src/lint/rules/adapters.ts b/packages/core/src/lint/rules/adapters.ts
index 50be06e1f..c6d61fb99 100644
--- a/packages/core/src/lint/rules/adapters.ts
+++ b/packages/core/src/lint/rules/adapters.ts
@@ -37,8 +37,16 @@ export const adapterRules: Array<(ctx: LintContext) => HyperframeLintFinding[]>
const usesThree = allScriptTexts.some((t) => /\bTHREE\./.test(t));
const hasThreeScript = allScriptSrcs.some((src) => /three/i.test(src));
+ const hasThreeImportMap = allScriptTexts.some(
+ (t) =>
+ /["']three["']/.test(t) &&
+ /importmap/.test(scripts.find((s) => s.content === t)?.attrs || ""),
+ );
+ const hasThreeModuleImport = allScriptTexts.some(
+ (t) => /\bimport\b.*['"]three['"]/.test(t) || /\bfrom\s+['"]three['"]/.test(t),
+ );
- if (!usesThree || hasThreeScript) return [];
+ if (!usesThree || hasThreeScript || hasThreeImportMap || hasThreeModuleImport) return [];
return [
{
code: "missing_three_script",
diff --git a/packages/core/src/lint/rules/core.ts b/packages/core/src/lint/rules/core.ts
index b4991d234..273eb340b 100644
--- a/packages/core/src/lint/rules/core.ts
+++ b/packages/core/src/lint/rules/core.ts
@@ -157,7 +157,11 @@ export const coreRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> = [
const findings: HyperframeLintFinding[] = [];
for (const script of scripts) {
const attrs = script.attrs || "";
- if (/\bsrc\s*=/.test(attrs) || /\btype\s*=\s*["']application\/json["']/.test(attrs)) continue;
+ if (
+ /\bsrc\s*=/.test(attrs) ||
+ /\btype\s*=\s*["'](?:application\/json|importmap|module)["']/.test(attrs)
+ )
+ continue;
const syntaxError = getInlineScriptSyntaxError(script.content);
if (!syntaxError) continue;
findings.push({
diff --git a/packages/core/src/runtime/init.ts b/packages/core/src/runtime/init.ts
index 5c677959e..01036dc84 100644
--- a/packages/core/src/runtime/init.ts
+++ b/packages/core/src/runtime/init.ts
@@ -964,6 +964,11 @@ export function initSandboxRuntimeModular(): void {
return true;
};
+ (window as Window & { __hfForceTimelineRebind?: () => void }).__hfForceTimelineRebind = () => {
+ childrenBound = false;
+ bindRootTimelineIfAvailable();
+ };
+
const emitRootStageLayoutDiagnostics = () => {
const rootNode = resolveRootCompositionElement();
if (!(rootNode instanceof HTMLElement)) {
diff --git a/packages/engine/src/services/frameCapture.ts b/packages/engine/src/services/frameCapture.ts
index e945063a2..62b3729e7 100644
--- a/packages/engine/src/services/frameCapture.ts
+++ b/packages/engine/src/services/frameCapture.ts
@@ -349,6 +349,54 @@ async function pollPageExpression(
return Boolean(await page.evaluate(expression));
}
+async function pollSubCompositionTimelines(
+ page: Page,
+ timeoutMs: number,
+ intervalMs: number = 150,
+): Promise {
+ const expression = `(function() {
+ var hosts = document.querySelectorAll("[data-composition-id]");
+ if (hosts.length === 0) return true;
+ var timelines = window.__timelines || {};
+ for (var i = 0; i < hosts.length; i++) {
+ var id = hosts[i].getAttribute("data-composition-id");
+ if (!id) continue;
+ if (!timelines[id]) return false;
+ }
+ return true;
+ })()`;
+ const timelinesBeforePoll = Number(
+ await page.evaluate(`Object.keys(window.__timelines || {}).length`),
+ );
+ const ready = await pollPageExpression(page, expression, timeoutMs, intervalMs);
+ const timelinesAfterPoll = Number(
+ await page.evaluate(`Object.keys(window.__timelines || {}).length`),
+ );
+ if (ready && timelinesAfterPoll > timelinesBeforePoll) {
+ await page.evaluate(`(function() {
+ if (typeof window.__hfForceTimelineRebind === "function") {
+ window.__hfForceTimelineRebind();
+ }
+ })()`);
+ }
+ if (!ready) {
+ const missing = await page.evaluate(`(function() {
+ var hosts = document.querySelectorAll("[data-composition-id]");
+ var timelines = window.__timelines || {};
+ var m = [];
+ for (var i = 0; i < hosts.length; i++) {
+ var id = hosts[i].getAttribute("data-composition-id");
+ if (id && !timelines[id]) m.push(id);
+ }
+ return m.join(", ");
+ })()`);
+ console.warn(
+ `[FrameCapture] Sub-composition timelines not registered after ${timeoutMs}ms: ${missing}. ` +
+ `Compositions that load data asynchronously (e.g. fetch) must register window.__timelines[id] after setup completes.`,
+ );
+ }
+}
+
async function pollVideosReady(
page: Page,
skipIds: readonly string[],
@@ -360,7 +408,16 @@ async function pollVideosReady(
await page.evaluate((skipIdList: readonly string[]) => {
const skip = new Set(skipIdList);
const vids = Array.from(document.querySelectorAll("video")).filter((v) => !skip.has(v.id));
- return vids.length === 0 || vids.every((v) => (v as HTMLVideoElement).readyState >= 2);
+ return (
+ vids.length === 0 ||
+ vids.every((v) => {
+ const ve = v as HTMLVideoElement;
+ if (ve.readyState >= 2) return true;
+ if (ve.error) return true;
+ if (ve.networkState === HTMLMediaElement.NETWORK_NO_SOURCE) return true;
+ return false;
+ })
+ );
}, skipIds),
);
};
@@ -499,6 +556,8 @@ export async function initializeSession(session: CaptureSession): Promise
);
}
+ await pollSubCompositionTimelines(page, pageReadyTimeout);
+
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
// Wait for all video elements to have decoded their CURRENT frame, not
@@ -519,8 +578,17 @@ export async function initializeSession(session: CaptureSession): Promise
pageReadyTimeout,
);
if (!videosReady) {
- throw new Error(
- `[FrameCapture] video first frame not decoded after ${pageReadyTimeout}ms. Video elements must reach readyState >= 2 (HAVE_CURRENT_DATA) before capture starts.`,
+ const failedVideos = await page.evaluate((skipIdList: readonly string[]) => {
+ const skip = new Set(skipIdList);
+ return Array.from(document.querySelectorAll("video"))
+ .filter((v) => !skip.has(v.id))
+ .filter((v) => (v as HTMLVideoElement).readyState < 2 && !(v as HTMLVideoElement).error)
+ .map((v) => (v as HTMLVideoElement).src || v.getAttribute("src") || "(no src)")
+ .join(", ");
+ }, session.options.skipReadinessVideoIds ?? []);
+ console.warn(
+ `[FrameCapture] Some video elements did not decode within ${pageReadyTimeout}ms: ${failedVideos}. ` +
+ `Continuing render — affected videos will appear as blank/black frames.`,
);
}
@@ -615,14 +683,30 @@ export async function initializeSession(session: CaptureSession): Promise
);
}
+ await pollSubCompositionTimelines(page, pageReadyTimeout);
+
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
// Same readyState contract as the screenshot path above (>= 2 / HAVE_CURRENT_DATA).
- await pollVideosReady(
+ const bfVideosReady = await pollVideosReady(
page,
session.options.skipReadinessVideoIds ?? [],
session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout,
);
+ if (!bfVideosReady) {
+ const failedVideos = await page.evaluate((skipIdList: readonly string[]) => {
+ const skip = new Set(skipIdList);
+ return Array.from(document.querySelectorAll("video"))
+ .filter((v) => !skip.has(v.id))
+ .filter((v) => (v as HTMLVideoElement).readyState < 2 && !(v as HTMLVideoElement).error)
+ .map((v) => (v as HTMLVideoElement).src || v.getAttribute("src") || "(no src)")
+ .join(", ");
+ }, session.options.skipReadinessVideoIds ?? []);
+ console.warn(
+ `[FrameCapture] Some video elements did not decode within ${pageReadyTimeout}ms: ${failedVideos}. ` +
+ `Continuing render — affected videos will appear as blank/black frames.`,
+ );
+ }
// Font check (no rAF dependency — uses fonts.ready API directly)
await page.evaluate(`document.fonts?.ready`);
diff --git a/packages/engine/src/services/videoFrameExtractor.ts b/packages/engine/src/services/videoFrameExtractor.ts
index 67681df04..ef32c2ef2 100644
--- a/packages/engine/src/services/videoFrameExtractor.ts
+++ b/packages/engine/src/services/videoFrameExtractor.ts
@@ -512,8 +512,10 @@ export function resolveProjectRelativeSrc(
baseDir: string,
compiledDir?: string,
): string {
- const fromCompiled = compiledDir ? join(compiledDir, src) : null;
- const fromBase = join(baseDir, src);
+ const qIdx = src.indexOf("?");
+ const cleanSrc = qIdx >= 0 ? src.slice(0, qIdx) : src;
+ const fromCompiled = compiledDir ? join(compiledDir, cleanSrc) : null;
+ const fromBase = join(baseDir, cleanSrc);
const candidates: string[] = [];
if (fromCompiled) candidates.push(fromCompiled);
candidates.push(fromBase);
@@ -528,7 +530,7 @@ export function resolveProjectRelativeSrc(
// then strip any remaining leading `..` segments. Stripping `..` from the
// raw input would leave dangling siblings (`assets/../../assets/foo`
// would become `assets/assets/foo` instead of `assets/foo`).
- const normalized = posix.normalize(src.replace(/\\/g, "/"));
+ const normalized = posix.normalize(cleanSrc.replace(/\\/g, "/"));
const stripped = normalized.replace(/^(\.\.\/)+/, "");
if (stripped && stripped !== src && !stripped.startsWith("..")) {
if (compiledDir) candidates.push(join(compiledDir, stripped));
diff --git a/packages/producer/node_modules/.bin/esbuild b/packages/producer/node_modules/.bin/esbuild
deleted file mode 120000
index 00d2d93cd..000000000
--- a/packages/producer/node_modules/.bin/esbuild
+++ /dev/null
@@ -1 +0,0 @@
-../../../../node_modules/.bun/@esbuild+darwin-arm64@0.27.7/node_modules/@esbuild/darwin-arm64/bin/esbuild
\ No newline at end of file
diff --git a/packages/producer/node_modules/@types/node b/packages/producer/node_modules/@types/node
deleted file mode 120000
index 454f1340f..000000000
--- a/packages/producer/node_modules/@types/node
+++ /dev/null
@@ -1 +0,0 @@
-../../../../node_modules/.bun/@types+node@22.19.19/node_modules/@types/node
\ No newline at end of file
diff --git a/packages/producer/node_modules/esbuild b/packages/producer/node_modules/esbuild
deleted file mode 120000
index 2bb1f6fa5..000000000
--- a/packages/producer/node_modules/esbuild
+++ /dev/null
@@ -1 +0,0 @@
-../../../node_modules/.bun/esbuild@0.27.7/node_modules/esbuild
\ No newline at end of file
diff --git a/packages/producer/src/services/htmlCompiler.ts b/packages/producer/src/services/htmlCompiler.ts
index 533e04c7e..4b9362404 100644
--- a/packages/producer/src/services/htmlCompiler.ts
+++ b/packages/producer/src/services/htmlCompiler.ts
@@ -578,6 +578,18 @@ function inlineSubCompositions(
},
);
+ // Set data-hf-authored-id on host elements so the scoped script proxy
+ // can rewrite #id selectors (e.g. #us-map → [data-hf-authored-id="us-map"]).
+ // Unlike flattenInnerRoot (which changes DOM structure and breaks baselines),
+ // this preserves the existing innerHTML-based inlining while enabling the
+ // authored-id selector contract.
+ for (const hostEl of hosts) {
+ const compId = hostEl.getAttribute("data-composition-id");
+ if (compId && !hostEl.getAttribute("data-hf-authored-id")) {
+ hostEl.setAttribute("data-hf-authored-id", compId);
+ }
+ }
+
// Producer-specific: set explicit pixel dimensions on host elements so
// children using width/height: 100% resolve correctly. The runtime does
// this automatically but compiled HTML needs it inline.
diff --git a/registry/blocks/spain-map/registry-item.json b/registry/blocks/spain-map/registry-item.json
new file mode 100644
index 000000000..8329e66a4
--- /dev/null
+++ b/registry/blocks/spain-map/registry-item.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
+ "name": "spain-map",
+ "type": "hyperframes:block",
+ "title": "Spain Map",
+ "description": "Animated Spain choropleth by autonomous community with staggered reveals and gradient legend — D3 conic conformal projection",
+ "tags": ["data", "map", "geography", "spain", "europe", "choropleth"],
+ "dimensions": { "width": 1920, "height": 1080 },
+ "duration": 12,
+ "files": [
+ {
+ "path": "spain-map.html",
+ "target": "compositions/spain-map.html",
+ "type": "hyperframes:composition"
+ }
+ ]
+}
diff --git a/registry/blocks/spain-map/spain-map.html b/registry/blocks/spain-map/spain-map.html
new file mode 100644
index 000000000..28e52d495
--- /dev/null
+++ b/registry/blocks/spain-map/spain-map.html
@@ -0,0 +1,393 @@
+
+
+
+
+
+ Spain Map
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Fuente: Instituto Nacional de Estadística
+
+
+
+
+
+
+
+
diff --git a/registry/blocks/us-map-bubble/registry-item.json b/registry/blocks/us-map-bubble/registry-item.json
new file mode 100644
index 000000000..0fd5c14bc
--- /dev/null
+++ b/registry/blocks/us-map-bubble/registry-item.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
+ "name": "us-map-bubble",
+ "type": "hyperframes:block",
+ "title": "US Bubble Map",
+ "description": "Animated US bubble map with proportional city markers, value callouts, and connection lines — composable with us-map",
+ "tags": ["data", "map", "geography", "usa", "bubble", "cities"],
+ "dimensions": {
+ "width": 1920,
+ "height": 1080
+ },
+ "duration": 12,
+ "files": [
+ {
+ "path": "us-map-bubble.html",
+ "target": "compositions/us-map-bubble.html",
+ "type": "hyperframes:composition"
+ }
+ ]
+}
diff --git a/registry/blocks/us-map-bubble/us-map-bubble.html b/registry/blocks/us-map-bubble/us-map-bubble.html
new file mode 100644
index 000000000..943269a94
--- /dev/null
+++ b/registry/blocks/us-map-bubble/us-map-bubble.html
@@ -0,0 +1,477 @@
+
+
+
+
+
+ US Bubble Map
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: U.S. Census Bureau, 2024
+
+
+
+
+
+
+
+
diff --git a/registry/blocks/us-map-flow/registry-item.json b/registry/blocks/us-map-flow/registry-item.json
new file mode 100644
index 000000000..9c82cdb6e
--- /dev/null
+++ b/registry/blocks/us-map-flow/registry-item.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
+ "name": "us-map-flow",
+ "type": "hyperframes:block",
+ "title": "US Flow Map",
+ "description": "Animated connection arcs between US cities over a base map — composable origin-destination flow visualization",
+ "tags": ["data", "map", "geography", "usa", "flow", "connections", "arcs"],
+ "dimensions": { "width": 1920, "height": 1080 },
+ "duration": 12,
+ "files": [
+ {
+ "path": "us-map-flow.html",
+ "target": "compositions/us-map-flow.html",
+ "type": "hyperframes:composition"
+ }
+ ]
+}
diff --git a/registry/blocks/us-map-flow/us-map-flow.html b/registry/blocks/us-map-flow/us-map-flow.html
new file mode 100644
index 000000000..1805d7c3a
--- /dev/null
+++ b/registry/blocks/us-map-flow/us-map-flow.html
@@ -0,0 +1,429 @@
+
+
+
+
+
+ US Flow Map
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: Illustrative data
+
+
+
+
+
+
+
diff --git a/registry/blocks/us-map-hex/registry-item.json b/registry/blocks/us-map-hex/registry-item.json
new file mode 100644
index 000000000..c7877d8b9
--- /dev/null
+++ b/registry/blocks/us-map-hex/registry-item.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
+ "name": "us-map-hex",
+ "type": "hyperframes:block",
+ "title": "US Hex Grid Map",
+ "description": "Animated hexagonal tile grid map — each state as an equal-weight hex with data fill and abbreviation label",
+ "tags": ["data", "map", "geography", "usa", "hexgrid", "tilegrid"],
+ "dimensions": { "width": 1920, "height": 1080 },
+ "duration": 10,
+ "files": [
+ {
+ "path": "us-map-hex.html",
+ "target": "compositions/us-map-hex.html",
+ "type": "hyperframes:composition"
+ }
+ ]
+}
diff --git a/registry/blocks/us-map-hex/us-map-hex.html b/registry/blocks/us-map-hex/us-map-hex.html
new file mode 100644
index 000000000..3d8ec4faf
--- /dev/null
+++ b/registry/blocks/us-map-hex/us-map-hex.html
@@ -0,0 +1,559 @@
+
+
+
+
+
+ US Hex Grid Map
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: U.S. Census Bureau, American Community Survey 2024
+
+
+
+
+
+
+
diff --git a/registry/blocks/us-map/registry-item.json b/registry/blocks/us-map/registry-item.json
new file mode 100644
index 000000000..b842ce50d
--- /dev/null
+++ b/registry/blocks/us-map/registry-item.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
+ "name": "us-map",
+ "type": "hyperframes:block",
+ "title": "US Map",
+ "description": "Animated US choropleth map with staggered state reveals, value labels, and gradient legend — pure inline SVG with GSAP",
+ "tags": ["data", "map", "geography", "usa", "choropleth"],
+ "dimensions": {
+ "width": 1920,
+ "height": 1080
+ },
+ "duration": 12,
+ "files": [
+ {
+ "path": "us-map.html",
+ "target": "compositions/us-map.html",
+ "type": "hyperframes:composition"
+ }
+ ]
+}
diff --git a/registry/blocks/us-map/us-map.html b/registry/blocks/us-map/us-map.html
new file mode 100644
index 000000000..3a8c59a3b
--- /dev/null
+++ b/registry/blocks/us-map/us-map.html
@@ -0,0 +1,402 @@
+
+
+
+
+
+ US Map
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: U.S. Census Bureau
+
+
+
+
+
+
+
+
diff --git a/registry/blocks/world-map/registry-item.json b/registry/blocks/world-map/registry-item.json
new file mode 100644
index 000000000..9a248d98f
--- /dev/null
+++ b/registry/blocks/world-map/registry-item.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
+ "name": "world-map",
+ "type": "hyperframes:block",
+ "title": "World Map",
+ "description": "Animated world choropleth with country-by-country reveal, tooltip labels, and rotating globe inset — D3 Natural Earth projection",
+ "tags": ["data", "map", "geography", "world", "choropleth"],
+ "dimensions": { "width": 1920, "height": 1080 },
+ "duration": 14,
+ "files": [
+ {
+ "path": "world-map.html",
+ "target": "compositions/world-map.html",
+ "type": "hyperframes:composition"
+ }
+ ]
+}
diff --git a/registry/blocks/world-map/world-map.html b/registry/blocks/world-map/world-map.html
new file mode 100644
index 000000000..4e451c217
--- /dev/null
+++ b/registry/blocks/world-map/world-map.html
@@ -0,0 +1,435 @@
+
+
+
+
+
+ World Map
+
+
+
+
+
+
+
+
+
+
+
Global GDP per Capita
+
Nominal GDP per capita, 2024 IMF estimates
+
+
+
+
+
+
+
+
Source: International Monetary Fund
+
+
+
+
+
+
+
diff --git a/registry/registry.json b/registry/registry.json
index 26c8ffd02..0fb114096 100644
--- a/registry/registry.json
+++ b/registry/registry.json
@@ -39,6 +39,30 @@
"name": "data-chart",
"type": "hyperframes:block"
},
+ {
+ "name": "us-map",
+ "type": "hyperframes:block"
+ },
+ {
+ "name": "us-map-bubble",
+ "type": "hyperframes:block"
+ },
+ {
+ "name": "us-map-hex",
+ "type": "hyperframes:block"
+ },
+ {
+ "name": "us-map-flow",
+ "type": "hyperframes:block"
+ },
+ {
+ "name": "world-map",
+ "type": "hyperframes:block"
+ },
+ {
+ "name": "spain-map",
+ "type": "hyperframes:block"
+ },
{
"name": "flowchart",
"type": "hyperframes:block"