Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,5 @@ tmp/
*.gif
output/
renders/
!docs/assets/renders/
!docs/assets/renders/*.mp4
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@

Every template below is a real, animated single-file HTML video — these are live renders, not mockups. Drop one in, let the agent fill it with your content, export to MP4.

Rendered sample: [frame-product-promo.mp4](docs/assets/renders/frame-product-promo.mp4) (`frame-product-promo`, 14.4s, 1920×1080, 60fps).

```bash
node packages/cli/dist/bin.js project-render <project_id> --output docs/assets/renders/frame-product-promo.mp4 --stream-progress
```

<table>
<tr>
<td width="50%"><img src="docs/assets/templates/frame-data-chart-nyt.png" alt="NYT-style data chart" /></td>
Expand Down
6 changes: 6 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@

下面每个模板都是一支真实、带动画的单文件 HTML 视频 —— 这些是实时渲染截图,不是效果图。挑一个,让 agent 填进你的内容,导出 MP4。

渲染样片:[frame-product-promo.mp4](docs/assets/renders/frame-product-promo.mp4)(`frame-product-promo`,14.4 秒,1920×1080,60fps)。

```bash
node packages/cli/dist/bin.js project-render <project_id> --output docs/assets/renders/frame-product-promo.mp4 --stream-progress
```

<table>
<tr>
<td width="50%"><img src="docs/assets/templates/frame-data-chart-nyt.png" alt="NYT 风格数据图表" /></td>
Expand Down
Binary file added docs/assets/renders/frame-product-promo.mp4
Binary file not shown.
67 changes: 65 additions & 2 deletions packages/adapter-hyperframes/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,15 @@ export async function render(input: RenderInput, ctx: RenderContext): Promise<Re
const td = typeof c.totalDuration === 'function' ? c.totalDuration() : 0;
if (Number.isFinite(td)) gsapMs = Math.max(gsapMs, td * 1000);
}
return Math.max(maxMs, gsapMs);
let scheduledMs = 0;
document.querySelectorAll<HTMLElement>('[data-start][data-duration]').forEach((el) => {
const start = Number.parseFloat(el.getAttribute('data-start') || '0') || 0;
const duration = Number.parseFloat(el.getAttribute('data-duration') || '0') || 0;
if (Number.isFinite(start) && Number.isFinite(duration)) {
scheduledMs = Math.max(scheduledMs, (start + duration) * 1000);
}
});
return Math.max(maxMs, gsapMs, scheduledMs);
});
// +0.4s settle so the final animation frame is actually captured; cap at
// 30s so a stray huge value can't make a frame run away.
Expand Down Expand Up @@ -463,6 +471,17 @@ async function prepareSourceHtml(
const player = `
<script>
(function () {
function readNumber(value, fallback) {
var n = parseFloat(value || '');
return Number.isFinite(n) ? n : fallback;
}
function findCompositionElement(id) {
var all = document.querySelectorAll('[data-composition-id]');
for (var i = 0; i < all.length; i++) {
if (all[i].getAttribute('data-composition-id') === id) return all[i];
}
return null;
}
function reexec(root) {
root.querySelectorAll('script').forEach(function (old) {
if (old.src) { old.parentNode.removeChild(old); return; }
Expand Down Expand Up @@ -494,10 +513,54 @@ async function prepareSourceHtml(
// probe see the real 14.7s and record the whole story.
window.__hvPlayAll = function () {
var tls = window.__timelines || {};
var gsap = window.gsap;
var master = gsap && typeof gsap.timeline === 'function'
? gsap.timeline({ paused: true })
: null;
var hosts = Array.prototype.slice.call(document.querySelectorAll('[data-composition-src]'));

if (gsap && typeof gsap.set === 'function') {
hosts.forEach(function (host) { gsap.set(host, { autoAlpha: 0 }); });
} else {
hosts.forEach(function (host) {
host.style.opacity = '0';
host.style.visibility = 'hidden';
});
}

hosts.forEach(function (host) {
var start = readNumber(host.getAttribute('data-start'), 0);
var duration = readNumber(host.getAttribute('data-duration'), 0);
if (master) {
master.set(host, { autoAlpha: 1 }, start);
if (duration > 0) master.set(host, { autoAlpha: 0 }, start + duration);
} else {
setTimeout(function () {
host.style.opacity = '1';
host.style.visibility = 'visible';
}, start * 1000);
if (duration > 0) {
setTimeout(function () {
host.style.opacity = '0';
host.style.visibility = 'hidden';
}, (start + duration) * 1000);
}
}
});

Object.keys(tls).forEach(function (k) {
var tl = tls[k];
if (tl && typeof tl.play === 'function') tl.play(0);
if (!tl || typeof tl.play !== 'function') return;
var el = findCompositionElement(k);
var start = readNumber(el && el.getAttribute('data-start'), 0);
if (master && typeof master.add === 'function') {
if (typeof tl.pause === 'function') tl.pause(0);
master.add(tl, start);
} else {
setTimeout(function () { tl.play(0); }, start * 1000);
}
});
if (master) master.play(0);
};
function boot() {
window.__timelines = window.__timelines || {};
Expand Down