A CLI tool that renders motion design videos from JSON scenarios. No browser, no Node.js — just a single Rust binary.
cargo install rustmotionRequirements: Rust toolchain + C++ compiler (for openh264). Recommended: ffmpeg CLI for 10-bit H.264 and H.265/VP9/ProRes/WebM/GIF output.
# Render a video
rustmotion render scenario.json -o video.mp4
# Render with a specific codec
rustmotion render scenario.json -o video.webm --codec vp9 --crf 30
# Export as PNG sequence
rustmotion render scenario.json -o frames/ --format png-seq
# Export as animated GIF
rustmotion render scenario.json -o output.gif --format gif
# Render a single frame for preview
rustmotion render scenario.json --frame 42 -o frame.png
# Validate without rendering
rustmotion validate scenario.json
# Export JSON Schema (for editor autocompletion or LLM prompts)
rustmotion schema -o schema.json
# Show scenario info
rustmotion info scenario.json| Flag | Description | Default |
|---|---|---|
input |
Path to the JSON scenario file | (required) |
-o, --output |
Output file path | output.mp4 |
--frame <N> |
Render a single frame to PNG (0-indexed) | |
--codec <CODEC> |
Video codec: h264, h265, vp9, prores |
h264 |
--crf <0-51> |
Constant Rate Factor (lower = better quality) | 23 |
--format <FMT> |
Output format: mp4, webm, mov, gif, png-seq |
auto from extension |
--transparent |
Transparent background (PNG sequence, WebM, ProRes 4444) | false |
--output-format json |
Machine-readable JSON output for CI pipelines | |
-q, --quiet |
Suppress all output except errors |
{
"version": "1.0",
"video": { ... },
"audio": [ ... ],
"scenes": [ ... ]
}{
"video": {
"width": 1080,
"height": 1920,
"fps": 30,
"background": "#0f172a",
"codec": "h264",
"crf": 23
}
}| Field | Type | Default | Description |
|---|---|---|---|
width |
u32 |
(required) | Video width in pixels (must be even) |
height |
u32 |
(required) | Video height in pixels (must be even) |
fps |
u32 |
30 |
Frames per second |
background |
string |
"#000000" |
Default background color (hex) |
codec |
string |
"h264" |
Video codec: h264, h265, vp9, prores |
crf |
u8 |
23 |
Constant Rate Factor (0-51, lower = better quality) |
{
"audio": [
{
"src": "music.mp3",
"start": 0.0,
"end": 10.0,
"volume": 0.8,
"fade_in": 1.0,
"fade_out": 2.0
}
]
}| Field | Type | Default | Description |
|---|---|---|---|
src |
string |
(required) | Path to audio file (MP3, WAV, OGG, FLAC, AAC) |
start |
f64 |
0.0 |
Start time in the output video (seconds) |
end |
f64 |
End time (omit for full track) | |
volume |
f32 |
1.0 |
Volume multiplier (0.0 - 1.0) |
fade_in |
f64 |
Fade-in duration (seconds) | |
fade_out |
f64 |
Fade-out duration (seconds) |
Each scene is an implicit flex container at video dimensions (default direction: column). Children participate in flex flow automatically. Use positioned container for absolute positioning.
{
"scenes": [
{
"duration": 3.0,
"background": "#1a1a2e",
"layout": {
"direction": "column",
"align_items": "center",
"justify_content": "center",
"gap": 24
},
"children": [ ... ],
"transition": {
"type": "fade",
"duration": 0.5
}
}
]
}| Field | Type | Default | Description |
|---|---|---|---|
duration |
f64 |
(required) | Scene duration in seconds |
background |
string |
Scene background (overrides video.background) |
|
freeze_at |
f64 |
Freeze the scene at this time (seconds) | |
children |
Component[] |
[] |
Components rendered bottom-to-top |
layout |
object |
Scene-level flex layout options | |
transition |
Transition |
Transition effect from the previous scene |
layout options: direction (column/row), gap, align_items (start/center/end/stretch), justify_content (start/center/end/space_between/space_around/space_evenly), padding
Transitions blend between two consecutive scenes. Set on the second scene.
{
"transition": {
"type": "clock_wipe",
"duration": 0.8
}
}| Type | Description |
|---|---|
fade |
Linear crossfade between scenes |
wipe_left |
Horizontal wipe revealing scene B from the left |
wipe_right |
Horizontal wipe revealing scene B from the right |
wipe_up |
Vertical wipe revealing scene B from the top |
wipe_down |
Vertical wipe revealing scene B from the bottom |
zoom_in |
Scene A zooms in and fades out, revealing scene B |
zoom_out |
Scene B zooms out from larger to normal size |
flip |
3D Y-axis flip simulation |
clock_wipe |
Circular clockwise sweep from 12 o'clock |
iris |
Expanding circle from the center reveals scene B |
slide |
Scene B pushes scene A to the left |
dissolve |
Per-pixel noise dissolve |
none |
Hard cut at the midpoint |
| Field | Type | Default | Description |
|---|---|---|---|
type |
string |
(required) | One of the transition types above |
duration |
f64 |
0.5 |
Transition duration in seconds |
Scene entries can reference external scenario files to inject their scenes inline:
{
"scenes": [
{ "include": "shared/intro.json" },
{ "duration": 5.0, "children": [ ... ] },
{ "include": "shared/outro.json", "scenes": [0, 2] }
]
}| Field | Type | Default | Description |
|---|---|---|---|
include |
string |
(required) | Path (relative to parent file) or URL to a scenario JSON |
scenes |
usize[] |
Only include scenes at these 0-based indices. Omit to include all | |
config |
object |
Config overrides to pass to a structural component (see below) |
- The included file's
videoconfig is ignored - Audio tracks from included files are merged
- Includes can be nested (max depth: 8)
Structural components are reusable scenario files with declared variables. When rendered standalone, default values apply. When included from a parent, the parent can override any variable.
Add a config object at the root of a scenario. Each entry has a type, a default value, and an optional description:
{
"config": {
"cta_text": { "type": "string", "default": "Book your demo" },
"accent_color": { "type": "string", "default": "#5C39EE" },
"logo_src": { "type": "string", "default": "assets/logo.svg" },
"counter_target": { "type": "number", "default": 400 },
"tagline_spans": {
"type": "array",
"default": [
{ "text": "Don't ", "color": "#5C39EE" },
{ "text": "miss any lead" }
]
}
},
"video": { "width": 1080, "height": 1920, "fps": 30 },
"scenes": [
{
"duration": 7.0,
"children": [
{ "type": "svg", "src": "$logo_src" },
{ "type": "text", "content": "$cta_text", "style": { "color": "$accent_color" } },
{ "type": "counter", "from": 0, "to": { "$var": "counter_target" } },
{ "type": "rich_text", "spans": { "$var": "tagline_spans" } }
]
}
]
}Supported types: string, number, boolean, object, array. Array and object types allow passing full components (e.g. rich_text spans, children arrays).
| Syntax | Context | Behavior |
|---|---|---|
"$var_name" |
Entire string value | Replaced by the config value (any type) |
"prefix $var_name suffix" |
String interpolation | Replaced inline (value must be string/number/boolean) |
{ "$var": "var_name" } |
Any position | Replaced by the config value (for non-string types in object position) |
"$$literal" |
Escape | Produces the literal string "$literal" |
{
"scenes": [
{ "duration": 5.0, "children": [ ... ] },
{
"include": "components/outro.json",
"config": {
"cta_text": "Try WhatsApp",
"accent_color": "#25D366",
"tagline_spans": [
{ "text": "Stop losing " },
{ "text": "customers", "color": "#25D366" }
]
}
}
]
}Config entries not listed in overrides keep their default values. Referencing an undefined config key produces an error.
When rendering a structural component directly (rustmotion render components/outro.json), all default values are applied automatically.
All components are discriminated by "type". Rendered in array order (first = bottom, last = top).
Available on all component types:
| Field | Type | Default | Description |
|---|---|---|---|
start_at |
f64 |
Component appears at this time (seconds within scene) | |
end_at |
f64 |
Component disappears after this time |
All visual properties are inside a "style" object:
| Style field | Type | Default | Description |
|---|---|---|---|
opacity |
f32 |
1.0 |
0.0 to 1.0 |
padding |
f32 | {top, right, bottom, left} |
Inner spacing | |
margin |
f32 | {top, right, bottom, left} |
Outer spacing | |
animation |
array | object |
[] |
Animation effects (see Animations) |
{
"type": "text",
"content": "Hello World",
"max_width": 800,
"style": {
"font-size": 48,
"color": "#FFFFFF",
"font-family": "Inter",
"font-weight": "bold",
"text-align": "center",
"line-height": 1.2,
"letter-spacing": 2.0,
"animation": [{ "name": "fade_in_up", "delay": 0.3, "duration": 0.6 }]
}
}Root fields: content (required), max_width
| Style field | Type | Default | Description |
|---|---|---|---|
font-size |
f32 |
48.0 |
Font size in pixels |
color |
string |
"#FFFFFF" |
Text color (hex) |
font-family |
string |
"Inter" |
Font family name |
font-weight |
enum |
"normal" |
"normal" or "bold" |
font-style |
enum |
"normal" |
"normal", "italic", "oblique" |
text-align |
enum |
"left" |
"left", "center", "right" |
line-height |
f32 |
Line height multiplier | |
letter-spacing |
f32 |
Additional spacing between characters | |
text-shadow |
object |
{ "color": "#000", "offset_x": 2, "offset_y": 2, "blur": 4 } |
|
stroke |
object |
{ "color": "#000", "width": 2 } |
|
text-background |
object |
{ "color": "#000", "padding": 4, "corner_radius": 4 } |
{
"type": "shape",
"shape": "rounded_rect",
"size": { "width": 300, "height": 200 },
"style": {
"fill": "#3b82f6",
"border-radius": 16,
"stroke": { "color": "#ffffff", "width": 2 },
"animation": [{ "name": "scale_in", "duration": 0.6 }]
}
}Root fields: shape (required), size, text
| Style field | Type | Default | Description |
|---|---|---|---|
fill |
string | gradient |
Fill color (hex) or gradient object | |
stroke |
{color, width} |
Stroke outline | |
border-radius |
f32 |
Corner radius (for rounded_rect) |
Shape types: rect, circle, rounded_rect, ellipse, triangle, star (with points, default 5), polygon (with sides, default 6), path (with data SVG path string)
Gradient fill:
{
"fill": {
"type": "linear",
"colors": ["#667eea", "#764ba2"],
"angle": 135,
"stops": [0.0, 1.0]
}
}Types: linear, radial.
Embedded text in shapes (text field):
{
"type": "shape",
"shape": "circle",
"size": { "width": 56, "height": 56 },
"style": { "fill": "#2A74FF" },
"text": {
"content": "1",
"font_size": 22,
"color": "#FFFFFF",
"font_weight": "bold",
"align": "center",
"vertical_align": "middle"
}
}vertical_align: "top", "middle", "bottom" (default: "middle").
{
"type": "image",
"src": "photo.png",
"size": { "width": 1080, "height": 1080 },
"fit": "cover",
"style": {
"animation": [{ "name": "fade_in", "duration": 0.5 }]
}
}| Field | Type | Default | Description |
|---|---|---|---|
src |
string |
(required) | Path to image file (PNG, JPEG, WebP) |
size |
{width, height} |
Target size (uses native image size if omitted) | |
fit |
string |
"cover" |
"cover", "contain", "fill", "none" |
{
"type": "svg",
"src": "logo.svg",
"size": { "width": 200, "height": 200 }
}Or with inline SVG:
{
"type": "svg",
"data": "<svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='red'/></svg>"
}| Field | Type | Default | Description |
|---|---|---|---|
src |
string |
Path to .svg file |
|
data |
string |
Inline SVG markup | |
size |
{width, height} |
Target size (uses SVG intrinsic size if omitted) |
One of src or data is required.
Renders an icon from the Iconify open-source framework (200,000+ icons from 150+ sets). Icons are fetched from the Iconify API at render time.
Browse all available icons at icon-sets.iconify.design.
{
"type": "icon",
"icon": "lucide:home",
"size": { "width": 64, "height": 64 },
"style": { "color": "#38bdf8" }
}| Field | Type | Default | Description |
|---|---|---|---|
icon |
string |
(required) | Iconify identifier "prefix:name" (e.g. "lucide:home", "mdi:account") |
size |
{width, height} |
24x24 |
Icon size in pixels |
Style: color (default "#FFFFFF")
Common icon sets:
| Prefix | Name | Best for |
|---|---|---|
lucide |
Lucide | Clean UI icons (default choice) |
mdi |
Material Design Icons | Material UI, Android |
heroicons |
Heroicons | Tailwind projects |
ph |
Phosphor | Modern UI |
tabler |
Tabler Icons | Dashboards |
simple-icons |
Simple Icons | Brand/company logos |
devicon |
Devicon | Programming language logos |
Embeds a video clip as a component. Requires ffmpeg on PATH.
{
"type": "video",
"src": "clip.mp4",
"size": { "width": 1080, "height": 1920 },
"trim_start": 2.0,
"trim_end": 8.0,
"playback_rate": 0.5,
"fit": "cover",
"volume": 0.0
}| Field | Type | Default | Description |
|---|---|---|---|
src |
string |
(required) | Path to video file |
size |
{width, height} |
(required) | Display size |
trim_start |
f64 |
0.0 |
Start offset in the source clip (seconds) |
trim_end |
f64 |
End offset in the source clip (seconds) | |
playback_rate |
f64 |
1.0 |
Playback speed (0.5 = half speed, 2.0 = double) |
fit |
string |
"cover" |
"cover", "contain", "fill" |
volume |
f32 |
1.0 |
Audio volume (0.0 = mute) |
loop_video |
bool |
Loop the clip |
Displays an animated GIF, synced to the scene timeline.
{
"type": "gif",
"src": "animation.gif",
"size": { "width": 300, "height": 300 },
"fit": "cover"
}| Field | Type | Default | Description |
|---|---|---|---|
src |
string |
(required) | Path to .gif file |
size |
{width, height} |
Display size (uses GIF native size if omitted) | |
fit |
string |
"cover" |
"cover", "contain", "fill" |
loop_gif |
bool |
true |
Loop the GIF animation |
Timed word-by-word captions with active word highlighting.
{
"type": "caption",
"words": [
{ "text": "Hello", "start": 0.0, "end": 0.5 },
{ "text": "world!", "start": 0.5, "end": 1.0 }
],
"mode": "highlight",
"active_color": "#FFD700",
"max_width": 900,
"style": { "font-size": 48, "color": "#FFFFFF" }
}| Field | Type | Default | Description |
|---|---|---|---|
words |
array |
(required) | [{ "text", "start", "end" }] |
mode |
enum |
"default" |
"default", "highlight", "karaoke", "bounce" |
active_color |
string |
"#FFD700" |
Active word color |
max_width |
f32 |
Maximum width before word-wrapping |
Style: font-size (48.0), font-family, color (#FFFFFF)
Animated number counter. Must be used standalone (not inside a card).
{
"type": "counter",
"from": 0,
"to": 1250,
"decimals": 0,
"separator": " ",
"suffix": "€",
"easing": "ease_out",
"start_at": 0.5,
"end_at": 2.5,
"style": {
"font-size": 72,
"color": "#FFFFFF",
"font-weight": "bold",
"text-align": "center"
}
}| Field | Type | Default | Description |
|---|---|---|---|
from |
f64 |
(required) | Start value |
to |
f64 |
(required) | End value |
decimals |
u8 |
0 |
Number of decimal places |
separator |
string |
Thousands separator (e.g. " ", ",") |
|
prefix |
string |
Text before the number (e.g. "$") |
|
suffix |
string |
Text after the number (e.g. "%", "€") |
|
easing |
string |
"linear" |
Easing for the counter interpolation |
Style: font-size, color, font-family, font-weight, text-align, letter-spacing, text-shadow, stroke
Container that places children at fixed absolute coordinates (like Flutter's Stack/Positioned). Each child uses position: {x, y} relative to the container's top-left.
{
"type": "positioned",
"children": [
{ "type": "shape", "shape": "rect", "position": { "x": 0, "y": 0 }, "size": { "width": 400, "height": 300 }, "style": { "fill": "#1E293B", "border-radius": 16 } },
{ "type": "icon", "icon": "lucide:phone-off", "position": { "x": 170, "y": 120 }, "size": { "width": 64, "height": 64 }, "style": { "color": "#FFFFFF" } }
]
}Invisible wrapper that groups children for shared transforms (scale, opacity, rotation, etc.). Like an HTML <div> with no visual styling. When you scale a container, all children scale together from the container's center — no overlap or distortion.
Use container instead of card when you don't need background, border, or shadow.
{
"type": "container",
"size": { "width": "auto", "height": "auto" },
"style": {
"align-items": "center",
"gap": 36,
"timeline": [
{ "at": 3.5, "animation": [{ "name": "keyframes", "keyframes": [
{ "property": "scale", "keyframes": [{ "time": 0, "value": 1 }, { "time": 0.8, "value": 4 }], "easing": "ease_in" },
{ "property": "opacity", "keyframes": [{ "time": 0, "value": 1 }, { "time": 0.7, "value": 0 }], "easing": "ease_in" }
]}]}
]
},
"children": [
{ "type": "icon", "icon": "lucide:zap", "size": { "width": 80, "height": 80 }, "style": { "color": "#25D366" } },
{ "type": "text", "content": "All children scale together", "style": { "font-size": 48, "color": "#FFFFFF" } }
]
}Supports all flex layout properties (flex-direction, align-items, justify-content, gap, padding) and all style properties (animation, timeline, opacity, margin). No background, border, box-shadow, or clipping — children can overflow freely.
Visual container with CSS-like flex & grid layout. flex is an alias for card. Each dimension of size can be a number or "auto".
Flex example:
{
"type": "card",
"size": { "width": 800, "height": "auto" },
"style": {
"flex-direction": "row",
"align-items": "center",
"gap": 16,
"padding": 24,
"background": "#1E293B",
"border-radius": 16,
"animation": [{ "name": "fade_in_up", "delay": 0.3, "duration": 0.6 }]
},
"children": [
{ "type": "icon", "icon": "lucide:check-circle", "size": { "width": 48, "height": 48 }, "style": { "color": "#22C55E" } },
{ "type": "text", "content": "Feature enabled", "style": { "font-size": 32, "color": "#FFFFFF" } }
]
}Grid example (2x2): Grid containers need an explicit height (not "auto") to prevent rows from stretching.
{
"type": "card",
"size": { "width": 600, "height": 400 },
"style": {
"display": "grid",
"grid-template-columns": [{ "fr": 1 }, { "fr": 1 }],
"grid-template-rows": [{ "fr": 1 }, { "fr": 1 }],
"gap": 16,
"padding": 24,
"background": "#1a1a2e"
},
"children": [
{ "type": "text", "content": "Cell 1", "style": { "color": "#FFFFFF" } },
{ "type": "text", "content": "Cell 2", "style": { "color": "#FFFFFF" } },
{ "type": "text", "content": "Cell 3", "style": { "color": "#FFFFFF" } },
{ "type": "text", "content": "Cell 4", "style": { "color": "#FFFFFF" } }
]
}Style fields:
| Style field | Type | Default | Description |
|---|---|---|---|
display |
enum |
"flex" |
"flex" or "grid" |
background |
string |
Background color (hex) | |
border-radius |
f32 |
12.0 |
Corner radius |
border |
object |
{ "color": "#E5E7EB", "width": 1 } |
|
box-shadow |
object |
{ "color": "#00000040", "offset_x": 0, "offset_y": 4, "blur": 12 } |
|
padding |
f32 | object |
Inner spacing | |
flex-direction |
enum |
"column" |
"column", "row", "column_reverse", "row_reverse" |
flex-wrap |
bool |
false |
Wrap children to next line |
align-items |
enum |
"start" |
"start", "center", "end", "stretch" |
justify-content |
enum |
"start" |
"start", "center", "end", "space_between", "space_around", "space_evenly" |
gap |
f32 |
0 |
Spacing between children |
grid-template-columns |
array |
[{"px": N}, {"fr": N}, "auto"] |
|
grid-template-rows |
array |
Same format as columns |
Per-child layout properties (in child "style"):
flex-grow(f32) — default 0flex-shrink(f32) — default 1flex-basis(f32) — defaults to natural sizealign-self(enum) —"start","center","end","stretch"grid-column(object) —{ "start": 1, "span": 2 }(1-indexed)grid-row(object) —{ "start": 1, "span": 2 }(1-indexed)
Code block with syntax highlighting, chrome, reveal animations, and animated diff transitions.
{
"type": "codeblock",
"code": "fn main() {\n println!(\"Hello\");\n}",
"language": "rust",
"theme": "base16-ocean.dark",
"show_line_numbers": true,
"chrome": { "enabled": true, "title": "main.rs" },
"reveal": { "mode": "typewriter", "start": 0, "duration": 2.5 },
"style": { "font-size": 18, "border-radius": 12, "padding": 16 },
"states": [
{
"code": "fn main() {\n println!(\"Hello, world!\");\n}",
"at": 5.0,
"duration": 2.0,
"cursor": { "enabled": true, "blink": true }
}
]
}Root fields: code (required), language, theme, size, show_line_numbers, chrome, highlights, reveal, states
| Style field | Type | Default |
|---|---|---|
font-family |
string |
"JetBrains Mono" |
font-size |
f32 |
14.0 |
font-weight |
enum |
"normal" |
line-height |
f32 |
1.5 (multiplier) |
background |
string |
(uses theme) |
border-radius |
f32 |
12.0 |
padding |
f32 | object |
16 |
| Field | Type | Default | Description |
|---|---|---|---|
chrome.enabled |
bool |
true |
Show the title bar |
chrome.title |
string |
Title text (e.g. filename) |
{ "highlights": [{ "lines": [2], "color": "#FFFF0022", "start": 3.0, "end": 4.5 }] }| Field | Type | Default | Description |
|---|---|---|---|
reveal.mode |
string |
(required) | "typewriter" or "line_by_line" |
reveal.start |
f64 |
0.0 |
Start time (seconds) |
reveal.duration |
f64 |
1.0 |
Duration (seconds) |
Animate between code versions with automatic diff detection.
| Field | Type | Default | Description |
|---|---|---|---|
states[].code |
string |
(required) | New code content |
states[].at |
f64 |
(required) | Transition start time |
states[].duration |
f64 |
0.6 |
Transition duration |
states[].cursor.enabled |
bool |
true |
Show editing cursor |
states[].cursor.blink |
bool |
true |
Blink the cursor |
Syntect built-in: base16-ocean.dark, base16-ocean.light, base16-eighties.dark, base16-mocha.dark, InspiredGitHub, Solarized (dark), Solarized (light)
Catppuccin: catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha
Shiki / VS Code: andromeeda, aurora-x, ayu-dark, ayu-light, ayu-mirage, dark-plus, dracula, dracula-soft, everforest-dark, everforest-light, github-dark, github-dark-default, github-dark-dimmed, github-dark-high-contrast, github-light, github-light-default, github-light-high-contrast, gruvbox-dark-hard, gruvbox-dark-medium, gruvbox-dark-soft, gruvbox-light-hard, gruvbox-light-medium, gruvbox-light-soft, horizon, horizon-bright, houston, kanagawa-dragon, kanagawa-lotus, kanagawa-wave, laserwave, light-plus, material-theme, material-theme-darker, material-theme-lighter, material-theme-ocean, material-theme-palenight, min-dark, min-light, monokai, night-owl, night-owl-light, nord, one-dark-pro, one-light, plastic, poimandres, red, rose-pine, rose-pine-dawn, rose-pine-moon, slack-dark, slack-ochin, snazzy-light, solarized-dark, solarized-light, synthwave-84, tokyo-night, vesper, vitesse-black, vitesse-dark, vitesse-light
Visual separator line (horizontal or vertical).
{
"type": "divider",
"direction": "horizontal",
"thickness": 2,
"line_style": "solid",
"style": { "color": "#4B5563" }
}| Field | Type | Default | Description |
|---|---|---|---|
direction |
enum |
"horizontal" |
"horizontal" or "vertical" |
thickness |
f32 |
2.0 |
Line thickness in pixels |
line_style |
enum |
"solid" |
"solid", "dashed", "dotted" |
length |
f32 |
Fixed length (omit for 100% of parent) |
Style: color (default "#FFFFFF")
Compact label with text and optional icon, pill-shaped.
{
"type": "badge",
"text": "New",
"icon": "lucide:star",
"variant": "solid",
"badge_size": "md",
"style": { "background": "#3B82F6" }
}| Field | Type | Default | Description |
|---|---|---|---|
text |
string |
(required) | Badge text |
icon |
string |
Iconify icon id (e.g. "lucide:star") |
|
variant |
enum |
"solid" |
"solid" (filled) or "outline" (border only) |
badge_size |
enum |
"md" |
"sm", "md", "lg" |
Style: background (default "#3B82F6") — badge color for solid variant or border color for outline, font-size, font-family
Circular image with optional border and status indicator.
{
"type": "avatar",
"src": "photo.jpg",
"size": 80,
"border_color": "#3B82F6",
"border_width": 3,
"status": "online"
}| Field | Type | Default | Description |
|---|---|---|---|
src |
string |
(required) | Path to image file |
size |
f32 |
64.0 |
Diameter in pixels |
border_color |
string |
Border color (hex) | |
border_width |
f32 |
0.0 |
Border thickness |
status |
enum |
"none" |
"online", "offline", "away", "none" |
status_color |
string |
Override status dot color |
Status colors: online=#22C55E, offline=#9CA3AF, away=#F59E0B
Speech bubble with directional arrow.
{
"type": "callout",
"text": "Hello!",
"arrow_direction": "bottom",
"style": {
"background": "#333333",
"color": "#FFFFFF",
"border-radius": 8,
"font-size": 16
}
}| Field | Type | Default | Description |
|---|---|---|---|
text |
string |
(required) | Callout text |
arrow_direction |
enum |
"bottom" |
"top", "bottom", "left", "right" |
arrow_size |
f32 |
12.0 |
Arrow triangle size |
size |
{width, height} |
Fixed size (auto-sized if omitted) |
Style: background (default "#333333"), color (default "#FFFFFF"), border-radius (default 8), font-size (default 16), font-family
Terminal/console window with colored lines and optional chrome.
{
"type": "terminal",
"title": "Terminal",
"theme": "dark",
"reveal": { "mode": "typewriter", "start": 0.5, "duration": 3.0 },
"lines": [
{ "text": "npm install", "line_type": "prompt" },
{ "text": "added 42 packages", "line_type": "output" }
],
"size": { "width": 600, "height": 300 }
}| Field | Type | Default | Description |
|---|---|---|---|
lines |
array |
(required) | [{ "text", "line_type", "color" }] |
theme |
enum |
"dark" |
"dark" or "light" |
title |
string |
Window title | |
show_chrome |
bool |
true |
Show title bar with traffic light dots |
reveal |
object |
{ "mode": "typewriter"|"line_by_line", "start": 0, "duration": 1, "easing": "linear" } |
|
size |
{width, height} |
Terminal size (default 500x auto) |
Line types: "prompt" (shows $ prefix in green), "command" (white text), "output" (gray text)
Reveal modes: "typewriter" reveals characters one by one, "line_by_line" fades lines in sequentially.
Data table with headers and styled rows.
{
"type": "table",
"headers": ["Name", "Role", "Status"],
"rows": [
["Alice", "Engineer", "Active"],
["Bob", "Designer", "Away"]
],
"size": { "width": 600, "height": 200 },
"style": { "color": "#FFFFFF", "font-size": 14 }
}| Field | Type | Default | Description |
|---|---|---|---|
headers |
string[] |
(required) | Column headers |
rows |
string[][] |
(required) | Data rows |
header_color |
string |
"#374151" |
Header row background |
row_colors |
string[] |
["#1F2937", "#111827"] |
Alternating row colors |
border_color |
string |
"#4B5563" |
Grid line color |
header_text_color |
string |
"#FFFFFF" |
Header text color |
size |
{width, height} |
Table size |
Style: color (default "#FFFFFF") — cell text color, font-size (default 14), font-family, border-radius
Data visualization with bar, line, or pie charts. Animated by default.
{
"type": "chart",
"chart_type": "bar",
"data": [
{ "value": 85, "label": "Q1" },
{ "value": 120, "label": "Q2" },
{ "value": 95, "label": "Q3" },
{ "value": 150, "label": "Q4" }
],
"size": { "width": 400, "height": 300 }
}| Field | Type | Default | Description |
|---|---|---|---|
chart_type |
enum |
(required) | "bar", "line", "pie" |
data |
array |
(required) | [{ "value", "label"?, "color"? }] |
size |
{width, height} |
300x200 |
Chart size |
animated |
bool |
true |
Animate chart fill/draw |
animation_duration |
f64 |
1.5 |
Animation duration in seconds |
colors |
string[] |
Custom color palette (hex) |
Default palette: #3B82F6, #EF4444, #22C55E, #F59E0B, #8B5CF6, #EC4899, #06B6D4, #F97316
Device frame (phone, laptop, browser) with image content inside.
{
"type": "mockup",
"device": "iphone",
"src": "screenshot.png",
"theme": "dark",
"size": { "width": 375, "height": 812 }
}| Field | Type | Default | Description |
|---|---|---|---|
device |
enum |
(required) | "iphone", "android", "laptop", "browser" |
src |
string |
(required) | Path to content image |
theme |
enum |
"dark" |
"dark" or "light" bezel color |
size |
{width, height} |
Device size (defaults: iPhone 375x812, Android 360x800, Laptop 800x550, Browser 800x600) |
Animated particle system for visual effects (confetti, snow, stars, bubbles).
{
"type": "particle",
"particle_type": "confetti",
"count": 80,
"speed": 1.2,
"seed": 42
}| Field | Type | Default | Description |
|---|---|---|---|
particle_type |
enum |
(required) | "confetti", "snow", "stars", "bubbles", "halo" |
count |
u32 |
50 |
Number of particles |
colors |
string[] |
Custom colors (defaults vary by type) | |
speed |
f32 |
1.0 |
Speed multiplier |
size_range |
{min, max} |
{4, 12} |
Particle size range in pixels |
seed |
u64 |
42 |
Random seed for reproducible results |
Particle behaviors:
- confetti: colored rectangles falling with rotation and horizontal wobble
- snow: white circles falling gently with lateral drift
- stars: fixed positions with twinkling opacity
- bubbles: semi-transparent circles rising with oscillation
- halo: soft glowing circles drifting slowly with pulsing opacity (great for backgrounds)
Directional arrow with optional bezier curves. Use draw_in or stroke_reveal animation presets for drawing effects.
{
"type": "arrow",
"x1": 100, "y1": 300,
"x2": 500, "y2": 300,
"curve": 0.3,
"width": 3,
"color": "#58A6FF",
"arrow_end": true,
"style": {
"animation": [{ "name": "draw_in", "duration": 1.0 }]
}
}| Field | Type | Default | Description |
|---|---|---|---|
x1, y1 |
f32 |
0.0 |
Start point |
x2, y2 |
f32 |
(required) | End point |
cp |
{x, y} |
Quadratic bezier control point | |
cp1, cp2 |
{x, y} |
Cubic bezier control points | |
curve |
f32 |
Auto-curve (-1.0 to 1.0) | |
width |
f32 |
3.0 |
Stroke width |
color |
string |
"#FFFFFF" |
Arrow color |
arrow_end |
bool |
true |
Arrowhead at end |
arrow_start |
bool |
false |
Arrowhead at start |
arrow_size |
f32 |
12.0 |
Arrowhead size |
dashed |
f32[] |
Dash pattern (e.g. [8, 4]) |
Connects two points with automatic routing. Great for diagrams and flowcharts.
{
"type": "connector",
"from": { "x": 200, "y": 150 },
"to": { "x": 600, "y": 400 },
"routing": "curved",
"color": "#58A6FF",
"arrow_end": true,
"style": {
"animation": [{ "name": "stroke_reveal", "duration": 0.8 }]
}
}| Field | Type | Default | Description |
|---|---|---|---|
from |
{x, y} |
(required) | Start point |
to |
{x, y} |
(required) | End point |
routing |
string |
"straight" |
"straight", "curved", "elbow" (L-shaped) |
curvature |
f32 |
0.4 |
Curve intensity (for curved routing) |
width |
f32 |
2.0 |
Stroke width |
color |
string |
"#FFFFFF" |
Line color |
arrow_end |
bool |
true |
Arrowhead at end |
arrow_start |
bool |
false |
Arrowhead at start |
arrow_size |
f32 |
10.0 |
Arrowhead size |
dashed |
f32[] |
Dash pattern |
Step-by-step timeline with animated progress bar and node icons.
{
"type": "timeline",
"width": 800,
"direction": "horizontal",
"fill_progress": 0.75,
"steps": [
{ "label": "Design", "sublabel": "Week 1", "color": "#58A6FF", "icon": "1" },
{ "label": "Build", "sublabel": "Week 2-3", "color": "#58A6FF", "icon": "2" },
{ "label": "Ship", "sublabel": "Week 4", "color": "#22C55E", "icon": "🚀" }
]
}| Field | Type | Default | Description |
|---|---|---|---|
steps |
array |
(required) | [{ "label", "sublabel"?, "color"?, "icon"? }] |
width |
f32 |
800.0 |
Timeline width |
direction |
string |
"horizontal" |
"horizontal" or "vertical" |
node_radius |
f32 |
24.0 |
Circle radius |
bar_color |
string |
"#333333" |
Background bar color |
bar_fill_color |
string |
"#58A6FF" |
Filled bar color |
fill_progress |
f32 |
1.0 |
Progress 0.0 to 1.0 |
Renders Lottie animations from pre-rendered PNG frame sequences.
{
"type": "lottie",
"src": "animation.json",
"frames_dir": "/path/to/frames",
"size": { "width": 300, "height": 300 },
"speed": 1.0,
"loop": true
}| Field | Type | Default | Description |
|---|---|---|---|
src |
string |
Path to Lottie JSON (for timing metadata) | |
data |
string |
Inline Lottie JSON data | |
frames_dir |
string |
Directory with numbered PNGs (0000.png, 0001.png, ...) |
|
size |
{width, height} |
Display size (falls back to Lottie intrinsic size) | |
speed |
f32 |
1.0 |
Playback speed multiplier |
loop |
bool |
true |
Loop the animation |
Generate frames with: npx lottie-to-frames animation.json --output frames/
Animated cursor with click effects, blinking, and smooth path animation.
{
"type": "cursor",
"color": "#FFFFFF",
"blink": 0.5,
"auto_path": [
{ "time": 0.5, "x": 100, "y": 200 },
{ "time": 1.5, "x": 400, "y": 300 },
{ "time": 2.5, "x": 600, "y": 150 }
],
"path_easing": "ease_in_out",
"position": { "x": 200, "y": 200 }
}| Field | Type | Default | Description |
|---|---|---|---|
width |
f32 |
3.0 |
Cursor width |
height |
f32 |
40.0 |
Cursor height |
color |
string |
"#FFFFFF" |
Cursor color |
blink |
f32 |
0.5 |
Blink cycle (0 = no blink) |
click_at |
f64[] |
[] |
Click animation times (seconds) |
auto_path |
array |
[] |
Waypoints: [{ "time", "x", "y" }] |
click_duration |
f32 |
0.3 |
Click animation duration |
path_easing |
string |
"ease_in_out" |
"linear", "ease_out", "ease_in_out" |
When auto_path is set, clicks trigger at each waypoint. Uses Catmull-Rom spline interpolation for smooth curves.
Simple line from point A to point B. Supports draw_in animation.
{
"type": "line",
"x1": 0, "y1": 0,
"x2": 400, "y2": 200,
"width": 2,
"color": "#58A6FF",
"style": { "animation": [{ "name": "draw_in", "duration": 0.8 }] }
}| Field | Type | Default | Description |
|---|---|---|---|
x1, y1 |
f32 |
0.0 |
Start point |
x2, y2 |
f32 |
(required) | End point |
width |
f32 |
2.0 |
Stroke width |
color |
string |
"#FFFFFF" |
Line color |
dashed |
f32[] |
Dash pattern |
Multi-styled text with individually styled spans. Each span inherits unset properties from the component's style.
{
"type": "rich_text",
"spans": [
{ "text": "Hello ", "color": "#FFFFFF", "font-weight": "bold" },
{ "text": "World", "color": "#58A6FF", "font-size": 64 }
],
"max_width": 800,
"style": { "font-size": 48, "color": "#FFFFFF" }
}| Field | Type | Default | Description |
|---|---|---|---|
spans |
array |
(required) | [{ "text", "color"?, "font-size"?, "font-weight"?, "font-family"?, "font-style"?, "letter-spacing"? }] |
max_width |
f32 |
Maximum width before wrapping |
Scenes support a virtual camera with animatable pan, zoom, and rotation.
{
"duration": 5.0,
"camera": {
"x": 0, "y": 0, "zoom": 1.0, "rotation": 0,
"keyframes": [
{ "property": "zoom", "values": [{ "time": 0, "value": 1.0 }, { "time": 3, "value": 1.5 }], "easing": "ease_in_out" },
{ "property": "x", "values": [{ "time": 0, "value": 0 }, { "time": 3, "value": -100 }], "easing": "ease_out" }
]
},
"children": [...]
}| Field | Type | Default | Description |
|---|---|---|---|
x |
f32 |
0.0 |
Camera center X offset |
y |
f32 |
0.0 |
Camera center Y offset |
zoom |
f32 |
1.0 |
Zoom factor (2.0 = 2x in) |
rotation |
f32 |
0.0 |
Rotation in degrees |
keyframes |
array |
[] |
[{ "property", "values": [{"time", "value"}], "easing" }] |
Scenes can have animated gradient backgrounds. Gradients are interpolated in linear color space with subdivided color stops for smooth dark transitions.
{
"duration": 5.0,
"animated-background": {
"colors": ["#667eea", "#764ba2", "#f093fb"],
"speed": 30,
"gradient_type": "linear"
},
"children": [...]
}| Field | Type | Default | Description |
|---|---|---|---|
colors |
string[] |
[] |
Gradient colors (hex) |
speed |
f32 |
30.0 |
Animation speed |
gradient_type |
string |
"linear" |
"linear" or "radial" |
preset |
string |
"gradient_shift", "concentric_circles", "grid_dots" |
|
element_size |
f32 |
4.0 |
Dot size for grid_dots; stroke width for concentric_circles |
spacing |
f32 |
60.0 |
Element spacing for grid_dots/concentric_circles |
count |
u32 |
Number of circles for concentric_circles (overrides spacing) |
All animation effects are defined inside style.animation as a typed array, each discriminated by "name". A single effect (without array) is also accepted.
{
"style": {
"animation": [
{ "name": "fade_in_up", "delay": 0.2, "duration": 0.8 },
{ "name": "glow", "color": "#6366F1", "radius": 20, "intensity": 2.0 },
{ "name": "wiggle", "property": "translate_y", "amplitude": 5, "frequency": 0.8, "seed": 42 }
]
}
}| Effect name | Fields | Description |
|---|---|---|
| preset name | delay, duration, loop, overshoot |
Any of the 39 presets (e.g. fade_in_up, scale_in) |
| char preset | delay, duration, stagger, granularity, easing, overshoot |
Per-char/word text animation: char_scale_in, char_fade_in, char_wave, char_bounce, char_rotate_in, char_slide_up |
glow |
color, radius, intensity |
Luminous halo effect |
wiggle |
property, amplitude, frequency, mode, seed, ... |
Procedural noise animation |
orbit |
radius_x, radius_y, speed, depth, tilt, ... |
Elliptical orbital motion with pseudo-3D |
keyframes |
keyframes |
Custom keyframe animations |
motion_blur |
intensity |
Motion blur effect |
Components also support a timeline field (array of { "at": f64, "animation": [...] } steps) for multi-phase sequential animations within a scene.
{
"style": {
"animation": [{ "name": "fade_in_up", "delay": 0.2, "duration": 0.8, "loop": false }]
}
}| Field | Type | Default | Description |
|---|---|---|---|
delay |
f64 |
0.0 |
Delay before animation starts (seconds) |
duration |
f64 |
0.8 |
Animation duration (seconds) |
loop |
bool |
false |
Loop the animation continuously |
overshoot |
f64 |
0.08 |
Overshoot/anticipation intensity for scale_in/scale_out (0.0 = none) |
| Preset | Description |
|---|---|
fade_in |
Fade from transparent |
fade_in_up |
Fade in + slide up |
fade_in_down |
Fade in + slide down |
fade_in_left |
Fade in + slide from left |
fade_in_right |
Fade in + slide from right |
slide_in_left |
Slide in from far left |
slide_in_right |
Slide in from far right |
slide_in_up |
Slide in from below |
slide_in_down |
Slide in from above |
scale_in |
Scale up from 0 with overshoot (configurable via overshoot, default 8%) |
bounce_in |
Bouncy scale from small to normal |
blur_in |
Fade in from blurred |
rotate_in |
Rotate + scale from half size |
elastic_in |
Elastic underdamped spring scale |
| Preset | Description |
|---|---|
fade_out |
Fade to transparent |
fade_out_up |
Fade out + slide up |
fade_out_down |
Fade out + slide down |
slide_out_left |
Slide out to the left |
slide_out_right |
Slide out to the right |
slide_out_up |
Slide out upward |
slide_out_down |
Slide out downward |
scale_out |
Scale down to 0 with anticipation (configurable via overshoot, default 8%) |
bounce_out |
Bouncy scale to small |
blur_out |
Fade out with blur |
rotate_out |
Rotate + scale to half size |
Use "loop": true for continuous animation:
| Preset | Description |
|---|---|
pulse |
Gentle scale oscillation |
float |
Vertical floating motion |
shake |
Horizontal shake |
spin |
360-degree continuous rotation |
float_3d |
Floating + subtle 3D rotation with perspective |
| Preset | Description |
|---|---|
flip_in_x |
3D flip around X axis (card flip from top) |
flip_in_y |
3D flip around Y axis (card flip from side) |
flip_out_x |
3D flip out around X axis |
flip_out_y |
3D flip out around Y axis |
tilt_in |
3D tilt entrance (rotate_x + rotate_y) |
For arrows, connectors, and lines:
| Preset | Description |
|---|---|
draw_in |
Animate draw_progress from 0 to 1 (stroke drawing effect) |
stroke_reveal |
draw_in + fade-in opacity over first 20% of duration |
| Preset | Description |
|---|---|
typewriter |
Progressive character reveal |
wipe_left |
Slide in from left with fade |
wipe_right |
Slide in from right with fade |
{
"style": {
"animation": [
{
"name": "keyframes",
"keyframes": [
{
"property": "opacity",
"keyframes": [
{ "time": 0.0, "value": 0.0 },
{ "time": 0.5, "value": 1.0 }
],
"easing": "ease_out"
}
]
}
]
}
}Animatable properties: opacity, translate_x, translate_y, scale_x, scale_y, scale (both axes), rotation, blur, color, rotate_x, rotate_y, perspective
11 easing functions: linear, ease_in, ease_out, ease_in_out, ease_in_quad, ease_out_quad, ease_in_cubic, ease_out_cubic, ease_in_expo, ease_out_expo, spring
Spring physics (when easing is spring):
{
"easing": "spring",
"spring": { "damping": 15, "stiffness": 100, "mass": 1 }
}{
"style": {
"animation": [
{ "name": "glow", "color": "#ff00ff", "radius": 20, "intensity": 2.5 }
]
}
}| Field | Type | Default | Description |
|---|---|---|---|
color |
string |
"#FFFFFF" |
Glow color (hex) |
radius |
f32 |
10.0 |
Blur radius |
intensity |
f32 |
1.0 |
Brightness multiplier |
Wiggle adds continuous organic movement. Offsets are applied additively on top of presets and keyframes.
{
"style": {
"animation": [
{ "name": "wiggle", "property": "translate_x", "amplitude": 5, "frequency": 3, "seed": 42 },
{ "name": "wiggle", "property": "rotation", "amplitude": 2, "frequency": 2, "seed": 99, "decay": 0.5 }
]
}
}| Field | Type | Default | Description |
|---|---|---|---|
property |
string |
(required) | Property to wiggle (same as animatable properties) |
amplitude |
f64 |
(required) | Maximum deviation (pixels for translate, degrees for rotation) |
frequency |
f64 |
(required) | Cycles per second (Hz) |
mode |
string |
"noise" |
"noise" (layered simplex) or "sine" (pure sine wave) |
seed |
u64 |
0 |
Random seed for reproducible results (noise mode only) |
octaves |
u32 |
3 |
Noise complexity (noise mode only) |
phase |
f64 |
0.0 |
Phase offset |
decay |
f64 |
Exponential decay rate | |
easing |
string |
Remap noise through an easing curve |
Creates continuous circular or elliptical orbital motion with pseudo-3D depth. Applied additively like wiggle.
{
"style": {
"animation": [
{ "name": "orbit", "radius_x": 30, "radius_y": 20, "speed": 0.5, "depth": 0.15, "tilt": 20 }
]
}
}| Field | Type | Default | Description |
|---|---|---|---|
radius_x |
f64 |
30.0 |
Horizontal orbit radius |
radius_y |
f64 |
30.0 |
Vertical orbit radius |
speed |
f64 |
0.5 |
Revolutions per second |
start_angle |
f64 |
0.0 |
Starting angle in degrees |
depth |
f64 |
0.15 |
Scale modulation for pseudo-3D |
opacity_depth |
f64 |
0.0 |
Opacity modulation for depth |
tilt |
f64 |
0.0 |
Orbit plane tilt in degrees |
phase |
f64 |
0.0 |
Phase offset (0.0 to 1.0) |
Animate each character or word independently with staggered timing. Use char_* animation presets inside style.animation on text components.
Char animation presets: char_scale_in, char_fade_in, char_wave, char_bounce, char_rotate_in, char_slide_up
{
"type": "text",
"content": "Hello World",
"style": {
"font-size": 64, "color": "#FFFFFF",
"animation": [{ "name": "char_scale_in", "stagger": 0.03, "duration": 0.4, "delay": 0.2 }]
}
}| Field | Type | Default | Description |
|---|---|---|---|
stagger |
f64 |
0.03 |
Delay between each unit (seconds) |
duration |
f64 |
0.4 |
Each unit's animation duration |
delay |
f64 |
0.0 |
Initial delay before first unit |
easing |
string |
"linear" |
Easing function |
granularity |
string |
"char" |
"char" (per-character) or "word" (per-word) |
overshoot |
f64 |
0.08 |
Overshoot intensity for char_scale_in/char_bounce (0.0 = none) |
Per-word mode ("granularity": "word") splits text by whitespace and animates each word as a unit. Use larger stagger values (0.1–0.3s) for word reveals:
{
"type": "text",
"content": "One platform to rule them all",
"style": {
"font-size": 56, "color": "#FFFFFF", "font-weight": "bold",
"animation": [{ "name": "char_fade_in", "stagger": 0.15, "duration": 0.5, "granularity": "word" }]
}
}Any component can be rendered with true 3D perspective using keyframe animations on rotate_x, rotate_y, and perspective properties:
{
"style": {
"box-shadow": { "color": "#00000060", "offset_x": 0, "offset_y": 20, "blur": 60 },
"animation": [{
"name": "keyframes",
"keyframes": [
{ "property": "rotate_x", "keyframes": [{ "time": 0, "value": 20 }, { "time": 2, "value": 8 }], "easing": "ease_out" },
{ "property": "rotate_y", "keyframes": [{ "time": 0, "value": -15 }, { "time": 2, "value": -5 }], "easing": "ease_out" },
{ "property": "perspective", "keyframes": [{ "time": 0, "value": 800 }, { "time": 2, "value": 800 }], "easing": "linear" }
]
}]
}
}Uses a Skia M44 4x4 matrix for real 3D rendering. Components with box-shadow get 3D adaptive shadows — the shadow automatically shifts and scales based on tilt angles.
Define sequential animation phases within a single scene using the timeline field:
{
"style": {
"animation": [{ "name": "fade_in_up", "duration": 0.6 }],
"timeline": [
{ "at": 2.0, "animation": [{ "name": "shake", "duration": 0.5 }] },
{ "at": 4.0, "animation": [{ "name": "fade_out", "duration": 0.8 }] }
]
}
}Each step activates at step.at seconds, with animations resolved relative to that time. Steps merge additively with base animations.
{
"style": {
"animation": [
{ "name": "motion_blur", "intensity": 0.8 }
]
}
}Renders multiple sub-frames and composites them for physically-correct motion blur.
| Format | Command | Requires |
|---|---|---|
| MP4 (H.264 10-bit) | rustmotion render in.json -o out.mp4 |
ffmpeg (auto-detected) |
| MP4 (H.264 8-bit) | rustmotion render in.json -o out.mp4 |
Built-in (fallback without ffmpeg) |
| MP4 (H.265) | rustmotion render in.json -o out.mp4 --codec h265 |
ffmpeg |
| WebM (VP9) | rustmotion render in.json -o out.webm --codec vp9 |
ffmpeg |
| MOV (ProRes) | rustmotion render in.json -o out.mov --codec prores |
ffmpeg |
| Animated GIF | rustmotion render in.json -o out.gif --format gif |
Built-in |
| PNG Sequence | rustmotion render in.json -o frames/ --format png-seq |
Built-in |
| Single Frame | rustmotion render in.json --frame 0 -o preview.png |
Built-in |
Transparency is supported with --transparent for PNG sequences, WebM (VP9), and ProRes 4444.
Gradient quality: When ffmpeg is available, H.264 uses 10-bit color depth (
yuv420p10le,high10profile) which greatly reduces banding on dark gradients. For maximum quality, use--codec prores. The built-in openh264 encoder (fallback without ffmpeg) outputs 8-bit only.
{
"version": "1.0",
"video": {
"width": 1080,
"height": 1920,
"fps": 30,
"background": "#0f172a"
},
"scenes": [
{
"duration": 4.0,
"layout": { "align_items": "center", "justify_content": "center", "gap": 32 },
"children": [
{
"type": "shape",
"shape": "rounded_rect",
"size": { "width": 900, "height": 520 },
"style": {
"fill": {
"type": "linear",
"colors": ["#6366f1", "#8b5cf6"],
"angle": 135
},
"border-radius": 32,
"animation": [{ "name": "scale_in", "duration": 0.6 }]
}
},
{
"type": "icon",
"icon": "lucide:rocket",
"size": { "width": 80, "height": 80 },
"style": {
"color": "#FFFFFF",
"animation": [{ "name": "fade_in_up", "delay": 0.3, "duration": 0.6 }]
}
},
{
"type": "text",
"content": "Ship Faster",
"style": {
"font-size": 64,
"color": "#FFFFFF",
"font-weight": "bold",
"text-align": "center",
"animation": [{ "name": "fade_in_up", "delay": 0.5, "duration": 0.6 }]
}
},
{
"type": "text",
"content": "Build motion videos in Rust.\nNo browser needed.",
"max_width": 700,
"style": {
"font-size": 32,
"color": "#CBD5E1",
"text-align": "center",
"line-height": 1.5,
"animation": [{ "name": "fade_in_up", "delay": 0.7, "duration": 0.6 }]
}
}
]
},
{
"duration": 3.0,
"background": "#1e293b",
"transition": { "type": "iris", "duration": 0.8 },
"layout": { "align_items": "center", "justify_content": "center" },
"children": [
{
"type": "text",
"content": "No browser needed.",
"style": {
"font-size": 56,
"color": "#e2e8f0",
"text-align": "center",
"animation": [{ "name": "typewriter", "duration": 1.5 }]
}
}
]
}
]
}- Rendering: skia-safe (same engine as Chrome/Flutter)
- Video encoding: openh264 (Cisco BSD, compiled from source) + ffmpeg (optional, for H.265/VP9/ProRes)
- Audio encoding: AAC via minimp4
- SVG rendering: resvg + usvg
- Icon rendering: Iconify API (200k+ icons)
- GIF decoding/encoding: gif crate
- MP4 muxing: minimp4
- JSON Schema: schemars (auto-generated from Rust types)
- Parallelism: rayon (multi-threaded frame rendering)
MIT