Package Exports
- motionmark
- motionmark/engine
- motionmark/exporter
- motionmark/renderer
- motionmark/schema
- motionmark/validator
Readme
MotionMark
Markdown for Motion Graphics. Text-in, video-out.
MotionMark is a domain-specific language (DSL) for creating motion graphics and animations using simple, readable text files. Write declarative code, preview and export in browser.
Quick Start
Requirements
- Node.js
Installation
npm install
npm run buildPreview
npm run preview
# or with a specific file
npm run preview examples/showcase/trig-graphs.mmarkOpen http://localhost:5175 in your browser. Edit code on the left, see live preview on the right. Export to WebM/MP4 directly from the browser.
File Format
MotionMark files use the .mmark extension.
Basic Structure
---
canvas: 1920x1080
bg: #0f0f1a
fps: 60
$accent: #e74c5c
$teal: #4ecdc4
---
rect r1 w:200 h:100 fill:#e74c5c | 0s - 3s
x: 100 -> 500 over 2s ease-out
y: 200
opacity: 0 -> 1 over 0.5sSyntax Reference
Header Block
Define canvas settings and variables at the top of your file:
---
canvas: 640x360
bg: #0f0f1a
fps: 24
$accent: #e74c5c
$teal: #4ecdc4
$speed: 80
---| Property | Description | Example |
|---|---|---|
canvas |
Width x Height in pixels | 1920x1080, 640x360 |
bg |
Background color (hex) | #0f0f1a |
fps |
Frames per second | 24, 30, 60 |
$name |
Variable declaration | $accent: #e74c5c |
Elements
Rectangle
rect id w:WIDTH h:HEIGHT fill:COLOR
x: VALUE
y: VALUECircle
circle id r:RADIUS fill:COLOR
x: VALUE
y: VALUEEllipse
ellipse id rx:RX ry:RY fill:COLOR
x: VALUE
y: VALUEText
text id "Content" font:FONTNAME size:SIZE fill:COLOR
x: VALUE
y: VALUELine
line id from:(X1,Y1) to:(X2,Y2) stroke:COLOR strokeWidth:WIDTHGradients
Use gradients anywhere a fill or stroke paint is accepted. Stops are written as color, offset pairs, where offsets run from 0 to 1.
rect panel w:240 h:120 | 0s - 3s
fill: linear(0, 0, 240, 0, #e74c5c, 0, #4ecdc4, 1)
circle glow r:80 | 0s - 3s
fill: radial(0, 0, 0, 0, 0, 80, #fbbf24, 0, #ef4444, 1)Path
Paths support 4 creation methods:
1. SVG d syntax (from design tools, bezier curves)
path arrow d:"M 10 50 L 90 50 L 70 30 M 90 50 L 70 70" stroke:#fff strokeWidth:22. Points array (manual coordinates, polygons)
path triangle [(100,200), (150,100), (200,200)] fill:#e74c5c closed:true3. Function path fx (graphs, waves — y depends on x)
path sine fx:"200 - 50 * sin(x * 0.05)" xRange:"0-400" steps:50 stroke:#60a5fa strokeWidth:24. Parametric path xt/yt (circles, spirals, hearts)
path circle xt:"200 + 80 * cos(t)" yt:"200 + 80 * sin(t)" tRange:"0-6.28" steps:60 stroke:#4ade80 strokeWidth:2When to use which:
| Method | Use when |
|---|---|
d:"..." |
You have SVG path data from Figma/Illustrator |
[(x,y), ...] |
You have exact coordinates for a simple shape |
fx:"..." |
Drawing a graph/wave where y = f(x) |
xt/yt:"..." |
Drawing shapes that loop back (circles, spirals, hearts) |
Quick test: Can you draw it left-to-right without going backwards on x?
- Yes → use
fx - No → use
xt+yt
Image
image id "filename.png" w:WIDTH h:HEIGHT
x: VALUE
y: VALUEElement Lifetime
Control when elements appear and disappear:
rect r1 w:200 h:100 fill:#e74c5c | 0s - 3s # visible from 0s to 3s
rect r2 w:200 h:100 fill:#4ecdc4 | 2s - 5s # visible from 2s to 5s
rect r3 w:200 h:100 fill:#fff | persist # visible entire durationAnimation
Declarative Animation (Recommended)
rect r1 w:200 h:100 fill:#e74c5c | 0s - 3s
x: 100 -> 500 over 2s ease-out # animate from 100 to 500
opacity: 0 -> 1 over 0.5s # fade in
scale: 1 -> 1.2 -> 1 over 1s ease-in-out # scale up then downMulti-step Keyframes
circle ball r:18 fill:#e74c5c | 0s - 4s
y: 272 at 0s, 112 at 0.8s ease-out, 272 at 1.6s ease-in, 150 at 2.4s ease-outExpression Mode (Advanced)
Use mathematical expressions with f(t) where t is time in seconds:
circle ball r:14 fill:#e74c5c | 0.8s - 4s
x: f(t) = 74 + 80 * t
y: f(t) = 260 - (120 * t - 35 * t^2)Available math functions: sin, cos, tan, sqrt, abs, min, max, pow
Use degrees with the deg suffix: cos(45deg)
Easing Functions
| Easing | Description |
|---|---|
linear |
Constant speed |
ease-in |
Slow start |
ease-out |
Slow end |
ease-in-out |
Slow start and end |
cubic-bezier(x1,y1,x2,y2) |
Custom bezier curve |
Variables
Define in header, use with $:
---
$accent: #e74c5c
$speed: 80
---
circle ball r:20 fill:$accent
x: f(t) = 200 + $speed * tScenes
Organize animations into logical sections:
= Setup | 0s - 5s =
rect title w:400 h:80 fill:#4ecdc4
x: 100
y: 100
= Launch | 5s - 10s =
circle ball r:20 fill:#e74c5c | 5s - 10s
x: f(t) = 200 + 50 * tElements without explicit lifetime inherit their scene's time bounds.
Custom Motions (@motion)
Define reusable animation patterns:
@motion fade-in(dur=0.4s, ease=ease-out)
opacity: 0 -> 1 over $dur $ease
@motion slide-up(from, to, dur=0.7s, ease=ease-out)
y: $from -> $to over $dur $ease
@motion projectile(vx, vy, g, x0, y0)
x: f(t) = $x0 + $vx * t
y: f(t) = $y0 - ($vy * t - 0.5 * $g * t^2)Apply motions to elements:
text title "Hello" font:Roboto size:48 fill:#fff | 0s - 4s
x: 100
fade-in()
slide-up(from: 200, to: 150)
circle ball r:20 fill:#e74c5c | 2s - 8s
projectile(vx: 160, vy: 330, g: 250, x0: 220, y0: 780)Standard Library Motions
@motion fade-in(dur=0.5s, ease=ease-out)
@motion fade-out(dur=0.5s, ease=ease-in)
@motion slide-right(from, to, dur=1s, ease=ease-out)
@motion slide-left(from, to, dur=1s, ease=ease-out)
@motion slide-down(from, to, dur=1s, ease=ease-out)
@motion slide-up(from, to, dur=1s, ease=ease-out)
@motion scale-in(dur=0.5s, ease=ease-out)
@motion scale-out(dur=0.5s, ease=ease-in)
@motion pulse(dur=0.8s, ease=ease-in-out)
@motion drift-x(speed)Audio Tracks
Add independent audio elements to your animation timeline:
audio "bg-music.mp3" | 0s - end volume:0.3 fade-in:2000 fade-out:1000
audio "whoosh.wav" | 1.5s - 2.5s volume:0.8| Property | Description | Default |
|---|---|---|
volume |
Playback volume (0-1) | 1 |
pan |
Stereo pan (-1 left, 1 right) | 0 |
fade-in |
Fade-in duration in ms | 0 |
fade-out |
Fade-out duration in ms | 0 |
trim |
Skip this many ms from the start of the file | 0 |
loop |
Loop the audio (true/false) |
false |
Audio files are resolved relative to the .mmark file's directory.
Masking
Apply rectangular masks to elements:
rect panel w:440 h:150 fill:#e74c5c | 0s - 4s
x: 100
y: 112
mask: rect(100, 112, 0, 150)Examples
Bouncing Ball
---
canvas: 640x360
bg: #0f0f1a
fps: 24
$accent: #e74c5c
---
@motion fade-in(dur=0.3s, ease=ease-out)
opacity: 0 -> 1 over $dur $ease
text title "Bouncing Ball" font:Roboto size:28 fill:#ffffff | 0s - 4s
x: 36
y: 54
fade-in()
line floor from:(40,304) to:(600,304) stroke:#ffffff55 strokeWidth:3 | persist
circle ball r:18 fill:$accent | 0s - 4s
x: 80 -> 520 over 4s linear
y: 272 at 0s, 112 at 0.8s ease-out, 272 at 1.6s ease-in, 150 at 2.4s ease-out, 272 at 3.2s ease-inProduct Intro
---
canvas: 640x360
bg: #111827
fps: 24
$accent: #e74c5c
$teal: #4ecdc4
---
@motion fade-in(dur=0.4s, ease=ease-out)
opacity: 0 -> 1 over $dur $ease
@motion slide-up(from, to, dur=0.7s, ease=ease-out)
y: $from -> $to over $dur $ease
rect logo w:72 h:72 fill:$accent | 0s - 4s
x: 70
y: 110
opacity: 0 -> 1 over 0.4s
text brand "MotionMark" font:Roboto size:42 fill:#ffffff | 0.25s - 4s
x: 170
slide-up(from: 164, to: 142)
fade-in(0.5s)
text tagline "Markdown for motion graphics" font:Roboto size:22 fill:#cbd5e1 | 0.8s - 4s
x: 174
slide-up(from: 216, to: 194, dur: 0.55s)
fade-in(0.45s)Physics Simulation
@motion projectile(vx, vy, g, x0, y0)
x: f(t) = $x0 + $vx * t
y: f(t) = $y0 - ($vy * t - 0.5 * $g * t^2)
circle ball r:20 fill:#e74c5c | 2s - 8s
projectile(vx: 160, vy: 330, g: 250, x0: 220, y0: 780)
opacity: 0 -> 1 over 0.2sProperties Reference
Common Properties
| Property | Description | Animatable |
|---|---|---|
x |
Horizontal position | Yes |
y |
Vertical position | Yes |
opacity |
Transparency (0-1) | Yes |
scale |
Size multiplier | Yes |
rotation |
Rotation (radians) | Yes |
draw |
Draw progress (0-1) | Yes |
Shape-specific Properties
| Element | Properties |
|---|---|
rect |
w, h, fill, stroke, strokeWidth, cornerRadius |
circle |
r (radius), fill, stroke, strokeWidth |
ellipse |
rx, ry (radii), fill, stroke, strokeWidth |
text |
font, size, fill |
line |
from, to, stroke, strokeWidth, strokeCap |
path |
d, points, fx+xRange, xt+yt+tRange, steps, fill, stroke, closed, strokeCap, strokeJoin |
image |
w, h, source path |
Architecture
.mmark file (DSL)
|
v
PARSER (DSL -> Scene IR)
|
v
SCENE IR (JSON) ─── audioTracks[]
| |
v v
ENGINE (IR + time) Web Audio API
| |
v v
CANVAS 2D ──────────> MediaRecorder / WebCodecs
|
v
WebM / MP4 (browser export)Project Structure
motion-mark/
packages/
schema/ # IR JSON schema + TypeScript types
parser/ # DSL tokenizer, AST, compiler
engine/ # IR + time -> frame state
renderer/ # Frame rendering
exporter/ # Headless render -> MP4
validator/ # DSL validation
ai/ # NL -> DSL pipeline
stdlib/
motion/ # Standard @motion marks
examples/ # Example .mmark files
tests/ # Test suiteDraw Animation
Reveal elements progressively with the draw property (0-1):
line arrow x1:0 y1:100 x2:200 y2:100 stroke:#fff strokeWidth:2 | 0s - 3s
draw: 0 -> 1 over 2s ease-out
circle ring r:50 fill:none stroke:#4ecdc4 strokeWidth:3 | 0s - 3s
x: 300
y: 100
draw: 0 -> 1 over 1.5s ease-out
text title "Hello" font:Roboto size:32 fill:#fff | 0s - 3s
x: 200
y: 200
draw: 0 -> 1 over 2s linear
path curve fx:"100 - 30 * sin(x * 0.05)" xRange:"0-300" steps:50 stroke:#e74c5c strokeWidth:2 | 0s - 3s
draw: 0 -> 1 over 2s ease-out| Element | Draw Effect |
|---|---|
line |
Reveals from start to end |
circle |
Arc reveals 0° → 360° |
rect |
Outline draws clockwise |
text |
Typewriter (char by char) |
path |
Stroke reveals along curve |
image |
Wipe reveal left → right |
Stroke Controls
Fine-grained control over stroke rendering with drawStart, drawEnd, dashArray, and dashOffset:
circle spinner r:48 fill:none stroke:#60a5fa strokeWidth:6 | 0s - 6s
x: 200
y: 170
drawStart: 0 -> 0.75 over 6s linear
drawEnd: 0.25 -> 1 over 6s linear
dashArray: [8, 6]
dashOffset: 0 -> 120 over 6s linear
rect ants w:240 h:150 fill:none stroke:#34d399 strokeWidth:4 anchor:top-left | 0s - 6s
x: 410
y: 130
dashArray: [14, 10]
dashOffset: 0 -> 220 over 6s linear
path squiggle d:"M 120 380 C 210 300, 330 460, 420 380 S 630 300, 720 380" stroke:#f59e0b strokeWidth:6 fill:none | 0s - 6s
drawStart: 0 -> 0.7 over 6s linear
drawEnd: 0.3 -> 1 over 6s linear| Property | Description | Animatable |
|---|---|---|
drawStart |
Start of visible stroke window (0-1) | Yes |
drawEnd |
End of visible stroke window (0-1) | Yes |
dashArray |
Dash pattern lengths [dash, gap, ...] |
No |
dashOffset |
Offset into dash pattern | Yes |
strokeCap |
Line endpoint style: butt, round, square |
No |
strokeJoin |
Corner join style: miter, round, bevel |
No |
Shape Styling
Corner Radius
Round the corners of rectangles with a single value or per-corner array:
rect card w:200 h:120 fill:#1f2937 cornerRadius:12 | persist
rect pill w:200 h:60 fill:#4ecdc4 cornerRadius:30 | persist
rect mixed w:200 h:120 fill:#fff cornerRadius:[20, 0, 20, 0] | persistAnimatable — smoothly morph between sharp and rounded:
rect morph w:100 h:100 fill:#e74c5c | 0s - 2s
cornerRadius: 0 -> 50 over 2s ease-in-outStroke Cap & Join
Control how line endpoints and corners render:
line beam x1:50 y1:100 x2:300 y2:100 stroke:#f59e0b strokeWidth:10 strokeCap:round | persist
path zigzag d:"M 50 200 L 150 250 L 250 200" stroke:#f472b6 strokeWidth:8 fill:none strokeJoin:round | persist| Value | strokeCap | strokeJoin |
|---|---|---|
butt |
Flat end (default for paths) | — |
round |
Rounded end (default for lines) | Smooth arc corner |
square |
Extended flat end | — |
miter |
— | Sharp pointed corner (default) |
bevel |
— | Flat diagonal cut |
Filters
Apply visual effects to any element:
image photo "image.jpg" w:360 h:240 | 0s - 6s
x: 220
y: 245
blur: 12 -> 0 over 1.2s ease-out
saturate: 0 -> 1 over 1.6s ease-out
contrast: 0.9 -> 1.15 over 2s ease-in-out
brightness: 0.8 -> 1.1 over 2s ease-in-out
hueRotate: 0 -> 25 over 6s linear
shadow: 0 14 28 #00000066
text neon "NEON" font:Roboto size:56 fill:#22d3ee weight:bold | 0s - 6s
x: 610
y: 200
shadow: 0 0 24 #22d3ee
brightness: 1 -> 1.35 over 1.4s ease-in-out| Property | Description | Default | Animatable |
|---|---|---|---|
blur |
Gaussian blur in px | 0 |
Yes |
brightness |
Brightness multiplier | 1 |
Yes |
contrast |
Contrast multiplier | 1 |
Yes |
saturate |
Saturation multiplier | 1 |
Yes |
hueRotate |
Hue rotation in degrees | 0 |
Yes |
shadow |
Drop shadow offsetX offsetY blur color |
none | No |
Dynamics (Wiggle + Spring)
Spring Easing
Use spring() as an easing function for physically-based animations:
rect card w:220 h:120 fill:#1f2937 stroke:#22d3ee strokeWidth:2 | 0s - 6s
x: 290 -> 400 over 1.2s spring(stiffness:180, damping:12)
scale: 0.7 -> 1 over 1s spring(140, 11)Spring parameters: spring(stiffness, damping, mass, velocity) — all optional with defaults 180, 12, 1, 0.
Wiggle Expression
Deterministic noise for organic floating motion:
circle float r:14 fill:#fb7185 | 0s - 6s
x: wiggle(2, 44, base:180, seed:1)
y: wiggle(3, 26, base:360, seed:2)wiggle(frequency, amplitude, base, seed) — generates smooth pseudo-random motion around base.
Motion Path
Animate elements along an SVG path with arc-length parameterization:
path route d:"M 80 315 C 180 120, 360 120, 440 260 S 620 430, 720 210" stroke:#334155 strokeWidth:3 fill:none | 0s - 7s
dashArray: [12, 10]
dashOffset: 0 -> 160 over 7s linear
rect comet w:34 h:14 fill:#22d3ee anchor:center | 0s - 7s
motionPath: "M 80 315 C 180 120, 360 120, 440 260 S 620 430, 720 210"
motionProgress: 0 -> 1 over 5s ease-in-out
motionRotate: auto
shadow: 0 0 22 #22d3ee| Property | Description | Animatable |
|---|---|---|
motionPath |
SVG path d string to follow |
No |
motionProgress |
Position along path (0-1) | Yes |
motionRotate |
auto to orient along tangent |
No |
Repeat
Clone an element with staggered offsets:
circle spark r:7 fill:#fb7185 | 1s - 7s
repeat: 10
repeatOffset: x:26 y:-10 opacity:-0.075 delay:0.06s
x: 285
y: 390
scale: 0 -> 1 over 0.35s spring(stiffness:140, damping:10)
opacity: 1 -> 0 over 1.2s ease-out| Property | Description |
|---|---|
repeat |
Number of clones |
repeatOffset |
Per-clone offset: x, y, opacity, scale, rotation, delay |
Tips
Z-order: Elements are rendered in declaration order. Later elements appear on top.
Time is relative: In expressions,
tstarts at 0 when the element appears, not global time.Use variables: Define colors and values once in the header for easy theming.
Compose motions: Apply multiple @motion definitions to a single element.
Start simple: Begin with static elements, then add animation progressively.
License
See LICENSE file for details.