E1 · Create the project
5 minRun the "create the project and see what came out" block.
Done when: The output shows saída: 0, the list with index.html and package.json, the line hyperframes@0.8.176 render and Status: disabled.
Lesson 2 · 15 min
HyperFrames is an open engine from HeyGen that renders HTML into video. Three attributes set the size and duration, a paused GSAP timeline sets the motion, and the check verifies everything before the render. In this lesson you create the project, write the first composition, and walk away with a verified MP4.
Start the lesson
Goal: create a HyperFrames project, write the composition with data-*, and render an MP4 that passes the checker.
Click each step.
When the root has data-duration, it rules: a shorter timeline holds the last frame.
init without prompts (--non-interactive) and telemetry turned off before anything else.
export HYPERFRAMES_NO_TELEMETRY=1 NO_COLOR=1
E=${TMPDIR:-/tmp}/ensaio-motion/a2; rm -rf "$E"; mkdir -p "$E"; cd "$E"
npx -y hyperframes@0.8.176 init minha-abertura --non-interactive > init.log 2>&1; echo "saída: $?"
ls minha-abertura
grep '"render"' minha-abertura/package.json
npx -y hyperframes@0.8.176 telemetry status | head -2saída: 0
AGENTS.md
CLAUDE.md
hyperframes.json
index.html
meta.json
package.json
"render": "npx --yes hyperframes@0.8.176 render",
Status: disabledThe render script in package.json already calls the exact version. Telemetry shows as disabled because the block exported HYPERFRAMES_NO_TELEMETRY=1.
By default the CLI sends anonymous usage data. telemetry status in 0.8.176 shows how to turn it off: hyperframes telemetry disable (saved permanently) or the variables HYPERFRAMES_NO_TELEMETRY=1 or DO_NOT_TRACK=1 (current session only). The course blocks use the variable.
Title, rule, and subtitle entering in order. Copy it over the index.html that init created.
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 1920px; height: 1080px; overflow: hidden; background: #0c0c10; }
#root { width: 100%; height: 100%; position: relative; font-family: ui-sans-serif, system-ui, sans-serif; }
#cena { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; padding-left: 200px; }
#titulo { color: #f4f4f5; font-size: 120px; font-weight: 700; line-height: 1.1; letter-spacing: -0.02em; }
#linha { width: 640px; height: 6px; margin: 36px 0; background: #e9b949; transform-origin: left center; }
#sub { color: #a1a1aa; font-size: 48px; line-height: 1.2; }
</style>
</head>
<body>
<div id="root" data-composition-id="main" data-start="0" data-duration="5" data-width="1920" data-height="1080">
<section id="cena">
<h1 id="titulo">Vídeo é código</h1>
<div id="linha"></div>
<p id="sub">Uma página HTML, um relógio, um MP4.</p>
</section>
</div>
<script>
const tl = gsap.timeline({ paused: true });
tl.fromTo("#titulo", { y: 60, opacity: 0 }, { y: 0, opacity: 1, duration: 0.8, ease: "power3.out" }, 0.3);
tl.fromTo("#linha", { scaleX: 0 }, { scaleX: 1, duration: 0.7, ease: "power2.inOut" }, 0.9);
tl.fromTo("#sub", { y: 30, opacity: 0 }, { y: 0, opacity: 1, duration: 0.6, ease: "power2.out" }, 1.4);
window.__timelines["main"] = tl;
</script>
</body>
</html>K=~/projetos/formacao-cinema-ia-motion-v72/kit
export HYPERFRAMES_NO_TELEMETRY=1 NO_COLOR=1
cd ${TMPDIR:-/tmp}/ensaio-motion/a2/minha-abertura
cp "$K/exemplos/a2-ola/index.html" index.html
npx -y hyperframes@0.8.176 check 2>&1 | grep -E "◇|✗|⚠"
npx -y hyperframes@0.8.176 render --quality draft --workers 2 --quiet --output renders/abertura.mp4 > render.log 2>&1
echo "render: $?"
python3 "$K/conferir.py" renders/abertura.mp4 5 1920x1080; echo "saída: $?"◇ 0 errors, 0 warnings ◇ 0 errors, 0 warnings ◇ 0 issues across 9 sample(s) ◇ 0 errors, 0 warnings ◇ 7/7 text checks pass WCAG AA ◇ disabled ◇ Check passed render: 0 OK: 5,0 s · 1920×1080 · 30 fps · sem áudio saída: 0
The first five ◇ lines are the parts of the check (lint, execution, layout, motion, contrast); the sixth, disabled, is frame capture, which comes turned off (--snapshots turns it on). The checker reads the MP4 with ffprobe and compares it with what you asked for: 5 s, 1920×1080.
Putting data-start on a section that has a title and text inside made the 0.8.176 lint warn nested_structure_needs_subcomposition. In the example, the section is just a container (no data-start) and the timeline animates the children; a scene with several elements of its own becomes a subcomposition.
HyperFrames publishes skills for agents (research, §5). The init already leaves an AGENTS.md and a CLAUDE.md in the project folder.
npx skills add heygen-com/hyperframes
From init to a checked MP4, plus one change of your own.
Run the "create the project and see what came out" block.
Done when: The output shows saída: 0, the list with index.html and package.json, the line hyperframes@0.8.176 render and Status: disabled.
Run the "check, render and verify" block.
Done when: ◇ Check passed, render: 0 and OK: 5,0 s · 1920×1080 · 30 fps · sem áudio with saída: 0.
In your project's index.html, change the root's data-duration to 8, swap the title for one of your own, and run check and render again.
Done when: python3 ~/projetos/formacao-cinema-ia-motion-v72/kit/conferir.py renders/abertura.mp4 8 1920x1080; echo "saída: $?" → OK: 8,0 s · 1920×1080 · 30 fps · sem áudio and saída: 0.
The explanation appears after you answer.
Question 1 of 4
What sets the MP4's duration in HyperFrames?
Question 2 of 4
The check passed, but the render came out wrong. Which of these does the check NOT guarantee?
Question 3 of 4
Why does the course pin hyperframes@0.8.176 in every command?
Question 4 of 4
How do you turn off telemetry only for the terminal session?
Pick one option. The explanation appears after your attempt.