Document
{
"format": "motion-engine", // always this string
"version": "1.0",
"meta": { "name": "…", "source": "figma", "figmaNodeId": "12:34" },
"duration": 1.2, // SECONDS
"fps": 60, // suggested sampling fps (engine is time-based)
"stage": { "width": 375, "height": 812, "background": "#FFFFFFFF" },
"layers": [ /* Layer[], back-to-front */ ]
}
Layer
{
"id": "12:40",
"name": "Card",
"type": "rect", // rect | ellipse | text | image | vector | group
"children": [ /* Layer[] */ ],
"base": {
"x": 24, "y": 120, "width": 327, "height": 200, // x/y relative to parent box
"opacity": 1, "rotation": 0, "scaleX": 1, "scaleY": 1,
"anchor": { "x": 0.5, "y": 0.5 }, // transform-origin, 0..1
"cornerRadius": [12, 12, 12, 12], // [tl, tr, br, bl]
"fill": { "type": "solid", "color": "#2D6CFFFF" },
"stroke": { "color": "#00000022", "weight": 1 }, // or null
"text": null, // Text for type:"text"
"image": null, // data-URL texture
"shape": null, // Shape geometry
"effects":[ /* Effect[] */ ],
"clip": true // clip children to box
},
"tracks": [ /* Track[] */ ]
}
Paint (fill)
{ "type": "solid", "color": "#RRGGBBAA" }
{ "type": "linear", "angle": 90, "stops": [ {"pos":0,"color":"#.."}, {"pos":1,"color":"#.."} ] }
{ "type": "image", "src": "data:image/png;base64,…", "fit": "cover" }
Text
{ "characters": "Hello", "fontSize": 16, "fontFamily": "Inter", "fontWeight": 600,
"color": "#111111FF", "lineHeight": 1.3, "letterSpacing": 0, "align": "left" }
Shape (native geometry — not rasterized)
{ "kind": "polygon", "points": 3 }
{ "kind": "star", "points": 5, "ratio": 0.4 }
{ "kind": "path", "vw": 780, "vh": 0,
"paths": [ { "d": "M0 0 L760 0", "stroke": "#FF0000FF", "strokeWidth": 2,
"cap": "round", "markerEnd": "arrow" } ] }
Effect (effects[])
{ "type": "drop", "x": 0, "y": 16, "radius": 34, "spread": 0, "color": "#0000002E" }
{ "type": "inner", "x": 0, "y": 2, "radius": 8, "spread": 0, "color": "#00000040" }
{ "type": "blur", "radius": 12 }
{ "type": "bgblur","radius": 12 }
Track
{
"property": "translateX", // normalized name (table below)
"op": "set", // set | offset | scale
"unit":"px", // px | deg | ratio | none
"base": 0, // base value the op combines against
"keys": [ /* Keyframe[] */ ]
}
op | result | meaning |
|---|---|---|
set | v | absolute |
offset | base + v | delta |
scale | base * v | multiplier |
Property names
| Property | unit | applied as |
|---|---|---|
translateX / translateY | px | transform |
translateXY ([x,y]) | px | transform |
rotation | deg | transform |
scaleX / scaleY | ratio | transform |
scaleXY ([x,y]) | ratio | transform |
opacity | none | style |
width / height | px | style |
cornerRadiusTL/TR/BL/BR | px | style |
strokeWeight | px | style |
polygonCount | none | style (re-derives polygon/star) |
trimStart / trimEnd | ratio | style |
fillColor | none | style |
0 (translate/rotate) or 1 (scale). Style
properties override the box.
Keyframe
{
"t": 0.0, // ABSOLUTE time on the timeline, seconds
"v": 120, // number | [x,y] | "#RRGGBBAA" | bool | string
"easing": { "type": "cubicBezier", "p": [0.42, 0, 0.58, 1] }
}
easing governs interpolation from this keyframe to the next. The last
keyframe’s easing is ignored. Before the first / after the last keyframe, the value is held.
Easing
{ "type": "linear" }
{ "type": "hold" } // step
{ "type": "cubicBezier", "p": [x1,y1,x2,y2] } // y may overshoot 0..1
{ "type": "spring", "bounce": 0.3 } // 0 … 1
Value types
| Type | v |
|---|---|
| float | number |
| vector | [x, y] |
| color | "#RRGGBBAA" |
| bool | boolean |
| text | string |