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: 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:WIDTHPath
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 |
circle |
r (radius), fill, stroke, strokeWidth |
text |
font, size, fill |
line |
from, to, stroke, strokeWidth |
path |
d, points, fx+xRange, xt+yt+tRange, steps, fill, stroke, closed |
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 |
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.