| name | remotion-video |
| description | Converts an Amaroad slide deck into an animated video using Remotion. Reads
deck.config.ts and MDX slides, then generates a standalone Remotion project
with per-slide scene components, TransitionSeries orchestration, and theme-
matched styling.
Use when user says "make a video from this deck", "animated slide video",
"convert deck to video", "export as video", or "ใใฎใใใญใๅ็ปใซใใฆ".
Key capabilities: automatic storyboard from slide types, spring/interpolate
animations, Google Fonts mapping, asset copying, 1920x1080 30fps output,
and configurable per-slide hold durations.
|
| allowed-tools | ["Bash","Read","Write","Edit","Glob","Grep","Agent"] |
Prerequisites
- The
remotion-best-practices skill must be installed (provides Remotion API knowledge)
- Load relevant rule files from
.claude/skills/remotion-best-practices/rules/ as needed
Workflow
Step 1: Identify Target Deck
Ask the user which deck to convert if not specified.
List available decks:
ls decks/
Read the deck config and all MDX slides:
pnpm exec tsx .claude/skills/svg-diagram/scripts/extract-theme.ts --deck <deck-name>
Also read decks/<deck-name>/deck.config.ts to get the full config (title, logo, copyright, pageNumber, transition, accentLine).
Step 2: Read and Analyze Slides
Read all .mdx files in decks/<deck-name>/ (sorted by filename).
For each slide, extract:
- frontmatter: type, transition, notes, background, verticalAlign
- content: headings, paragraphs, lists, images, charts, components used
Build a slide manifest:
Slide 01 (cover): "Title text" โ has logo, gradient background
Slide 02 (section): "Section Title" โ centered, icon
Slide 03 (content): "Feature Overview" โ 3-column grid with cards
...
Step 3: Design Video Storyboard
Map each slide type to an animation pattern:
| Slide Type | Animation Pattern |
|---|
| cover | Fade-in background โ slide-in title โ fade-in subtitle/badges |
| section | Wipe transition โ scale-in icon โ fade-in heading + divider |
| content | Fade โ staggered card/item entrance (spring, 6-frame delay each) |
| comparison | Split columns slide-in from left/right |
| stats | Counter animation (interpolate numbers) โ fade-in labels |
| timeline | Sequential fade-in of timeline items top-to-bottom |
| image-left | Image slides in from left โ text fades in from right |
| image-right | Text fades in from left โ image slides in from right |
| image-full | Ken Burns effect (slow zoom + pan) |
| quote | Fade-in quotation marks โ typewriter text โ fade-in attribution |
| agenda | Staggered list item fade-in |
| ending | Fade-in logo โ slide-in text โ fade-in CTA |
Default timing per slide:
- Enter animation: 1s (30 frames at 30fps)
- Hold: 3s (90 frames) โ adjust based on content density
- Exit/transition: 0.5s (15 frames) via TransitionSeries
Step 4: Generate Remotion Project
Create the project under decks/<deck-name>/video/:
decks/<deck-name>/video/
โโโ package.json
โโโ tsconfig.json
โโโ remotion.config.ts
โโโ src/
โ โโโ Root.tsx # Top-level component with Composition
โ โโโ DeckVideo.tsx # Main component using TransitionSeries
โ โโโ scenes/
โ โ โโโ Scene01Cover.tsx
โ โ โโโ Scene02Section.tsx
โ โ โโโ Scene03Content.tsx
โ โ โโโ ...
โ โโโ components/
โ โโโ SlideBackground.tsx # Background with theme colors
โ โโโ AnimatedText.tsx # Reusable text animation
โ โโโ SlideOverlay.tsx # Logo, copyright, page number
package.json
{
"name": "<deck-name>-video",
"private": true,
"scripts": {
"studio": "remotion studio",
"render": "remotion render DeckVideo out/video.mp4",
"preview": "remotion preview"
},
"dependencies": {
"@remotion/cli": "latest",
"@remotion/transitions": "latest",
"@remotion/google-fonts": "latest",
"@remotion/media": "latest",
"react": "^19",
"react-dom": "^19",
"remotion": "latest"
},
"devDependencies": {
"@types/react": "^19",
"typescript": "^5"
}
}
Key Rules (from remotion-best-practices)
- 16:9 at 1920x1080, 30fps
- All animation via
useCurrentFrame() + interpolate() / spring() โ never CSS transitions or animations
- Fonts via
@remotion/google-fonts โ map from deck theme fonts
- Assets via
staticFile() โ copy deck assets to public/
- Colors: Map directly from deck theme colors extracted in Step 1
- TransitionSeries for slide-to-slide transitions (fade default, matching deck config)
Root.tsx Pattern
import { Composition } from "remotion";
import { DeckVideo } from "./DeckVideo";
export const RemotionRoot: React.FC = () => {
const fps = 30;
const totalDuration = ;
return (
<Composition
id="DeckVideo"
component={DeckVideo}
durationInFrames={totalDuration}
fps={fps}
width={1920}
height={1080}
/>
);
};
DeckVideo.tsx Pattern (TransitionSeries)
import { TransitionSeries } from "@remotion/transitions";
import { fade } from "@remotion/transitions/fade";
import { slide } from "@remotion/transitions/slide";
export const DeckVideo: React.FC = () => {
const fps = 30;
const transitionDuration = Math.round(0.5 * fps);
return (
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={4 * fps}>
<Scene01Cover />
</TransitionSeries.Sequence>
<TransitionSeries.Transition
presentation={fade()}
timing={{ type: "in-out", durationInFrames: transitionDuration }}
/>
<TransitionSeries.Sequence durationInFrames={4 * fps}>
<Scene02Section />
</TransitionSeries.Sequence>
{/* ... more scenes ... */}
</TransitionSeries>
);
};
Scene Component Pattern
import { useCurrentFrame, useVideoConfig, interpolate, spring } from "remotion";
export const Scene01Cover: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const titleOpacity = interpolate(frame, [15, 35], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const titleY = spring({ frame: frame - 15, fps, config: { damping: 15 } });
return (
<div style={{
width: "100%",
height: "100%",
backgroundColor: theme.background,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}>
<h1 style={{
opacity: titleOpacity,
transform: `translateY(${(1 - titleY) * 40}px)`,
fontFamily: theme.fontHeading,
fontWeight: theme.headingWeight,
color: theme.primary,
fontSize: 72,
}}>
{/* Deck title */}
</h1>
</div>
);
};
Step 5: Copy Assets
cp -r decks/<deck-name>/assets/* decks/<deck-name>/video/public/
cp public/amaroad-logo.svg decks/<deck-name>/video/public/
Step 6: Install and Render
cd decks/<deck-name>/video
pnpm install
pnpm exec remotion render DeckVideo out/video.mp4
Step 7: Report Results
Report to the user:
- Output file path:
decks/<deck-name>/video/out/video.mp4
- Total duration (seconds)
- Number of scenes
- Any warnings or issues
Slide Duration Guidelines
Adjust hold time based on content density:
| Content Type | Hold Duration |
|---|
| Cover / Ending | 3s |
| Section divider | 2.5s |
| Simple content (1-3 items) | 3s |
| Dense content (4+ items, tables) | 4-5s |
| Stats / Numbers | 3.5s |
| Quote | 3s |
| Image-focused | 3s |
Total enter+hold+transition per slide is typically 4-5.5 seconds.
Font Mapping
Map Amaroad theme fonts to Remotion Google Fonts:
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
import { loadFont as loadNotoSansJP } from "@remotion/google-fonts/NotoSansJP";
const inter = loadInter();
const notoSansJP = loadNotoSansJP();
If the deck font is not available in Google Fonts, use the closest match or load locally via @remotion/fonts.
Operational Notes
- Always read the
remotion-best-practices rules for API details before generating Remotion code
- Never use CSS
transition, animation, or @keyframes โ Remotion renders frame-by-frame
- All
<img> must be <Img> from @remotion/media
- All durations: multiply seconds by fps (e.g.,
3 * fps for 3 seconds)
- Use
extrapolateLeft: "clamp" and extrapolateRight: "clamp" on all interpolate() calls
- Japanese text needs
Noto Sans JP loaded via @remotion/google-fonts
- For charts/graphs in slides, recreate them as animated SVG (reference
rules/charts.md)
Error Handling
- If
pnpm exec remotion render fails, check the error output and fix the scene component
- Common issues: missing fonts, broken asset paths, invalid interpolation ranges
- Run
pnpm exec remotion studio first to preview in browser before rendering
Examples
Example 1: Convert a 10-slide deck to video
User says: "Make a video from my product-launch deck."
Actions:
- Run
extract-theme.ts --deck product-launch and read deck.config.ts.
- Read all 10 MDX files and build a slide manifest with types and content summaries.
- Design a storyboard mapping each slide type to its animation pattern (cover -> fade-in title, content -> staggered cards, ending -> logo + CTA).
- Generate the Remotion project under
decks/product-launch/video/ with 10 scene components.
- Copy assets and render:
pnpm exec remotion render DeckVideo out/video.mp4.
Result: A 45-second animated video at 1920x1080 30fps with smooth transitions between all 10 slides.
Example 2: Preview before rendering
User says: "Convert sample-deck to video but let me preview first."
Actions:
- Generate the Remotion project under
decks/sample-deck/video/.
- Run
pnpm install && pnpm exec remotion studio to open the browser preview.
- Review each scene in Remotion Studio and adjust timing or animations as needed.
- Render final video after user approval.
Result: Interactive preview in browser, then final rendered MP4 after confirmation.
Troubleshooting
Render fails with "Cannot find module" error
Symptom: pnpm exec remotion render exits with a module resolution error.
Cause: Dependencies were not installed or the import path is incorrect.
Fix: Run pnpm install in the video/ directory. Verify all scene imports in DeckVideo.tsx match the actual filenames in src/scenes/.
Images appear as broken placeholders in the video
Symptom: Rendered video shows broken image icons instead of deck assets.
Cause: Deck assets were not copied to the Remotion project's public/ directory, or staticFile() paths do not match.
Fix: Run cp -r decks/<deck>/assets/* decks/<deck>/video/public/ and ensure scene components reference images via staticFile("filename.png") (not relative paths).
Japanese text renders in a fallback font
Symptom: Japanese characters appear in a generic sans-serif font instead of the deck's theme font.
Cause: Noto Sans JP was not loaded via @remotion/google-fonts.
Fix: Add import { loadFont } from "@remotion/google-fonts/NotoSansJP" and call loadFont() at the component top level. See remotion-best-practices/rules/fonts.md.