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

Lesson 5 · 15 min

Remotion: frame, interpolate, spring, and when to pick React

Remotion does the same thing as HyperFrames in another language: each frame is a React component that receives the frame number. interpolate maps frames to values and spring gives physics to an entrance. In this lesson you render the same idea from lesson 2 in React, read the license, and decide when each engine is worth more.

Start the lesson
Editing monitor with a smooth animation curve drawn as a graph and a timeline of frames below.

In this lesson you will

  1. 1Composition: id, fps, duration in frames, and size
  2. 2interpolate with clamp and easing; spring with damping
  3. 3The Remotion license in Oct 2026
  4. 4HyperFrames or Remotion: the criteria for choosing

Goal: render a Remotion composition with interpolate and spring and choose between Remotion and HyperFrames.

01Remotion under the hood

Tap each piece. Compare with the diagram from lesson 1: it is the same drawing in React.

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

HyperFrames: time in seconds and GSAP. Remotion: time in frames and React.

02The composition in React

Title with spring, rule, and subtitle with interpolate. The package.json pins remotion 4.0.537 (npm, 10/19/2026).

File · kit/remotion/src/index.tsx
import {AbsoluteFill, Composition, Easing, interpolate, registerRoot, spring, useCurrentFrame, useVideoConfig} from 'remotion';

// Everything is a function of the current frame: the same frame always draws the same image.
const Abertura: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  // spring: enters with physics (0 → 1), starting at frame 10
  const entra = spring({frame: frame - 10, fps, config: {damping: 200}});
  // interpolate: maps frames to values; clamp holds the value at the ends
  const apoio = interpolate(frame, [30, 50], [0, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
  const barra = interpolate(frame, [20, 60], [0, 640], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.inOut(Easing.cubic),
  });
  return (
    <AbsoluteFill style={{background: '#0c0c10', justifyContent: 'center', paddingLeft: 200, fontFamily: 'system-ui, sans-serif'}}>
      <h1 style={{color: '#f4f4f5', fontSize: 120, lineHeight: 1.1, margin: 0, opacity: entra, transform: `translateY(${(1 - entra) * 60}px)`}}>
        Vídeo é React
      </h1>
      <div style={{width: barra, height: 6, background: '#e9b949', margin: '36px 0'}} />
      <p style={{color: '#a1a1aa', fontSize: 48, margin: 0, opacity: apoio}}>Um componente, um quadro, um MP4.</p>
    </AbsoluteFill>
  );
};

const Raiz: React.FC = () => (
  <Composition id="Abertura" component={Abertura} durationInFrames={150} fps={30} width={1920} height={1080} />
);

registerRoot(Raiz);
File · kit/remotion/package.json
{
  "name": "abertura-remotion",
  "private": true,
  "description": "Ensaio do curso Motion por código com agente (INEMA.CLUB): uma composição Remotion mínima.",
  "scripts": {
    "render": "remotion render src/index.tsx Abertura out/abertura.mp4"
  },
  "dependencies": {
    "@remotion/cli": "4.0.537",
    "react": "19.3.1",
    "react-dom": "19.3.1",
    "remotion": "4.0.537"
  }
}
Terminal · install, render, and check
K=~/projetos/formacao-cinema-ia-motion-v72/kit
E=${TMPDIR:-/tmp}/ensaio-motion/a5; rm -rf "$E"; mkdir -p "$E/remotion"
cp -r "$K"/remotion/{package.json,package-lock.json,src} "$E/remotion/"; cd "$E/remotion"
npm ci --no-audit --no-fund > npm.log 2>&1; echo "npm: $?"
npx remotion render src/index.tsx Abertura out/abertura.mp4 --concurrency=2 > render.log 2>&1
echo "render: $?"
python3 "$K/conferir.py" out/abertura.mp4 5 1920x1080; echo "saída: $?"
Example from the repository
npm: 0
render: 0
OK: 5,0 s · 1920×1080 · 30 fps · sem áudio
saída: 0

npm ci installs 245 MB into node_modules (measured on this machine). The first render may download the browser that Remotion uses.

03The most common error, isolated

Ask for the render with the id in lowercase.

Terminal · render with the wrong id
cd ${TMPDIR:-/tmp}/ensaio-motion/a5/remotion
npx remotion render src/index.tsx abertura out/errado.mp4 2>&1 | grep -m1 -o "Could not find composition.*"
echo "render: ${PIPESTATUS[0]}"
Optional · official Remotion skills for the agent
npx skills add remotion-dev/skills
Example from the repository
Could not find composition with ID abertura. Available compositions: Abertura
render: 1

The id is case-sensitive. The message lists the ids that exist: copy from there.

04License and choice

What to read before using it in a company project.

1. License today

Remotion's LICENSE.md allows free use for individuals, nonprofit organizations, and companies of up to 3 people; above that, the company needs the paid license.

HyperFrames is Apache-2.0: free to use, including commercially.

05Practice

The rehearsal, the error, and a change of your own.

E1 · First MP4 in React

10 min

Run the "install, render, and check" block.

Done when: npm: 0, render: 0, and OK: 5,0 s · 1920×1080 · 30 fps · sem áudio with saída: 0.

E2 · Read the error

5 min

Run the wrong-id block.

Done when: Could not find composition with ID abertura. Available compositions: Abertura and render: 1.

E3 · Six seconds and a different curve

15 min

In /tmp/ensaio-motion/a5/remotion/src/index.tsx, change durationInFrames to 180 and the bar's easing to Easing.out(Easing.exp). Render again with the same command from the block.

Done when: python3 ~/projetos/formacao-cinema-ia-motion-v72/kit/conferir.py out/abertura.mp4 6 1920x1080; echo "saída: $?" → OK: 6,0 s · 1920×1080 · 30 fps · sem áudio and saída: 0.

06Lesson quiz

The explanation appears after you answer.

Question 1 of 4

durationInFrames={150} at fps={30} gives how many seconds?

07In three sentences

FrameIn Remotion, everything is a function of the frame number: interpolate and spring draw the curve.
LicenseFree up to 3 people on 4.0.x; read the LICENSE.md with every update.
ChoiceReact when there is React to reuse; HTML when the agent writes from scratch.

Going further

Previous lessonNext lesson