Motion as code with an agent|INEMA.CLUB-PRO

Lesson 2 · 15 min

HyperFrames from scratch: HTML with data-* becomes MP4

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
An open laptop on a dark desk showing a dark video frame, with a reference monitor beside it.

In this lesson you will

  1. 1What init creates and why the version is pinned in package.json
  2. 2The root with data-composition-id, data-width, data-height, and data-duration
  3. 3The paused GSAP timeline registered in window.__timelines
  4. 4check, render, and the kit's checker

Goal: create a HyperFrames project, write the composition with data-*, and render an MP4 that passes the checker.

01The path of a project

Click each step.

Click each block in the diagram to see what it does.

When the root has data-duration, it rules: a shorter timeline holds the last frame.

02Create the project

init without prompts (--non-interactive) and telemetry turned off before anything else.

Terminal · create the project and see what came out
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 -2
Example from the repository
saída: 0
AGENTS.md
CLAUDE.md
hyperframes.json
index.html
meta.json
package.json
    "render": "npx --yes hyperframes@0.8.176 render",

  Status:     disabled

The render script in package.json already calls the exact version. Telemetry shows as disabled because the block exported HYPERFRAMES_NO_TELEMETRY=1.

03The first composition

Title, rule, and subtitle entering in order. Copy it over the index.html that init created.

File · kit/exemplos/a2-ola/index.html
<!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>
Terminal · check, render, and run the checker
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: $?"
Example from the repository
  ◇ 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.

04So the agent works with the right rules

HyperFrames publishes skills for agents (research, §5). The init already leaves an AGENTS.md and a CLAUDE.md in the project folder.

Optional · install the official skills in your agent
npx skills add heygen-com/hyperframes

05Practice

From init to a checked MP4, plus one change of your own.

E1 · Create the project

5 min

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.

E2 · First MP4

10 min

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.

E3 · Eight seconds

10 min

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.

06Lesson quiz

The explanation appears after you answer.

Question 1 of 4

What sets the MP4's duration in HyperFrames?

07In three sentences

Rootdata-composition-id, size and duration; clips with data-start.
A timelineGSAP paused in window.__timelines with the same key as the root.
Gatecheck before the render, checker after; pinned version and telemetry off.

Going further

Previous lessonNext lesson