diff --git a/bun.lock b/bun.lock
index cac1f849d6..4a86d91418 100644
--- a/bun.lock
+++ b/bun.lock
@@ -22,7 +22,7 @@
},
"packages/aws-lambda": {
"name": "@hyperframes/aws-lambda",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@aws-sdk/client-s3": "^3.700.0",
"@aws-sdk/client-sfn": "^3.700.0",
@@ -52,9 +52,24 @@
"constructs",
],
},
+ "packages/browser-export": {
+ "name": "@hyperframes/browser-export",
+ "version": "0.7.26",
+ "dependencies": {
+ "html-to-image": "^1.11.13",
+ "mediabunny": "^1.45.3",
+ },
+ "devDependencies": {
+ "@types/node": "^25.0.10",
+ "linkedom": "^0.18.12",
+ "tsup": "^8.0.0",
+ "typescript": "^5.0.0",
+ "vitest": "^3.2.4",
+ },
+ },
"packages/cli": {
"name": "@hyperframes/cli",
- "version": "0.7.22",
+ "version": "0.7.26",
"bin": {
"hyperframes": "./dist/cli.js",
},
@@ -103,7 +118,7 @@
},
"packages/core": {
"name": "@hyperframes/core",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@chenglou/pretext": "^0.0.5",
"@hyperframes/lint": "workspace:*",
@@ -128,7 +143,7 @@
},
"packages/engine": {
"name": "@hyperframes/engine",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@hono/node-server": "^1.13.0",
"@hyperframes/core": "workspace:^",
@@ -146,7 +161,7 @@
},
"packages/gcp-cloud-run": {
"name": "@hyperframes/gcp-cloud-run",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@google-cloud/storage": "^7.14.0",
"@google-cloud/workflows": "^4.2.0",
@@ -166,7 +181,7 @@
},
"packages/lint": {
"name": "@hyperframes/lint",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@hyperframes/parsers": "workspace:*",
"linkedom": "^0.18.12",
@@ -182,7 +197,7 @@
},
"packages/parsers": {
"name": "@hyperframes/parsers",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@babel/parser": "^7.27.0",
"acorn": "^8.17.0",
@@ -202,7 +217,7 @@
},
"packages/player": {
"name": "@hyperframes/player",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@hyperframes/core": "workspace:*",
},
@@ -217,7 +232,7 @@
},
"packages/producer": {
"name": "@hyperframes/producer",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@fontsource/archivo-black": "^5.2.8",
"@fontsource/eb-garamond": "^5.2.7",
@@ -261,7 +276,7 @@
},
"packages/sdk": {
"name": "@hyperframes/sdk",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@hyperframes/core": "workspace:*",
"@hyperframes/parsers": "workspace:*",
@@ -288,7 +303,7 @@
},
"packages/shader-transitions": {
"name": "@hyperframes/shader-transitions",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"html2canvas": "^1.4.1",
},
@@ -300,7 +315,7 @@
},
"packages/studio": {
"name": "@hyperframes/studio",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@codemirror/autocomplete": "^6.20.1",
"@codemirror/commands": "^6.10.3",
@@ -348,7 +363,7 @@
},
"packages/studio-server": {
"name": "@hyperframes/studio-server",
- "version": "0.7.22",
+ "version": "0.7.26",
"dependencies": {
"@hyperframes/core": "workspace:*",
"@hyperframes/parsers": "workspace:*",
@@ -715,6 +730,8 @@
"@hyperframes/aws-lambda": ["@hyperframes/aws-lambda@workspace:packages/aws-lambda"],
+ "@hyperframes/browser-export": ["@hyperframes/browser-export@workspace:packages/browser-export"],
+
"@hyperframes/cli": ["@hyperframes/cli@workspace:packages/cli"],
"@hyperframes/core": ["@hyperframes/core@workspace:packages/core"],
@@ -1589,6 +1606,8 @@
"html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="],
+ "html-to-image": ["html-to-image@1.11.13", "", {}, "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg=="],
+
"html2canvas": ["html2canvas@1.4.1", "", { "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" } }, "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA=="],
"htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="],
diff --git a/docs/docs.json b/docs/docs.json
index 9e6829eb40..708ce13b81 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -325,6 +325,7 @@
"packages/core",
"packages/parsers",
"packages/lint",
+ "packages/browser-export",
"packages/studio-server",
"packages/sdk",
"packages/engine",
diff --git a/docs/packages/browser-export.mdx b/docs/packages/browser-export.mdx
new file mode 100644
index 0000000000..0612e6fe9a
--- /dev/null
+++ b/docs/packages/browser-export.mdx
@@ -0,0 +1,66 @@
+---
+title: "@hyperframes/browser-export"
+description: "Render a composition to MP4/WebM entirely in the browser — no server, no FFmpeg, no headless Chrome."
+---
+
+`@hyperframes/browser-export` renders a **live** composition to video fully client-side: deterministic GSAP seeks, SVG `foreignObject` rasterization and WebCodecs encoding via [mediabunny](https://mediabunny.dev).
+
+```bash
+npm install @hyperframes/browser-export
+```
+
+## When to Use
+
+
+ Reach for this package when running Node.js, FFmpeg or headless Chrome is not viable: browser-based editors, template SaaS frontends, quick previews and one-off exports delivered straight to the user's Downloads folder.
+
+
+
+ This is a **complement** to `@hyperframes/producer`, not a replacement. The producer's headless-Chrome + FFmpeg pipeline remains the reference for deterministic, pixel-perfect production renders.
+
+
+## Quick Start
+
+```typescript
+import { exportComposition, downloadExport } from "@hyperframes/browser-export";
+
+const result = await exportComposition(document, {
+ fps: 30,
+ format: "mp4", // or "webm"
+ onProgress: ({ phase, fraction }) => {
+ console.log(`${phase}: ${(fraction * 100).toFixed(0)}%`);
+ },
+});
+
+downloadExport(result); // ".mp4"
+```
+
+The composition must be live in the current page (or a same-origin iframe document): GSAP timelines registered in `window.__timelines`, a root element carrying `data-composition-id` / `data-width` / `data-height`.
+
+## Pipeline
+
+1. **Plan** — locate the composition root, read dimensions, resolve the duration from the master timeline (or `options.duration`).
+2. **Audio** — parse `