The renderer semantics an agent must know to author a RenderRequest that renders right the first time — timing, layering, positioning, and the silent defaults that trip agents up.
You author a video as a single JSON object — a RenderRequest — submitted to a
deterministic C++ render engine. Field names are snake_case. Unknown or misspelled
fields are rejected at submit time with a JSON pointer to the offending path and a
did-you-mean suggestion; fix and resubmit.This page is the semantics — the renderer behavior that is not visible in the field
list, and the silent defaults that make an otherwise-valid request render wrong. For the
exhaustive field-by-field list (every element, every option, generated from the source of
truth), read the RenderRequest schema reference alongside
this. When you are ready to submit and await the artifact, continue with the
Render Video guide.
Times are absolute output-timeline seconds.time = when the element
appears; text/image/shape need duration. Video/audio length comes
from in_point/out_point (seconds into the source file); always set
out_point explicitly.
Layering: the painter’s rule. An explicit z_index always wins between
distinct values; among elements that don’t set one (defaults: text 1,
everything else 0), ties resolve by array order — later in elements renders
on top. So an overlay listed after a video stacks above it with no z_index
needed; set z_index only to override array order.
Positions/sizes:x/y are the element’s CENTER as canvas percentages
("50%" = centered). width/height accept "38%" (of canvas) or "140px".
Font sizes are pixels at the canvas width — the renderer normalizes
font_size by width only, so the same value is the same visual size whatever
the output height. Rescaling a measurement from a reference frame uses the
width ratio, never the height ratio.
Entrances/exits:"reversed": true runs a preset’s exit form, and only
presets that ship both accept it (fade, slide_*, wipe_*, rotate_*,
bounce). Entrance-only presets have a separate exit-only twin instead —
zoom_out (not zoom_in reversed), evaporate (not blur), whip_down,
drift_out, swing_out, elastic_drop — and those six set reversed for
you, so never type it. Asking for a missing exit form is a 422 that names the
replacement. Motion time is absolute; for entrances and loops set it to the
element’s time, for exits to element time + duration − motion duration.
delay only shifts that start (time + delay) on a non-loop motion, and
spaces the repeats on a loop_* one — it never staggers glyphs. Per-glyph or
per-word stagger is spacing on the motion entry: 1 (the engine default)
plays the glyph windows fully sequentially, smaller values overlap them, and
block animations ignore it.
Motion presets are surface-split. Text elements and everything else load
animations from two different renderer directories that overlap on only a
handful of names, so a preset is not portable: blur and evaporate are
text-only; swing_in, tilt_zoom, ken_burns_* and identity are
element-only; fade, slide_* and zoom_* work on both. A mismatch is a 422
naming the other surface. scope: "character" applies only to per-glyph text
presets — whole-block presets (fade, slide_up, zoom_in) reject it.
Read GET /v1/capabilities → motions[], where each preset carries
element_entrance / element_exit / character / text_block, before
picking one.
Gradient angle_degrees is mathematical (counterclockwise, y-up):
0° runs left→right (offset 0 at the left), 45° bottom-left→top-right,
315° top-left→bottom-right. The same convention governs a shape’s gradient
fill and the request-level background_gradient. A CSS string is converted
for you (angle_degrees = (90 − css_deg) mod 360), and a numeric CSS angle is
box-relative: 135deg behaves as to bottom right whatever the card’s aspect,
so only 0/90/180/270 are pixel-exact on a non-square box. A radial center.y
runs bottom-up — {x: 0.5, y: 0} is the bottom edge, the opposite of CSS
at 50% 100%.
Per-word captions:word_animation.words[*].start/end are ABSOLUTE
timeline seconds (not element-relative). Styles color and box paint with
the element’s background_color, which is required for them — omit it and
the request 422s (an absent value resolves to opaque black on the renderer, an
invisible highlight behind a 200). glow, scale_pop, slide_up and
fly_in do not use it. A text element takes exactly one animation route:
motion[], or word_animation, or animation_preset. Combining
word_animation (or animation_preset) with motion[] is a 422 — the engine
reads the word object only when no animations array is emitted, and its word
timings would re-time the motion entries.
Text is a different renderer. These are 422s on text: border_radius
≠ 0 (put the text over a rounded shape card), static
x_rotation/y_rotation (tilt only via a custom_animations keyframe
rotation: {x, y, z}), x_anchor/y_anchor other than "50%" (text rotates
about its box centre), and text_decoration: "underline" /
"strikethrough" (use a per-word style.underline in words[], or draw a
thin shape rule). background, stroke and shadow select one renderer
display mode, so setting two of them is a 422 — pick one. An element carrying
a counter also rejects per-glyph animation (a per-glyph animation_preset,
or a scope: "character" motion): glyph timings are computed once against the
starting value, so later glyphs of longer numbers never render — animate a
counter with a block preset (fade, scaleIn).
Sources: a video source_url must end .mp4, .mov or .webm — any
other extension is a 422 at submit, so re-encode first. Audio: use
AAC/M4A/MP3. Give the audio element an explicit out_point; keep it ≤ the
composition duration.
A solid or gradient card/bar is a shape with a rectangular path
("M0 0 H100 V100 H0 Z", rounded with corner_radius) and a fill — a hex
colour, a Gradient object ({kind, angle_degrees, stops}, ≥2 stops), or a CSS
linear-gradient(...) / radial-gradient(...) string, which the API normalizes
into the Gradient object at validation. This is the only spelling: the
gradient element was removed and {"type": "gradient"} is a 422 whose message
names shape and carries the JSON to send instead. A flat card is the same
shape with a hex fill. Full-frame overlays: x/y"50%", width/height"100%".
A gradient fill is just a paint: any path, plus everything else on the
element. It sits where a flat hex colour would sit, on the shape’s own
path — so a triangle, a blob or an icon outline takes a gradient as readily
as the card’s rectangle, and the same element can also carry
stroke/stroke_width/stroke_dash (a gradient shape outlined in a flat
colour), path_keyframes (morph a gradient-filled path), trim/trim_keyframes
(draw it on), and the compositing family — blend_mode,
mask_shape/mask/mask_keyframes, matte, backdrop_blur. fill_rule,
corner_radius and opacity behave exactly as with a flat fill. Two stacked
shapes — a gradient one behind a flat one — remain a good way to build a
layered look when you want two layers.
Stroke ends, corners and dashes follow SVG.stroke_linecap is butt |
round (default) | square — butt for timeline ticks and bar ends that must
stop exactly at the endpoint, since round/square extend the line by half the
stroke width. stroke_linejoin is miter | round (default) | bevel, with
stroke_miter_limit (default 4) turning a too-sharp miter into a bevel.
stroke_dash is an SVG dasharray of any length in viewBox units ([4] is 4 on,
4 off; [8, 4, 2, 4] alternates from there) and stroke_dash_offset slides
the pattern along the path. Marching ants are stroke_dash_offset_keyframes
({time, offset, easing?}, timeline seconds): keyframe the offset linearly by
one dash period — the pattern’s sum — per second, so [8, 4] marches with
[{"time": 0, "offset": 0}, {"time": 4, "offset": 48}]; negate the travel to
reverse the direction. The path subset is M L H V C S Q T A Z; an A arc
renders as a true arc, so a ring or a donut is four arcs, not a chord polygon.
Transparent output:alpha: true requires output_format"webm" or
"mov" — with mp4 it is a 422. With alpha on, background_color is
ignored and the wire background is forced fully transparent, so an alpha
export cannot also carry a coloured backdrop; if you need one, draw it as a
full-frame shape. Everything else composites normally.
Transitions are video-to-video only. Both from_id and to_id must name
visiblevideo elements — a transition onto an image, text or shape is
rejected (it crashes the render node). Each clip may be the outgoing
side of at most one transition and the incoming side of at most one.
{"type": "cross_dissolve", "duration": 0.8, "from_id": "clip1", "to_id": "clip2"} with clip2’s time at the boundary. To cross-fade stills,
animate opacity with fade motions instead.
Speed ramps:playback_rate_keyframes on a video are piecewise-linear
time/rate points in absolute timeline seconds (rate 0 = freeze), at most
512, and the times must be strictly increasing — for an instant step put
the two rates t and t + 0.001 apart. The audio inside the clip keeps the
static rate, so mute the video element (volume: 0, legal there) or move
the sound to its own audio element. A ramp ending at rate 0 needs an
explicit request-level duration to say when the freeze stops, otherwise it
is rejected. An audio element cannot be muted at all — its volume must be
0; delete it instead (visible: false drops the track, it is not a mute).
Masks:mask_shape ("circle", …) clips the element;
mask (with scale, center) sizes it (scale ≈ 0.15 = small iris, ≈ 2.0
covers the frame); mask_keyframes animates it over time. Masks,
blend_mode, matte and backdrop_blur exist on video, image and
shape only — on text they are unknown fields and the request 422s with
Extra inputs are not permitted. For text blends use the
motion presets overlay / difference; for a masked or blended card, put the
text over a shape.
Shadows and radii use four different scales. On video/image,
shadow_x/shadow_y and border_radius are real pixels — but a shadow whose
offset is 0,0 is rejected outright (the renderer skips the pass entirely, so
set at least 1px). On shape, shadow.distance/blur are the
engine’s 0–100 strength scale (reference scenes author 60–70; a px-looking
8 renders under a pixel on a small card) and corner_radius is already a
0–1 fraction of half the shorter side, where 1.0 is a pill. Percentage
strings are rejected on every pixel-unit field (border_radius,
border_width, shadow_x, shadow_y, backdrop_blur) — pass a number or
"Npx". On text, shadow_x/shadow_y/shadow_blur are a scale of their
own again — pixels normalized against the canvas, neither the 0–100 strength
of shape nor the border-grown pixels of video/image — so
author them as a few px of offset and a blur in the tens, and never carry a
card’s distance: 60 onto a text element.
3D tilt on video, image and shape:
x_rotation/y_rotation accept degree strings ("18°" or "18deg").
Animate the tilt — and tilt text at all — with a custom_animations
definition whose keyframes carry rotation: {x, y, z}, bound via
motion: [{"custom": "<name>", ...}].
A custom_animations keyframe time is a 0–1 fraction of the animation,
not seconds (0 = start, 1 = end); the wall-clock window comes from the
motion[] entry’s time/duration that binds it. Every other keyframe list
— mask_keyframes, playback_rate_keyframes, path_keyframes,
trim_keyframes, stroke_dash_offset_keyframes, intensity_keyframes,
word_animation.words[] — is in absolute timeline seconds.
(counter.start_time is a third unit: seconds
into the element.)
Accepted but inert — do not build on these.motion[].easing is ignored — curves are baked into the preset, or into your
custom_animations keyframes’ own easing. x_scale/y_scale,
aspect_ratio, clip, name and track cross nowhere; size with
width/height. lut_url/lut_intensity are accepted on image but
applied only on video — grade a still with temperature/tint/vibrance/
highlights/shadows, or bake it in. flip_horizontal/flip_vertical cross
only on video and image (mirror a shape by rewriting its path). On
text, background_opacity, x_padding and y_padding cross nowhere: use
an 8-digit background_color ("#000000aa") for a translucent plate, and pad
by widening width or setting the text over a shape card.
(color_overlay is the opposite case — it is rejected outright; tint with a
blend-moded overlay element.)
image and shape elements require a non-empty id and a
duration.text requires duration; an id is optional there, but give
every element a unique one anyway — duplicates are a blocking
DUPLICATE_ELEMENT_ID, and transitions, groups, mattes and ops all address
elements by id.
A dark hero card with a headline that fades in — note the explicit duration on the text,
the center-based % position, and the entrance motion whose time matches the element’s:
Do not write a gradient element — it was removed. {"type": "gradient", …} is a 422
whose message names shape and includes the exact JSON to send. Stored projects needed no
editing: every saved composition was rewritten to the shape form, which renders identically.
To port a body by hand: type"gradient" → "shape", rename gradient → fill, add
"path": "M0 0 H100 V100 H0 Z". Everything else (corner_radius, shadow, effects,
motion, the transform fields) keeps its name and units.
The panel a brief calls a “gradient background” or a “hero card” — a shape with a
rectangular path, a corner_radius and a gradient fill. List it before the headline and
the painter’s rule puts the text on top with no z_index:
The CSS shorthand is accepted wherever the object is — a shape’s fill and the request-level
background_gradient — and is normalized at validation, so the stored composition and every
read-back hold the object, never the string:
What the CSS parser cannot represent it refuses with the fix in the message: repeating-*
and conic-gradient(...), var()/calc(), vendor prefixes, a unitless angle (45 →
45deg) and pixel stop positions (40px → 25%).
shape — an SVG d path with fill/stroke/dash, where the fill is a hex
colour, "none", a Gradient object or a CSS linear-gradient(...) string, so a
gradient card is a rectangular path with a gradient fill; trim +
trim_keyframes draw it on (trim applies before dash, so a dashed line draws on
without the pattern re-flowing), stroke_dash_offset_keyframes march the dashes,
path_keyframes morph it.
groups[] — transform several elements as one (translate/scale/rotate/3D
tilt, a row/column stack layout, and their own animations[]); group
opacity is 0–1 while element opacity is 0–100.
custom_animations — your own keyframed presets, bound with
motion: [{"custom": "<name>"}]; the only way to tilt or 3D-rotate text.
Name them distinctively: none, overlay, difference, any name a catalog
animation already answers to (fade, pop, scale, bounce,
typewriter, …) and duplicates within one registry are all rejected — the
engine would silently run the catalog animation instead of yours. Binding:
motion[].custom resolves only against this request’s registry (an unknown
name is a 422, not a silent drop), takes no reversed: true (author the exit
as its own entry) and no scope: "character" (put group: "glyph" or
"word" on the definition instead).
matte — drive one layer’s coverage from another layer’s render; the
source layer is auto-hidden.
motion[].audio — beat/bass-reactive offsets on video/image/shape and
groups (never text); pair with type: "identity" for a pure generator.
effects[] — a shader chain of 47 named effects, on video, image and
shape alike. intensity is 0–100, but
intensity_keyframes are {time, value} with time in absolute timeline
seconds and value on a 0–1 scale, not 0–100 — a value above 1 is a
422. Two rules: props is whitelisted per shader and only aurora binds
one ({"speed": n}) — any other key on aurora, and any prop at all on
any other shader, is a 422, because the renderer would bind nothing and
silently no-op. And chroma_key takes its parameters through
chroma_settings (key_color, threshold, smoothness, spill_suppress),
never props — it is the one shader that builds its own props block, so a
props you set on it is silently discarded rather than rejected. Leave its
intensity unset for a full key, since an explicit 50 leaves a translucent
green wash.
motion_blur, background_gradient, blend_mode,
backdrop_blur — request- and element-level.
Field shapes live in GET /v1/capabilities → element_schemas (per element
type) and request_schema (composition level — groups, custom animations,
motion blur, transitions); this page does not repeat them.
A text element can name a renderer catalog text animation directly in
animation_preset — 70 of them, and none of them appear in
GET /v1/capabilities, whose motions[] covers only the 42 motion[]
presets. It is the largest authorable surface on text and there is no other way
to find it. Names are the engine’s camelCase ids, not snake_caseMotionType values.Entrances — ascent, billboard, block, blur, bounce, boxBounce,
boxHighlight, boxHighlightV2, burst, colourHighlight,
colourHighlightV2, compress, dragonfly, driftIn, dropIn, evaporate,
fade, fall, flipClock, flipboard, floatInBottom, floatInTop,
glitchPop, highlight, impact, impactPop, karaoke, reveal, roll,
rotateFlipClock, rotateZigZag, rubberIn, scale, scaleIn, skid,
slideDown, slideLeft, slideRight, slideUp, stomp, typewriter,
verticalStretch, vogue, wave, wavey, whipUp, zoomIn.Exits — out-billboard, out-block, out-burst, out-decompress,
out-dragonfly, out-driftOut, out-fade, out-fall, out-flipboard,
out-roll, out-scale, out-sink, out-skid, out-slideDown,
out-slideLeft, out-slideRight, out-slideUp, out-stomp,
out-verticalStretch, out-vogue, out-wavey, out-whipDown, out-zoomOut.Two further names resolve here without being catalog animations: overlay and
difference, the renderer’s static text blend keys (the same two you can reach
as motion[] presets).Three rules. A name that is none of those — not a catalog animation, not a
blend key, not a custom_animations.text entry in the same request — is a 422
(the engine would drop it with no warning). A loop-* name is a 422 too: a
loop needs an explicit cycle length this route cannot express, so use motion[]
with the matching loop_* type and duration = one cycle. And
animation_preset cannot be combined with motion[] — that pair is a 422.
Pairing it with word_animation is not rejected, which is worse:
word_animation takes precedence and your preset is silently dropped, so send
exactly one of the three.
Every composition can be linted for free before you spend a render. POST /v1/preview
with dry_run: true runs the cross-element linter and returns
{ "kind": "validation", "ok": <bool>, "violations": [...] } — no render is dispatched.Each violation carries a stable code, a human message, a path (e.g.
elements[2].time) with its RFC 6901 pointer (/elements/2/time), the target_id of
the element it is about, and, for layout findings, a bbox and time you can act on
directly. ok is true only when there are no error-severity violations — meaning the
request will submit. Every unsupported-feature rejection the submit path would
raise is folded into this array at error severity too, so the dry run surfaces
the whole 422 class before you spend a render. Repair every error, re-lint, then
render. Common findings:
OUT_OF_FRAME — an element’s box falls partly or fully off-canvas.
ELEMENT_OUT_OF_RANGE — an element starts at/after the output duration and the
renderer drops it.
DUPLICATE_ELEMENT_ID — two elements share an id; transition targets and ops
that address it become ambiguous. Rename one. (error)
DANGLING_TRANSITION — a transition from_id/to_id matches no element. (error)
TRANSITION_SELF — from_id equals to_id. (error)
WORD_OUT_OF_RANGE — a word_animation.words[] timing falls outside the element’s
[time, time + duration] window; word timings are absolute, not element-relative.
ENTRANCE_AFTER_ELEMENT_START — an element’s entrance motion starts after the element
itself, so it sits fully rendered on screen and then plays its entrance. Set
motion[].time (and delay) to the element’s own time.
AUDIO_HIDDEN — visible: false on an audio element removes the track from the
render; it is not a mute.
SHADOW_BELOW_MIN_SIZE — shadow.distance is a 0–100 strength, not pixels, and this
value renders an offset under one pixel.
HAIRLINE_BELOW_PIXEL — a shape’s stroke_width is in viewBox units and lands on the
canvas scaled by the box; this one renders under one device pixel and drops out. The
message carries the width that clears a pixel.
DASH_POINT_BUDGET — stroke_dash over this much path needs more dash points than the
renderer’s 100,000 cap, so the rest of the path draws solid. Lengthen the period (the
pattern’s sum, in viewBox units — the box does not scale it).
SHAPE_ASPECT_MISMATCH — the box aspect is more than 10% off the view_box aspect, so
a non-rectangular path is stretched along one axis (circles become ellipses, strokes
differ per axis). Match the box to the viewBox aspect or the viewBox to the box.
LOW_CONTRAST and TEXT_OVERLAP abstain rather than guess: the linter stays silent when
the backdrop is a photo, a video, a translucent fill or only a partial cover, and it
compares glyph ink rather than line boxes, so an ordinary title/subtitle stack whose
leading grazes is not reported.The same linter rides along on POST /v1/projects/{id}/ops, so iterative edits are validated
as you make them.It also rides along on the paths that spend money — the 202 from POST /v1/renders
and POST /v1/projects/{id}/renders, and the 201 from POST /v1/projects — where it
is advisory: ok and violations[] are attached, and the render is dispatched (or
the project stored) regardless. Treat that as a receipt, not a gate. The gate is the free
dry_run above, which is also the more informative of the two, because it runs the full
translator and so reports the whole 422 class as well. A LINT_UNAVAILABLE warning with
ok: true on a submit means the linter itself failed server-side; it says nothing about
your composition.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Framelane","colors":{"primary":"#0070f3","light":"#3b82f6","dark":"#1d4ed8"},"logo":{"light":"https://mintcdn.com/itis-c7dc7146/I-4ZfymyiJyBmcDs/logo/framelane-light.png?fit=max\u0026auto=format\u0026n=I-4ZfymyiJyBmcDs\u0026q=85\u0026s=a819adfb6e5b9a97575f20c034d41c77","dark":"https://mintcdn.com/itis-c7dc7146/I-4ZfymyiJyBmcDs/logo/framelane-dark.png?fit=max\u0026auto=format\u0026n=I-4ZfymyiJyBmcDs\u0026q=85\u0026s=14e93bd67dd1fedf3c655cbebd17e046"},"favicon":"https://mintcdn.com/itis-c7dc7146/I-4ZfymyiJyBmcDs/favicon.ico?fit=max\u0026auto=format\u0026n=I-4ZfymyiJyBmcDs\u0026q=85\u0026s=a0cbeea069121bb5f6014ae0b57c8b8b","api":{"playground":{"display":"interactive"}},"navbar":{"links":[{"href":"https://console.framelane.io","label":"Console"}],"primary":{"type":"button","label":"Get API Key","href":"https://console.framelane.io/signup"}},"footer":{"socials":{"x":"https://twitter.com/framelane","github":"https://github.com/framelane"}},"seo":{"metatags":{"og:type":"website","og:site_name":"Framelane","og:image":"https://cdn-assets.framelane.io/shared/images/og-docs.png","robots":"index, follow"},"indexing":"all"},"contextual":{"options":["copy","view","claude","chatgpt","download-spec"],"display":"header"},"styling":{"latex":false},"navigation":{"global":{"anchors":[{"anchor":"GitHub","icon":"github","href":"https://github.com/framelane"},{"anchor":"Status","icon":"signal-bars","href":"https://status.framelane.io"}]},"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"itis-c7dc7146","hasAudioNarration":false,"actualSubdomain":"itis-c7dc7146","customDomains":["docs.framelane.io"],"gitSource":{"type":"github","owner":"itishq","repo":"framelane-api","deployBranch":"main","contentDirectory":"docs","isPrivate":true,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6adf86d0737ceb5e7109b3ae","collectionId":"43553988-d245-46c7-9e69-6860d4cdb8de","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6adf86d0737ceb5e7109b3ae"},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6adf86d0737ceb5e7109b3ae","clientVersion":"0.0.3971","searchFilterCounts":[{"language":"default","tag":"API Reference","count":65},{"language":"default","tag":"Documentation","count":41},{"language":"default","tag":"Webhooks","count":13},{"language":"default","tag":"Projects","count":10},{"language":"default","tag":"Renders","count":10},{"language":"default","tag":"Get Started","count":9},{"language":"default","tag":"Render Examples","count":9},{"language":"default","tag":"Account","count":9},{"language":"default","tag":"Guides","count":7},{"language":"default","tag":"Tasks","count":7}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-v7-file-tree","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"pause-universal-search-reconcile","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-nav-generations","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-agent-model-picker","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"isAtlas":false,"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"guides/author-video","slug":"/guides/author-video","pageMetadata":{"title":"Author a video","description":"The renderer semantics an agent must know to author a RenderRequest that renders right the first time — timing, layering, positioning, and the silent defaults that trip agents up.","href":"/guides/author-video"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"The renderer semantics an agent must know to author a RenderRequest that renders right the first time — timing, layering, positioning, and the silent defaults that trip agents up.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,blockquote:`blockquote`,code:`code`,em:`em`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(Heading,{level:`2`,id:`the-model`,children:`The model`}),`\n`,_jsxs(_components.p,{children:[`You author a video as a single JSON object — a `,_jsx(_components.code,{children:`RenderRequest`}),` — submitted to a\ndeterministic C++ render engine. Field names are `,_jsx(_components.code,{children:`snake_case`}),`. Unknown or misspelled\nfields are rejected at submit time with a JSON pointer to the offending path and a\ndid-you-mean suggestion; fix and resubmit.`]}),`\n`,_jsxs(_components.p,{children:[`This page is the `,_jsx(_components.strong,{children:`semantics`}),` — the renderer behavior that is `,_jsx(_components.em,{children:`not`}),` visible in the field\nlist, and the silent defaults that make an otherwise-valid request render wrong. For the\nexhaustive field-by-field list (every element, every option, generated from the source of\ntruth), read the `,_jsx(_components.a,{href:`/api-reference/render-request`,children:`RenderRequest schema reference`}),` alongside\nthis. When you are ready to submit and await the artifact, continue with the\n`,_jsx(_components.a,{href:`/guides/render-video`,children:`Render Video`}),` guide.`]}),`\n`,_jsx(Heading,{level:`2`,id:`semantics-you-must-know-renderer-behavior`,children:`Semantics you must know (renderer behavior)`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Times are absolute output-timeline seconds.`}),` `,_jsx(_components.code,{children:`time`}),` = when the element\nappears; text/image/shape need `,_jsx(_components.code,{children:`duration`}),`. Video/audio length comes\nfrom `,_jsx(_components.code,{children:`in_point`}),`/`,_jsx(_components.code,{children:`out_point`}),` (seconds into the source file); always set\n`,_jsx(_components.code,{children:`out_point`}),` explicitly.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Layering:`}),` the painter’s rule. An explicit `,_jsx(_components.code,{children:`z_index`}),` always wins between\ndistinct values; among elements that don’t set one (defaults: text 1,\neverything else 0), ties resolve by array order — later in `,_jsx(_components.code,{children:`elements`}),` renders\non top. So an overlay listed after a video stacks above it with no `,_jsx(_components.code,{children:`z_index`}),`\nneeded; set `,_jsx(_components.code,{children:`z_index`}),` only to override array order.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Positions/sizes:`}),` `,_jsx(_components.code,{children:`x`}),`/`,_jsx(_components.code,{children:`y`}),` are the element’s CENTER as canvas percentages\n(`,_jsx(_components.code,{children:`\"50%\"`}),` = centered). `,_jsx(_components.code,{children:`width`}),`/`,_jsx(_components.code,{children:`height`}),` accept `,_jsx(_components.code,{children:`\"38%\"`}),` (of canvas) or `,_jsx(_components.code,{children:`\"140px\"`}),`.\nFont sizes are pixels `,_jsx(_components.strong,{children:`at the canvas width`}),` — the renderer normalizes\n`,_jsx(_components.code,{children:`font_size`}),` by width only, so the same value is the same visual size whatever\nthe output height. Rescaling a measurement from a reference frame uses the\nwidth ratio, never the height ratio.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Entrances/exits:`}),` `,_jsx(_components.code,{children:`\"reversed\": true`}),` runs a preset’s `,_jsx(_components.em,{children:`exit`}),` form, and only\npresets that ship both accept it (`,_jsx(_components.code,{children:`fade`}),`, `,_jsx(_components.code,{children:`slide_*`}),`, `,_jsx(_components.code,{children:`wipe_*`}),`, `,_jsx(_components.code,{children:`rotate_*`}),`,\n`,_jsx(_components.code,{children:`bounce`}),`). Entrance-only presets have a separate exit-only twin instead —\n`,_jsx(_components.code,{children:`zoom_out`}),` (not `,_jsx(_components.code,{children:`zoom_in`}),` reversed), `,_jsx(_components.code,{children:`evaporate`}),` (not `,_jsx(_components.code,{children:`blur`}),`), `,_jsx(_components.code,{children:`whip_down`}),`,\n`,_jsx(_components.code,{children:`drift_out`}),`, `,_jsx(_components.code,{children:`swing_out`}),`, `,_jsx(_components.code,{children:`elastic_drop`}),` — and those six set `,_jsx(_components.code,{children:`reversed`}),` for\nyou, so never type it. Asking for a missing exit form is a 422 that names the\nreplacement. Motion `,_jsx(_components.code,{children:`time`}),` is absolute; for entrances and loops set it to the\nelement’s `,_jsx(_components.code,{children:`time`}),`, for exits to `,_jsx(_components.code,{children:`element time + duration − motion duration`}),`.\n`,_jsx(_components.code,{children:`delay`}),` only shifts that start (`,_jsx(_components.code,{children:`time + delay`}),`) on a non-loop motion, and\nspaces the repeats on a `,_jsx(_components.code,{children:`loop_*`}),` one — it never staggers glyphs. Per-glyph or\nper-word stagger is `,_jsx(_components.code,{children:`spacing`}),` on the motion entry: `,_jsx(_components.code,{children:`1`}),` (the engine default)\nplays the glyph windows fully sequentially, smaller values overlap them, and\nblock animations ignore it.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Motion presets are surface-split.`}),` Text elements and everything else load\nanimations from two different renderer directories that overlap on only a\nhandful of names, so a preset is not portable: `,_jsx(_components.code,{children:`blur`}),` and `,_jsx(_components.code,{children:`evaporate`}),` are\ntext-only; `,_jsx(_components.code,{children:`swing_in`}),`, `,_jsx(_components.code,{children:`tilt_zoom`}),`, `,_jsx(_components.code,{children:`ken_burns_*`}),` and `,_jsx(_components.code,{children:`identity`}),` are\nelement-only; `,_jsx(_components.code,{children:`fade`}),`, `,_jsx(_components.code,{children:`slide_*`}),` and `,_jsx(_components.code,{children:`zoom_*`}),` work on both. A mismatch is a 422\nnaming the other surface. `,_jsx(_components.code,{children:`scope: \"character\"`}),` applies only to per-glyph text\npresets — whole-block presets (`,_jsx(_components.code,{children:`fade`}),`, `,_jsx(_components.code,{children:`slide_up`}),`, `,_jsx(_components.code,{children:`zoom_in`}),`) reject it.\nRead `,_jsx(_components.code,{children:`GET /v1/capabilities`}),` → `,_jsx(_components.code,{children:`motions[]`}),`, where each preset carries\n`,_jsx(_components.code,{children:`element_entrance`}),` / `,_jsx(_components.code,{children:`element_exit`}),` / `,_jsx(_components.code,{children:`character`}),` / `,_jsx(_components.code,{children:`text_block`}),`, before\npicking one.`]}),`\n`,_jsxs(_components.li,{children:[_jsxs(_components.strong,{children:[`Gradient `,_jsx(_components.code,{children:`angle_degrees`})]}),` is mathematical (counterclockwise, y-up):\n0° runs left→right (offset 0 at the left), 45° bottom-left→top-right,\n315° top-left→bottom-right. The same convention governs a `,_jsx(_components.code,{children:`shape`}),`’s gradient\n`,_jsx(_components.code,{children:`fill`}),` and the request-level `,_jsx(_components.code,{children:`background_gradient`}),`. A CSS string is converted\nfor you (`,_jsx(_components.code,{children:`angle_degrees = (90 − css_deg) mod 360`}),`), and a numeric CSS angle is\nbox-relative: `,_jsx(_components.code,{children:`135deg`}),` behaves as `,_jsx(_components.code,{children:`to bottom right`}),` whatever the card’s aspect,\nso only 0/90/180/270 are pixel-exact on a non-square box. A radial `,_jsx(_components.code,{children:`center.y`}),`\nruns bottom-up — `,_jsx(_components.code,{children:`{x: 0.5, y: 0}`}),` is the bottom edge, the opposite of CSS\n`,_jsx(_components.code,{children:`at 50% 100%`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Per-word captions:`}),` `,_jsx(_components.code,{children:`word_animation.words[*].start`}),`/`,_jsx(_components.code,{children:`end`}),` are ABSOLUTE\ntimeline seconds (not element-relative). Styles `,_jsx(_components.code,{children:`color`}),` and `,_jsx(_components.code,{children:`box`}),` paint with\nthe element’s `,_jsx(_components.code,{children:`background_color`}),`, which is `,_jsx(_components.strong,{children:`required`}),` for them — omit it and\nthe request 422s (an absent value resolves to opaque black on the renderer, an\ninvisible highlight behind a 200). `,_jsx(_components.code,{children:`glow`}),`, `,_jsx(_components.code,{children:`scale_pop`}),`, `,_jsx(_components.code,{children:`slide_up`}),` and\n`,_jsx(_components.code,{children:`fly_in`}),` do not use it. A text element takes exactly `,_jsx(_components.strong,{children:`one`}),` animation route:\n`,_jsx(_components.code,{children:`motion[]`}),`, or `,_jsx(_components.code,{children:`word_animation`}),`, or `,_jsx(_components.code,{children:`animation_preset`}),`. Combining\n`,_jsx(_components.code,{children:`word_animation`}),` (or `,_jsx(_components.code,{children:`animation_preset`}),`) with `,_jsx(_components.code,{children:`motion[]`}),` is a 422 — the engine\nreads the word object only when no animations array is emitted, and its word\ntimings would re-time the motion entries.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Text is a different renderer.`}),` These are 422s on `,_jsx(_components.code,{children:`text`}),`: `,_jsx(_components.code,{children:`border_radius`}),`\n≠ 0 (put the text over a rounded `,_jsx(_components.code,{children:`shape`}),` card), static\n`,_jsx(_components.code,{children:`x_rotation`}),`/`,_jsx(_components.code,{children:`y_rotation`}),` (tilt only via a `,_jsx(_components.code,{children:`custom_animations`}),` keyframe\n`,_jsx(_components.code,{children:`rotation: {x, y, z}`}),`), `,_jsx(_components.code,{children:`x_anchor`}),`/`,_jsx(_components.code,{children:`y_anchor`}),` other than `,_jsx(_components.code,{children:`\"50%\"`}),` (text rotates\nabout its box centre), and `,_jsx(_components.code,{children:`text_decoration: \"underline\"`}),` /\n`,_jsx(_components.code,{children:`\"strikethrough\"`}),` (use a per-word `,_jsx(_components.code,{children:`style.underline`}),` in `,_jsx(_components.code,{children:`words[]`}),`, or draw a\nthin `,_jsx(_components.code,{children:`shape`}),` rule). `,_jsx(_components.code,{children:`background`}),`, `,_jsx(_components.code,{children:`stroke`}),` and `,_jsx(_components.code,{children:`shadow`}),` select one renderer\ndisplay mode, so setting two of them is a 422 — pick one. An element carrying\na `,_jsx(_components.code,{children:`counter`}),` also rejects per-glyph animation (a per-glyph `,_jsx(_components.code,{children:`animation_preset`}),`,\nor a `,_jsx(_components.code,{children:`scope: \"character\"`}),` motion): glyph timings are computed once against the\nstarting value, so later glyphs of longer numbers never render — animate a\ncounter with a block preset (`,_jsx(_components.code,{children:`fade`}),`, `,_jsx(_components.code,{children:`scaleIn`}),`).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Sources:`}),` a video `,_jsx(_components.code,{children:`source_url`}),` must end `,_jsx(_components.code,{children:`.mp4`}),`, `,_jsx(_components.code,{children:`.mov`}),` or `,_jsx(_components.code,{children:`.webm`}),` — any\nother extension is a 422 at submit, so re-encode first. Audio: use\nAAC/M4A/MP3. Give the audio element an explicit `,_jsx(_components.code,{children:`out_point`}),`; keep it ≤ the\ncomposition duration.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`A solid or gradient card/bar`}),` is a `,_jsx(_components.code,{children:`shape`}),` with a rectangular `,_jsx(_components.code,{children:`path`}),`\n(`,_jsx(_components.code,{children:`\"M0 0 H100 V100 H0 Z\"`}),`, rounded with `,_jsx(_components.code,{children:`corner_radius`}),`) and a `,_jsx(_components.code,{children:`fill`}),` — a hex\ncolour, a Gradient object (`,_jsx(_components.code,{children:`{kind, angle_degrees, stops}`}),`, ≥2 stops), or a CSS\n`,_jsx(_components.code,{children:`linear-gradient(...)`}),` / `,_jsx(_components.code,{children:`radial-gradient(...)`}),` string, which the API normalizes\ninto the Gradient object at validation. This is the `,_jsx(_components.strong,{children:`only`}),` spelling: the\n`,_jsx(_components.code,{children:`gradient`}),` element was removed and `,_jsx(_components.code,{children:`{\"type\": \"gradient\"}`}),` is a 422 whose message\nnames `,_jsx(_components.code,{children:`shape`}),` and carries the JSON to send instead. A flat card is the same\nshape with a hex `,_jsx(_components.code,{children:`fill`}),`. Full-frame overlays: `,_jsx(_components.code,{children:`x`}),`/`,_jsx(_components.code,{children:`y`}),` `,_jsx(_components.code,{children:`\"50%\"`}),`, `,_jsx(_components.code,{children:`width`}),`/`,_jsx(_components.code,{children:`height`}),`\n`,_jsx(_components.code,{children:`\"100%\"`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsxs(_components.strong,{children:[`A gradient `,_jsx(_components.code,{children:`fill`}),` is just a paint: any path, plus everything else on the\nelement.`]}),` It sits where a flat hex colour would sit, on the shape’s own\n`,_jsx(_components.code,{children:`path`}),` — so a triangle, a blob or an icon outline takes a gradient as readily\nas the card’s rectangle, and the same element can also carry\n`,_jsx(_components.code,{children:`stroke`}),`/`,_jsx(_components.code,{children:`stroke_width`}),`/`,_jsx(_components.code,{children:`stroke_dash`}),` (a gradient shape outlined in a flat\ncolour), `,_jsx(_components.code,{children:`path_keyframes`}),` (morph a gradient-filled path), `,_jsx(_components.code,{children:`trim`}),`/`,_jsx(_components.code,{children:`trim_keyframes`}),`\n(draw it on), and the compositing family — `,_jsx(_components.code,{children:`blend_mode`}),`,\n`,_jsx(_components.code,{children:`mask_shape`}),`/`,_jsx(_components.code,{children:`mask`}),`/`,_jsx(_components.code,{children:`mask_keyframes`}),`, `,_jsx(_components.code,{children:`matte`}),`, `,_jsx(_components.code,{children:`backdrop_blur`}),`. `,_jsx(_components.code,{children:`fill_rule`}),`,\n`,_jsx(_components.code,{children:`corner_radius`}),` and `,_jsx(_components.code,{children:`opacity`}),` behave exactly as with a flat fill. Two stacked\nshapes — a gradient one behind a flat one — remain a good way to build a\nlayered look when you want two layers.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Stroke ends, corners and dashes follow SVG.`}),` `,_jsx(_components.code,{children:`stroke_linecap`}),` is `,_jsx(_components.code,{children:`butt`}),` |\n`,_jsx(_components.code,{children:`round`}),` (default) | `,_jsx(_components.code,{children:`square`}),` — `,_jsx(_components.code,{children:`butt`}),` for timeline ticks and bar ends that must\nstop exactly at the endpoint, since `,_jsx(_components.code,{children:`round`}),`/`,_jsx(_components.code,{children:`square`}),` extend the line by half the\nstroke width. `,_jsx(_components.code,{children:`stroke_linejoin`}),` is `,_jsx(_components.code,{children:`miter`}),` | `,_jsx(_components.code,{children:`round`}),` (default) | `,_jsx(_components.code,{children:`bevel`}),`, with\n`,_jsx(_components.code,{children:`stroke_miter_limit`}),` (default 4) turning a too-sharp miter into a bevel.\n`,_jsx(_components.code,{children:`stroke_dash`}),` is an SVG dasharray of any length in viewBox units (`,_jsx(_components.code,{children:`[4]`}),` is 4 on,\n4 off; `,_jsx(_components.code,{children:`[8, 4, 2, 4]`}),` alternates from there) and `,_jsx(_components.code,{children:`stroke_dash_offset`}),` slides\nthe pattern along the path. Marching ants are `,_jsx(_components.code,{children:`stroke_dash_offset_keyframes`}),`\n(`,_jsx(_components.code,{children:`{time, offset, easing?}`}),`, timeline seconds): keyframe the offset linearly by\none dash period — the pattern’s sum — per second, so `,_jsx(_components.code,{children:`[8, 4]`}),` marches with\n`,_jsx(_components.code,{children:`[{\"time\": 0, \"offset\": 0}, {\"time\": 4, \"offset\": 48}]`}),`; negate the travel to\nreverse the direction. The `,_jsx(_components.code,{children:`path`}),` subset is `,_jsx(_components.code,{children:`M L H V C S Q T A Z`}),`; an `,_jsx(_components.code,{children:`A`}),` arc\nrenders as a true arc, so a ring or a donut is four arcs, not a chord polygon.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Transparent output:`}),` `,_jsx(_components.code,{children:`alpha: true`}),` requires `,_jsx(_components.code,{children:`output_format`}),` `,_jsx(_components.code,{children:`\"webm\"`}),` or\n`,_jsx(_components.code,{children:`\"mov\"`}),` — with `,_jsx(_components.code,{children:`mp4`}),` it is a 422. With `,_jsx(_components.code,{children:`alpha`}),` on, `,_jsx(_components.code,{children:`background_color`}),` is\nignored and the wire background is forced fully transparent, so an alpha\nexport cannot also carry a coloured backdrop; if you need one, draw it as a\nfull-frame `,_jsx(_components.code,{children:`shape`}),`. Everything else composites normally.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Transitions are video-to-video only.`}),` Both `,_jsx(_components.code,{children:`from_id`}),` and `,_jsx(_components.code,{children:`to_id`}),` must name\n`,_jsx(_components.em,{children:`visible`}),` `,_jsx(_components.code,{children:`video`}),` elements — a transition onto an image, text or shape is\nrejected (it crashes the render node). Each clip may be the outgoing\nside of at most one transition and the incoming side of at most one.\n`,_jsx(_components.code,{children:`{\"type\": \"cross_dissolve\", \"duration\": 0.8, \"from_id\": \"clip1\", \"to_id\": \"clip2\"}`}),` with clip2’s `,_jsx(_components.code,{children:`time`}),` at the boundary. To cross-fade stills,\nanimate opacity with `,_jsx(_components.code,{children:`fade`}),` motions instead.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Speed ramps:`}),` `,_jsx(_components.code,{children:`playback_rate_keyframes`}),` on a video are piecewise-linear\n`,_jsx(_components.code,{children:`time`}),`/`,_jsx(_components.code,{children:`rate`}),` points in absolute timeline seconds (rate 0 = freeze), at most\n512, and the times must be `,_jsx(_components.strong,{children:`strictly increasing`}),` — for an instant step put\nthe two rates `,_jsx(_components.code,{children:`t`}),` and `,_jsx(_components.code,{children:`t + 0.001`}),` apart. The audio inside the clip keeps the\n`,_jsx(_components.em,{children:`static`}),` rate, so mute the video element (`,_jsx(_components.code,{children:`volume: 0`}),`, legal there) or move\nthe sound to its own `,_jsx(_components.code,{children:`audio`}),` element. A ramp ending at rate 0 needs an\nexplicit request-level `,_jsx(_components.code,{children:`duration`}),` to say when the freeze stops, otherwise it\nis rejected. An `,_jsx(_components.code,{children:`audio`}),` element cannot be muted at all — its `,_jsx(_components.code,{children:`volume`}),` must be`,`\n`,_jsxs(_components.blockquote,{children:[`\n`,_jsxs(_components.p,{children:[`0; delete it instead (`,_jsx(_components.code,{children:`visible: false`}),` drops the track, it is not a mute).`]}),`\n`]}),`\n`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Masks:`}),` `,_jsx(_components.code,{children:`mask_shape`}),` (`,_jsx(_components.code,{children:`\"circle\"`}),`, …) clips the element;\n`,_jsx(_components.code,{children:`mask`}),` (with `,_jsx(_components.code,{children:`scale`}),`, `,_jsx(_components.code,{children:`center`}),`) sizes it (scale ≈ 0.15 = small iris, ≈ 2.0\ncovers the frame); `,_jsx(_components.code,{children:`mask_keyframes`}),` animates it over time. Masks,\n`,_jsx(_components.code,{children:`blend_mode`}),`, `,_jsx(_components.code,{children:`matte`}),` and `,_jsx(_components.code,{children:`backdrop_blur`}),` exist on `,_jsxs(_components.strong,{children:[_jsx(_components.code,{children:`video`}),`, `,_jsx(_components.code,{children:`image`}),` and\n`,_jsx(_components.code,{children:`shape`}),` only`]}),` — on `,_jsx(_components.code,{children:`text`}),` they are unknown fields and the request 422s with\n`,_jsx(_components.code,{children:`Extra inputs are not permitted`}),`. For text blends use the\nmotion presets `,_jsx(_components.code,{children:`overlay`}),` / `,_jsx(_components.code,{children:`difference`}),`; for a masked or blended card, put the\ntext over a `,_jsx(_components.code,{children:`shape`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Shadows and radii use four different scales.`}),` On `,_jsx(_components.code,{children:`video`}),`/`,_jsx(_components.code,{children:`image`}),`,\n`,_jsx(_components.code,{children:`shadow_x`}),`/`,_jsx(_components.code,{children:`shadow_y`}),` and `,_jsx(_components.code,{children:`border_radius`}),` are real pixels — but a shadow whose\noffset is `,_jsx(_components.code,{children:`0,0`}),` is rejected outright (the renderer skips the pass entirely, so\nset at least 1px). On `,_jsx(_components.code,{children:`shape`}),`, `,_jsx(_components.code,{children:`shadow.distance`}),`/`,_jsx(_components.code,{children:`blur`}),` are the\nengine’s 0–100 `,_jsx(_components.em,{children:`strength`}),` scale (reference scenes author 60–70; a px-looking\n`,_jsx(_components.code,{children:`8`}),` renders under a pixel on a small card) and `,_jsx(_components.code,{children:`corner_radius`}),` is already a\n0–1 fraction of half the shorter side, where `,_jsx(_components.code,{children:`1.0`}),` is a pill. Percentage\nstrings are rejected on every pixel-unit field (`,_jsx(_components.code,{children:`border_radius`}),`,\n`,_jsx(_components.code,{children:`border_width`}),`, `,_jsx(_components.code,{children:`shadow_x`}),`, `,_jsx(_components.code,{children:`shadow_y`}),`, `,_jsx(_components.code,{children:`backdrop_blur`}),`) — pass a number or\n`,_jsx(_components.code,{children:`\"Npx\"`}),`. On `,_jsx(_components.code,{children:`text`}),`, `,_jsx(_components.code,{children:`shadow_x`}),`/`,_jsx(_components.code,{children:`shadow_y`}),`/`,_jsx(_components.code,{children:`shadow_blur`}),` are a scale of their\nown again — pixels normalized against the canvas, neither the 0–100 strength\nof `,_jsx(_components.code,{children:`shape`}),` nor the border-grown pixels of `,_jsx(_components.code,{children:`video`}),`/`,_jsx(_components.code,{children:`image`}),` — so\nauthor them as a few px of offset and a blur in the tens, and never carry a\ncard’s `,_jsx(_components.code,{children:`distance: 60`}),` onto a text element.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`3D tilt`}),` on `,_jsx(_components.code,{children:`video`}),`, `,_jsx(_components.code,{children:`image`}),` and `,_jsx(_components.code,{children:`shape`}),`:\n`,_jsx(_components.code,{children:`x_rotation`}),`/`,_jsx(_components.code,{children:`y_rotation`}),` accept degree strings (`,_jsx(_components.code,{children:`\"18°\"`}),` or `,_jsx(_components.code,{children:`\"18deg\"`}),`).\nAnimate the tilt — and tilt text at all — with a `,_jsx(_components.code,{children:`custom_animations`}),`\ndefinition whose keyframes carry `,_jsx(_components.code,{children:`rotation: {x, y, z}`}),`, bound via\n`,_jsx(_components.code,{children:`motion: [{\"custom\": \"\u003cname>\", ...}]`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsxs(_components.strong,{children:[`A `,_jsx(_components.code,{children:`custom_animations`}),` keyframe `,_jsx(_components.code,{children:`time`}),` is a 0–1 fraction of the animation,\nnot seconds`]}),` (`,_jsx(_components.code,{children:`0`}),` = start, `,_jsx(_components.code,{children:`1`}),` = end); the wall-clock window comes from the\n`,_jsx(_components.code,{children:`motion[]`}),` entry’s `,_jsx(_components.code,{children:`time`}),`/`,_jsx(_components.code,{children:`duration`}),` that binds it. Every other keyframe list\n— `,_jsx(_components.code,{children:`mask_keyframes`}),`, `,_jsx(_components.code,{children:`playback_rate_keyframes`}),`, `,_jsx(_components.code,{children:`path_keyframes`}),`,\n`,_jsx(_components.code,{children:`trim_keyframes`}),`, `,_jsx(_components.code,{children:`stroke_dash_offset_keyframes`}),`, `,_jsx(_components.code,{children:`intensity_keyframes`}),`,\n`,_jsx(_components.code,{children:`word_animation.words[]`}),` — is in absolute timeline seconds.\n(`,_jsx(_components.code,{children:`counter.start_time`}),` is a third unit: seconds\n`,_jsx(_components.em,{children:`into the element`}),`.)`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Accepted but inert — do not build on these.`}),` `,_jsx(_components.code,{children:`motion[].easing`}),` is ignored — curves are baked into the preset, or into your\n`,_jsx(_components.code,{children:`custom_animations`}),` keyframes’ own `,_jsx(_components.code,{children:`easing`}),`. `,_jsx(_components.code,{children:`x_scale`}),`/`,_jsx(_components.code,{children:`y_scale`}),`,\n`,_jsx(_components.code,{children:`aspect_ratio`}),`, `,_jsx(_components.code,{children:`clip`}),`, `,_jsx(_components.code,{children:`name`}),` and `,_jsx(_components.code,{children:`track`}),` cross nowhere; size with\n`,_jsx(_components.code,{children:`width`}),`/`,_jsx(_components.code,{children:`height`}),`. `,_jsx(_components.code,{children:`lut_url`}),`/`,_jsx(_components.code,{children:`lut_intensity`}),` are accepted on `,_jsx(_components.code,{children:`image`}),` but\napplied only on `,_jsx(_components.code,{children:`video`}),` — grade a still with `,_jsx(_components.code,{children:`temperature`}),`/`,_jsx(_components.code,{children:`tint`}),`/`,_jsx(_components.code,{children:`vibrance`}),`/\n`,_jsx(_components.code,{children:`highlights`}),`/`,_jsx(_components.code,{children:`shadows`}),`, or bake it in. `,_jsx(_components.code,{children:`flip_horizontal`}),`/`,_jsx(_components.code,{children:`flip_vertical`}),` cross\nonly on `,_jsx(_components.code,{children:`video`}),` and `,_jsx(_components.code,{children:`image`}),` (mirror a `,_jsx(_components.code,{children:`shape`}),` by rewriting its `,_jsx(_components.code,{children:`path`}),`). On\n`,_jsx(_components.code,{children:`text`}),`, `,_jsx(_components.code,{children:`background_opacity`}),`, `,_jsx(_components.code,{children:`x_padding`}),` and `,_jsx(_components.code,{children:`y_padding`}),` cross nowhere: use\nan 8-digit `,_jsx(_components.code,{children:`background_color`}),` (`,_jsx(_components.code,{children:`\"#000000aa\"`}),`) for a translucent plate, and pad\nby widening `,_jsx(_components.code,{children:`width`}),` or setting the text over a `,_jsx(_components.code,{children:`shape`}),` card.\n(`,_jsx(_components.code,{children:`color_overlay`}),` is the opposite case — it is rejected outright; tint with a\nblend-moded overlay element.)`]}),`\n`,_jsxs(_components.li,{children:[_jsxs(_components.strong,{children:[_jsx(_components.code,{children:`image`}),` and `,_jsx(_components.code,{children:`shape`}),` elements require a non-empty `,_jsx(_components.code,{children:`id`}),` and a\n`,_jsx(_components.code,{children:`duration`}),`.`]}),` `,_jsx(_components.code,{children:`text`}),` requires `,_jsx(_components.code,{children:`duration`}),`; an id is optional there, but give\nevery element a unique one anyway — duplicates are a blocking\n`,_jsx(_components.code,{children:`DUPLICATE_ELEMENT_ID`}),`, and transitions, groups, mattes and ops all address\nelements by id.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`a-minimal-composition`,children:`A minimal composition`}),`\n`,_jsxs(_components.p,{children:[`A dark hero card with a headline that fades in — note the explicit `,_jsx(_components.code,{children:`duration`}),` on the text,\nthe center-based `,_jsx(_components.code,{children:`%`}),` position, and the entrance motion whose `,_jsx(_components.code,{children:`time`}),` matches the element’s:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`21`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`21`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`{`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"width\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`1920`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"height\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`1080`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"duration\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`6`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"background_color\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"#0B1B3AFF\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"elements\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: [`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` {`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"type\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"text\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"id\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"headline\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"text\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"Meet Lumen\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"time\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"duration\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`6`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"x\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"50%\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"y\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"46%\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"font_size\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`120`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"font_weight\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`700`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"text_color\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"#FFFFFF\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"motion\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: [{ `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"type\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"fade\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"time\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"duration\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`1`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }]`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` ]`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),`\n`,_jsx(Heading,{level:`3`,id:`a-gradient-card`,children:`A gradient card`}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[`Do not write a `,_jsx(_components.code,{children:`gradient`}),` element — it was `,_jsx(_components.strong,{children:`removed`}),`. `,_jsx(_components.code,{children:`{\"type\": \"gradient\", …}`}),` is a 422\nwhose message names `,_jsx(_components.code,{children:`shape`}),` and includes the exact JSON to send. Stored projects needed no\nediting: every saved composition was rewritten to the `,_jsx(_components.code,{children:`shape`}),` form, which renders identically.\nTo port a body by hand: `,_jsx(_components.code,{children:`type`}),` `,_jsx(_components.code,{children:`\"gradient\"`}),` → `,_jsx(_components.code,{children:`\"shape\"`}),`, rename `,_jsx(_components.code,{children:`gradient`}),` → `,_jsx(_components.code,{children:`fill`}),`, add\n`,_jsx(_components.code,{children:`\"path\": \"M0 0 H100 V100 H0 Z\"`}),`. Everything else (`,_jsx(_components.code,{children:`corner_radius`}),`, `,_jsx(_components.code,{children:`shadow`}),`, `,_jsx(_components.code,{children:`effects`}),`,\n`,_jsx(_components.code,{children:`motion`}),`, the transform fields) keeps its name and units.`]})}),`\n`,_jsxs(_components.p,{children:[`The panel a brief calls a “gradient background” or a “hero card” — a `,_jsx(_components.code,{children:`shape`}),` with a\nrectangular `,_jsx(_components.code,{children:`path`}),`, a `,_jsx(_components.code,{children:`corner_radius`}),` and a gradient `,_jsx(_components.code,{children:`fill`}),`. List it before the headline and\nthe painter’s rule puts the text on top with no `,_jsx(_components.code,{children:`z_index`}),`:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`21`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`21`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`{`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"type\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"shape\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"id\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"hero_card\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"time\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"duration\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`6`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"x\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"50%\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"y\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"50%\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"width\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"62%\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"height\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"34%\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"path\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"M0 0 H100 V100 H0 Z\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"corner_radius\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0.18`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"fill\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"kind\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"linear\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"angle_degrees\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`45`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"stops\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: [`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"offset\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"color\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"#e07a4f\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` },`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"offset\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`1`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"color\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"#7c3aed\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` ]`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` },`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"shadow\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"color\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"#00000066\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"distance\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`65`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"direction\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`90`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"blur\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`60`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`The CSS shorthand is accepted wherever the object is — a shape’s `,_jsx(_components.code,{children:`fill`}),` and the request-level\n`,_jsx(_components.code,{children:`background_gradient`}),` — and is normalized at validation, so the stored composition and every\nread-back hold the object, never the string:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`13`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`13`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`{`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"type\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"shape\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"id\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"hero_card\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"time\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"duration\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`6`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"x\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"50%\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"y\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"50%\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"width\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"62%\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"height\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"34%\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"path\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"M0 0 H100 V100 H0 Z\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"corner_radius\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0.18`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"fill\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"linear-gradient(45deg, #e07a4f 0%, #7c3aed 100%)\"`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`What the CSS parser cannot represent it refuses with the fix in the message: `,_jsx(_components.code,{children:`repeating-*`}),`\nand `,_jsx(_components.code,{children:`conic-gradient(...)`}),`, `,_jsx(_components.code,{children:`var()`}),`/`,_jsx(_components.code,{children:`calc()`}),`, vendor prefixes, a unitless angle (`,_jsx(_components.code,{children:`45`}),` →\n`,_jsx(_components.code,{children:`45deg`}),`) and pixel stop positions (`,_jsx(_components.code,{children:`40px`}),` → `,_jsx(_components.code,{children:`25%`}),`).`]}),`\n`,_jsx(Heading,{level:`2`,id:`beyond-the-basics-—-what-else-the-engine-draws`,children:`Beyond the basics — what else the engine draws`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:_jsx(_components.code,{children:`shape`})}),` — an SVG `,_jsx(_components.code,{children:`d`}),` path with fill/stroke/dash, where the `,_jsx(_components.code,{children:`fill`}),` is a hex\ncolour, `,_jsx(_components.code,{children:`\"none\"`}),`, a Gradient object or a CSS `,_jsx(_components.code,{children:`linear-gradient(...)`}),` string, so a\ngradient card is a rectangular path with a gradient fill; `,_jsx(_components.code,{children:`trim`}),` +\n`,_jsx(_components.code,{children:`trim_keyframes`}),` draw it on (trim applies before dash, so a dashed line draws on\nwithout the pattern re-flowing), `,_jsx(_components.code,{children:`stroke_dash_offset_keyframes`}),` march the dashes,\n`,_jsx(_components.code,{children:`path_keyframes`}),` morph it.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:_jsx(_components.code,{children:`groups[]`})}),` — transform several elements as one (translate/scale/rotate/3D\ntilt, a row/column `,_jsx(_components.code,{children:`stack`}),` layout, and their own `,_jsx(_components.code,{children:`animations[]`}),`); group\n`,_jsx(_components.code,{children:`opacity`}),` is 0–1 while element `,_jsx(_components.code,{children:`opacity`}),` is 0–100.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:_jsx(_components.code,{children:`custom_animations`})}),` — your own keyframed presets, bound with\n`,_jsx(_components.code,{children:`motion: [{\"custom\": \"\u003cname>\"}]`}),`; the only way to tilt or 3D-rotate text.\nName them distinctively: `,_jsx(_components.code,{children:`none`}),`, `,_jsx(_components.code,{children:`overlay`}),`, `,_jsx(_components.code,{children:`difference`}),`, any name a catalog\nanimation already answers to (`,_jsx(_components.code,{children:`fade`}),`, `,_jsx(_components.code,{children:`pop`}),`, `,_jsx(_components.code,{children:`scale`}),`, `,_jsx(_components.code,{children:`bounce`}),`,\n`,_jsx(_components.code,{children:`typewriter`}),`, …) and duplicates within one registry are all rejected — the\nengine would silently run the catalog animation instead of yours. Binding:\n`,_jsx(_components.code,{children:`motion[].custom`}),` resolves only against `,_jsx(_components.em,{children:`this`}),` request’s registry (an unknown\nname is a 422, not a silent drop), takes no `,_jsx(_components.code,{children:`reversed: true`}),` (author the exit\nas its own entry) and no `,_jsx(_components.code,{children:`scope: \"character\"`}),` (put `,_jsx(_components.code,{children:`group: \"glyph\"`}),` or\n`,_jsx(_components.code,{children:`\"word\"`}),` on the definition instead).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:_jsx(_components.code,{children:`matte`})}),` — drive one layer’s coverage from another layer’s render; the\nsource layer is auto-hidden.`]}),`\n`,_jsxs(_components.li,{children:[_jsxs(_components.strong,{children:[_jsx(_components.code,{children:`text.counter`}),` / `,_jsx(_components.code,{children:`text.glow`}),` / `,_jsx(_components.code,{children:`text.words[]`})]}),` — animated numbers,\nem-relative glow, per-word colour/bold/underline runs.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:_jsx(_components.code,{children:`motion[].audio`})}),` — beat/bass-reactive offsets on video/image/shape and\ngroups (never text); pair with `,_jsx(_components.code,{children:`type: \"identity\"`}),` for a pure generator.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:_jsx(_components.code,{children:`effects[]`})}),` — a shader chain of 47 named effects, on `,_jsx(_components.code,{children:`video`}),`, `,_jsx(_components.code,{children:`image`}),` and\n`,_jsx(_components.code,{children:`shape`}),` alike. `,_jsx(_components.code,{children:`intensity`}),` is 0–100, but\n`,_jsx(_components.code,{children:`intensity_keyframes`}),` are `,_jsx(_components.code,{children:`{time, value}`}),` with `,_jsx(_components.code,{children:`time`}),` in absolute timeline\nseconds and `,_jsxs(_components.strong,{children:[_jsx(_components.code,{children:`value`}),` on a 0–1 scale, not 0–100`]}),` — a `,_jsx(_components.code,{children:`value`}),` above 1 is a\n422. Two rules: `,_jsx(_components.code,{children:`props`}),` is whitelisted per shader and `,_jsxs(_components.strong,{children:[`only `,_jsx(_components.code,{children:`aurora`}),` binds\none`]}),` (`,_jsx(_components.code,{children:`{\"speed\": n}`}),`) — any other key on `,_jsx(_components.code,{children:`aurora`}),`, and any prop at all on\nany other shader, is a 422, because the renderer would bind nothing and\nsilently no-op. And `,_jsx(_components.code,{children:`chroma_key`}),` takes its parameters through\n`,_jsx(_components.code,{children:`chroma_settings`}),` (`,_jsx(_components.code,{children:`key_color`}),`, `,_jsx(_components.code,{children:`threshold`}),`, `,_jsx(_components.code,{children:`smoothness`}),`, `,_jsx(_components.code,{children:`spill_suppress`}),`),\nnever `,_jsx(_components.code,{children:`props`}),` — it is the one shader that builds its own `,_jsx(_components.code,{children:`props`}),` block, so a\n`,_jsx(_components.code,{children:`props`}),` you set on it is silently discarded rather than rejected. Leave its\n`,_jsx(_components.code,{children:`intensity`}),` unset for a full key, since an explicit 50 leaves a translucent\ngreen wash.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:_jsx(_components.code,{children:`motion_blur`})}),`, `,_jsx(_components.strong,{children:_jsx(_components.code,{children:`background_gradient`})}),`, `,_jsx(_components.strong,{children:_jsx(_components.code,{children:`blend_mode`})}),`,\n`,_jsx(_components.strong,{children:_jsx(_components.code,{children:`backdrop_blur`})}),` — request- and element-level.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`Field shapes live in `,_jsx(_components.code,{children:`GET /v1/capabilities`}),` → `,_jsx(_components.code,{children:`element_schemas`}),` (per element\ntype) and `,_jsx(_components.code,{children:`request_schema`}),` (composition level — groups, custom animations,\nmotion blur, transitions); this page does not repeat them.`]}),`\n`,_jsxs(Heading,{level:`3`,id:`named-text-animations-animation_preset`,children:[`Named text animations (`,_jsx(_components.code,{children:`animation_preset`}),`)`]}),`\n`,_jsxs(_components.p,{children:[`A `,_jsx(_components.code,{children:`text`}),` element can name a renderer catalog text animation directly in\n`,_jsx(_components.code,{children:`animation_preset`}),` — 70 of them, and `,_jsxs(_components.strong,{children:[`none of them appear in\n`,_jsx(_components.code,{children:`GET /v1/capabilities`})]}),`, whose `,_jsx(_components.code,{children:`motions[]`}),` covers only the 42 `,_jsx(_components.code,{children:`motion[]`}),`\npresets. It is the largest authorable surface on text and there is no other way\nto find it. Names are the engine’s camelCase ids, not `,_jsx(_components.code,{children:`snake_case`}),`\n`,_jsx(_components.code,{children:`MotionType`}),` values.`]}),`\n`,_jsxs(_components.p,{children:[`Entrances — `,_jsx(_components.code,{children:`ascent`}),`, `,_jsx(_components.code,{children:`billboard`}),`, `,_jsx(_components.code,{children:`block`}),`, `,_jsx(_components.code,{children:`blur`}),`, `,_jsx(_components.code,{children:`bounce`}),`, `,_jsx(_components.code,{children:`boxBounce`}),`,\n`,_jsx(_components.code,{children:`boxHighlight`}),`, `,_jsx(_components.code,{children:`boxHighlightV2`}),`, `,_jsx(_components.code,{children:`burst`}),`, `,_jsx(_components.code,{children:`colourHighlight`}),`,\n`,_jsx(_components.code,{children:`colourHighlightV2`}),`, `,_jsx(_components.code,{children:`compress`}),`, `,_jsx(_components.code,{children:`dragonfly`}),`, `,_jsx(_components.code,{children:`driftIn`}),`, `,_jsx(_components.code,{children:`dropIn`}),`, `,_jsx(_components.code,{children:`evaporate`}),`,\n`,_jsx(_components.code,{children:`fade`}),`, `,_jsx(_components.code,{children:`fall`}),`, `,_jsx(_components.code,{children:`flipClock`}),`, `,_jsx(_components.code,{children:`flipboard`}),`, `,_jsx(_components.code,{children:`floatInBottom`}),`, `,_jsx(_components.code,{children:`floatInTop`}),`,\n`,_jsx(_components.code,{children:`glitchPop`}),`, `,_jsx(_components.code,{children:`highlight`}),`, `,_jsx(_components.code,{children:`impact`}),`, `,_jsx(_components.code,{children:`impactPop`}),`, `,_jsx(_components.code,{children:`karaoke`}),`, `,_jsx(_components.code,{children:`reveal`}),`, `,_jsx(_components.code,{children:`roll`}),`,\n`,_jsx(_components.code,{children:`rotateFlipClock`}),`, `,_jsx(_components.code,{children:`rotateZigZag`}),`, `,_jsx(_components.code,{children:`rubberIn`}),`, `,_jsx(_components.code,{children:`scale`}),`, `,_jsx(_components.code,{children:`scaleIn`}),`, `,_jsx(_components.code,{children:`skid`}),`,\n`,_jsx(_components.code,{children:`slideDown`}),`, `,_jsx(_components.code,{children:`slideLeft`}),`, `,_jsx(_components.code,{children:`slideRight`}),`, `,_jsx(_components.code,{children:`slideUp`}),`, `,_jsx(_components.code,{children:`stomp`}),`, `,_jsx(_components.code,{children:`typewriter`}),`,\n`,_jsx(_components.code,{children:`verticalStretch`}),`, `,_jsx(_components.code,{children:`vogue`}),`, `,_jsx(_components.code,{children:`wave`}),`, `,_jsx(_components.code,{children:`wavey`}),`, `,_jsx(_components.code,{children:`whipUp`}),`, `,_jsx(_components.code,{children:`zoomIn`}),`.`]}),`\n`,_jsxs(_components.p,{children:[`Exits — `,_jsx(_components.code,{children:`out-billboard`}),`, `,_jsx(_components.code,{children:`out-block`}),`, `,_jsx(_components.code,{children:`out-burst`}),`, `,_jsx(_components.code,{children:`out-decompress`}),`,\n`,_jsx(_components.code,{children:`out-dragonfly`}),`, `,_jsx(_components.code,{children:`out-driftOut`}),`, `,_jsx(_components.code,{children:`out-fade`}),`, `,_jsx(_components.code,{children:`out-fall`}),`, `,_jsx(_components.code,{children:`out-flipboard`}),`,\n`,_jsx(_components.code,{children:`out-roll`}),`, `,_jsx(_components.code,{children:`out-scale`}),`, `,_jsx(_components.code,{children:`out-sink`}),`, `,_jsx(_components.code,{children:`out-skid`}),`, `,_jsx(_components.code,{children:`out-slideDown`}),`,\n`,_jsx(_components.code,{children:`out-slideLeft`}),`, `,_jsx(_components.code,{children:`out-slideRight`}),`, `,_jsx(_components.code,{children:`out-slideUp`}),`, `,_jsx(_components.code,{children:`out-stomp`}),`,\n`,_jsx(_components.code,{children:`out-verticalStretch`}),`, `,_jsx(_components.code,{children:`out-vogue`}),`, `,_jsx(_components.code,{children:`out-wavey`}),`, `,_jsx(_components.code,{children:`out-whipDown`}),`, `,_jsx(_components.code,{children:`out-zoomOut`}),`.`]}),`\n`,_jsxs(_components.p,{children:[`Two further names resolve here without being catalog animations: `,_jsx(_components.code,{children:`overlay`}),` and\n`,_jsx(_components.code,{children:`difference`}),`, the renderer’s static text blend keys (the same two you can reach\nas `,_jsx(_components.code,{children:`motion[]`}),` presets).`]}),`\n`,_jsxs(_components.p,{children:[`Three rules. A name that is none of those — not a catalog animation, not a\nblend key, not a `,_jsx(_components.code,{children:`custom_animations.text`}),` entry in the same request — is a 422\n(the engine would drop it with no warning). A `,_jsx(_components.code,{children:`loop-*`}),` name is a 422 too: a\nloop needs an explicit cycle length this route cannot express, so use `,_jsx(_components.code,{children:`motion[]`}),`\nwith the matching `,_jsx(_components.code,{children:`loop_*`}),` type and `,_jsx(_components.code,{children:`duration`}),` = one cycle. And\n`,_jsx(_components.code,{children:`animation_preset`}),` cannot be combined with `,_jsx(_components.code,{children:`motion[]`}),` — that pair is a 422.\nPairing it with `,_jsx(_components.code,{children:`word_animation`}),` is `,_jsx(_components.em,{children:`not`}),` rejected, which is worse:\n`,_jsx(_components.code,{children:`word_animation`}),` takes precedence and your preset is silently dropped, so send\nexactly one of the three.`]}),`\n`,_jsx(Heading,{level:`2`,id:`validate-before-you-render`,children:`Validate before you render`}),`\n`,_jsxs(_components.p,{children:[`Every composition can be linted for free before you spend a render. `,_jsx(_components.code,{children:`POST /v1/preview`}),`\nwith `,_jsx(_components.code,{children:`dry_run: true`}),` runs the cross-element linter and returns\n`,_jsx(_components.code,{children:`{ \"kind\": \"validation\", \"ok\": \u003cbool>, \"violations\": [...] }`}),` — no render is dispatched.`]}),`\n`,_jsxs(_components.p,{children:[`Each violation carries a stable `,_jsx(_components.code,{children:`code`}),`, a human `,_jsx(_components.code,{children:`message`}),`, a `,_jsx(_components.code,{children:`path`}),` (e.g.\n`,_jsx(_components.code,{children:`elements[2].time`}),`) with its RFC 6901 `,_jsx(_components.code,{children:`pointer`}),` (`,_jsx(_components.code,{children:`/elements/2/time`}),`), the `,_jsx(_components.code,{children:`target_id`}),` of\nthe element it is about, and, for layout findings, a `,_jsx(_components.code,{children:`bbox`}),` and `,_jsx(_components.code,{children:`time`}),` you can act on\ndirectly. `,_jsx(_components.code,{children:`ok`}),` is `,_jsx(_components.code,{children:`true`}),` only when there are no error-severity violations — meaning the\nrequest `,_jsx(_components.em,{children:`will`}),` submit. Every unsupported-feature rejection the submit path would\nraise is folded into this array at `,_jsx(_components.code,{children:`error`}),` severity too, so the dry run surfaces\nthe whole 422 class before you spend a render. Repair every error, re-lint, then\nrender. Common findings:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`OUT_OF_FRAME`}),` — an element’s box falls partly or fully off-canvas.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`ELEMENT_OUT_OF_RANGE`}),` — an element starts at/after the output `,_jsx(_components.code,{children:`duration`}),` and the\nrenderer drops it.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`DUPLICATE_ELEMENT_ID`}),` — two elements share an `,_jsx(_components.code,{children:`id`}),`; transition targets and ops\nthat address it become ambiguous. Rename one. (error)`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`DANGLING_TRANSITION`}),` — a transition `,_jsx(_components.code,{children:`from_id`}),`/`,_jsx(_components.code,{children:`to_id`}),` matches no element. (error)`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`TRANSITION_SELF`}),` — `,_jsx(_components.code,{children:`from_id`}),` equals `,_jsx(_components.code,{children:`to_id`}),`. (error)`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`WORD_OUT_OF_RANGE`}),` — a `,_jsx(_components.code,{children:`word_animation.words[]`}),` timing falls outside the element’s\n`,_jsx(_components.code,{children:`[time, time + duration]`}),` window; word timings are absolute, not element-relative.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`ENTRANCE_AFTER_ELEMENT_START`}),` — an element’s entrance motion starts after the element\nitself, so it sits fully rendered on screen and `,_jsx(_components.em,{children:`then`}),` plays its entrance. Set\n`,_jsx(_components.code,{children:`motion[].time`}),` (and `,_jsx(_components.code,{children:`delay`}),`) to the element’s own `,_jsx(_components.code,{children:`time`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`AUDIO_HIDDEN`}),` — `,_jsx(_components.code,{children:`visible: false`}),` on an audio element removes the track from the\nrender; it is not a mute.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`SHADOW_BELOW_MIN_SIZE`}),` — `,_jsx(_components.code,{children:`shadow.distance`}),` is a 0–100 strength, not pixels, and this\nvalue renders an offset under one pixel.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`HAIRLINE_BELOW_PIXEL`}),` — a shape’s `,_jsx(_components.code,{children:`stroke_width`}),` is in viewBox units and lands on the\ncanvas scaled by the box; this one renders under one device pixel and drops out. The\nmessage carries the width that clears a pixel.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`DASH_POINT_BUDGET`}),` — `,_jsx(_components.code,{children:`stroke_dash`}),` over this much path needs more dash points than the\nrenderer’s 100,000 cap, so the rest of the path draws solid. Lengthen the period (the\npattern’s sum, in viewBox units — the box does not scale it).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`SHAPE_ASPECT_MISMATCH`}),` — the box aspect is more than 10% off the `,_jsx(_components.code,{children:`view_box`}),` aspect, so\na non-rectangular path is stretched along one axis (circles become ellipses, strokes\ndiffer per axis). Match the box to the viewBox aspect or the viewBox to the box.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`LOW_CONTRAST`}),` and `,_jsx(_components.code,{children:`TEXT_OVERLAP`}),` abstain rather than guess: the linter stays silent when\nthe backdrop is a photo, a video, a translucent fill or only a partial cover, and it\ncompares glyph ink rather than line boxes, so an ordinary title/subtitle stack whose\nleading grazes is not reported.`]}),`\n`,_jsxs(_components.p,{children:[`The same linter rides along on `,_jsx(_components.code,{children:`POST /v1/projects/{id}/ops`}),`, so iterative edits are validated\nas you make them.`]}),`\n`,_jsxs(_components.p,{children:[`It also rides along on the paths that spend money — the `,_jsx(_components.code,{children:`202`}),` from `,_jsx(_components.code,{children:`POST /v1/renders`}),`\nand `,_jsx(_components.code,{children:`POST /v1/projects/{id}/renders`}),`, and the `,_jsx(_components.code,{children:`201`}),` from `,_jsx(_components.code,{children:`POST /v1/projects`}),` — where it\nis `,_jsx(_components.strong,{children:`advisory`}),`: `,_jsx(_components.code,{children:`ok`}),` and `,_jsx(_components.code,{children:`violations[]`}),` are attached, and the render is dispatched (or\nthe project stored) regardless. Treat that as a receipt, not a gate. The gate is the free\n`,_jsx(_components.code,{children:`dry_run`}),` above, which is also the more informative of the two, because it runs the full\ntranslator and so reports the whole 422 class as well. A `,_jsx(_components.code,{children:`LINT_UNAVAILABLE`}),` warning with\n`,_jsx(_components.code,{children:`ok: true`}),` on a submit means the linter itself failed server-side; it says nothing about\nyour composition.`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Author a video","description":"The renderer semantics an agent must know to author a RenderRequest that renders right the first time — timing, layering, positioning, and the silent defaults that trip agents up.","href":"/guides/author-video"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"The model","slug":"the-model","depth":2,"children":[]},{"title":"Semantics you must know (renderer behavior)","slug":"semantics-you-must-know-renderer-behavior","depth":2,"children":[]},{"title":"A minimal composition","slug":"a-minimal-composition","depth":2,"children":[{"title":"A gradient card","slug":"a-gradient-card","depth":3,"children":[]}]},{"title":"Beyond the basics — what else the engine draws","slug":"beyond-the-basics-—-what-else-the-engine-draws","depth":2,"children":[{"title":"Named text animations (animation_preset)","slug":"named-text-animations-animation_preset","depth":3,"children":[]}]},{"title":"Validate before you render","slug":"validate-before-you-render","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"itis-c7dc7146","siteName":"Framelane","title":"Author a video - Framelane","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-10-26T06:01:42.324Z","originalFileLocation":"guides/author-video.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"The renderer semantics an agent must know to author a RenderRequest that renders right the first time — timing, layering, positioning, and the silent defaults that trip agents up.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Framelane\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003cmeta data-mint-head name=\"robots\" content=\"index, follow\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/itis-c7dc7146/3AM3-gs_iChWRW0W/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/itis-c7dc7146/3AM3-gs_iChWRW0W/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/itis-c7dc7146/3AM3-gs_iChWRW0W/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/itis-c7dc7146/3AM3-gs_iChWRW0W/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/itis-c7dc7146/3AM3-gs_iChWRW0W/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/itis-c7dc7146/3AM3-gs_iChWRW0W/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/itis-c7dc7146/3AM3-gs_iChWRW0W/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/itis-c7dc7146/3AM3-gs_iChWRW0W/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/itis-c7dc7146/3AM3-gs_iChWRW0W/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Author a video - Framelane\">\u003cmeta data-mint-head property=\"og:description\" content=\"The renderer semantics an agent must know to author a RenderRequest that renders right the first time — timing, layering, positioning, and the silent defaults that trip agents up.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Framelane\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.framelane.io/guides/author-video\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://cdn-assets.framelane.io/shared/images/og-docs.png\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Author a video - Framelane\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"The renderer semantics an agent must know to author a RenderRequest that renders right the first time — timing, layering, positioning, and the silent defaults that trip agents up.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://cdn-assets.framelane.io/shared/images/og-docs.png\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/itis-c7dc7146/3AM3-gs_iChWRW0W/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Framelane\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#0070f3\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.framelane.io/guides/author-video\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/guides/author-video.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.framelane.io/#organization\",\"name\":\"Framelane\",\"url\":\"https://docs.framelane.io\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/itis-c7dc7146/I-4ZfymyiJyBmcDs/logo/framelane-light.png?fit=max\u0026auto=format\u0026n=I-4ZfymyiJyBmcDs\u0026q=85\u0026s=a819adfb6e5b9a97575f20c034d41c77\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.framelane.io#website\",\"name\":\"Framelane\",\"url\":\"https://docs.framelane.io\",\"publisher\":{\"@id\":\"https://docs.framelane.io/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.framelane.io/guides/author-video#webpage\",\"url\":\"https://docs.framelane.io/guides/author-video\",\"name\":\"Author a video\",\"description\":\"The renderer semantics an agent must know to author a RenderRequest that renders right the first time — timing, layering, positioning, and the silent defaults that trip agents up.\",\"dateModified\":\"2026-10-26T06:01:42.324Z\",\"isPartOf\":{\"@id\":\"https://docs.framelane.io#website\"},\"breadcrumb\":{\"@id\":\"https://docs.framelane.io/guides/author-video#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.framelane.io/guides/author-video#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Framelane\",\"item\":\"https://docs.framelane.io\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Author a video\",\"item\":\"https://docs.framelane.io/guides/author-video\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.framelane.io/guides/author-video#article\",\"headline\":\"Author a video\",\"name\":\"Author a video\",\"description\":\"The renderer semantics an agent must know to author a RenderRequest that renders right the first time — timing, layering, positioning, and the silent defaults that trip agents up.\",\"url\":\"https://docs.framelane.io/guides/author-video\",\"mainEntityOfPage\":{\"@id\":\"https://docs.framelane.io/guides/author-video#webpage\"},\"image\":\"https://cdn-assets.framelane.io/shared/images/og-docs.png\",\"dateModified\":\"2026-10-26T06:01:42.324Z\",\"publisher\":{\"@id\":\"https://docs.framelane.io/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.framelane.io#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Framelane\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Framelane","nav":{"global":{"anchors":[{"anchor":"GitHub","icon":"github","href":"https://github.com/framelane"},{"anchor":"Status","icon":"signal-bars","href":"https://status.framelane.io"}]},"tabs":[{"tab":"Documentation","groups":[{"group":"Get Started","pages":[{"title":"Overview","href":"/introduction/overview"},{"title":"Quickstart","href":"/introduction/quickstart"},{"title":"Authentication","href":"/introduction/authentication"},{"title":"Self-provisioning for agents","href":"/introduction/self-provisioning"},{"title":"Uploading media","href":"/introduction/uploads"},{"title":"Errors","href":"/introduction/errors"},{"title":"Rate Limits","href":"/introduction/rate-limits"},{"title":"Production Checklist","href":"/introduction/production-checklist"},{"title":"Changelog","href":"/introduction/changelog"}]},{"group":"Renders","pages":[{"title":"Renders","href":"/renders/overview"},{"title":"Playground","href":"/renders/playground"},{"title":"Elements","href":"/renders/elements"},{"title":"Preview","href":"/renders/preview"}]},{"group":"Render Examples","pages":[{"title":"Video Examples","href":"/renders/examples/video"},{"title":"Audio Examples","href":"/renders/examples/audio"},{"title":"Text Examples","href":"/renders/examples/text"},{"title":"Image Examples","href":"/renders/examples/image"},{"title":"Color Adjustments","href":"/renders/examples/color-adjustments"},{"title":"Effects Examples","href":"/renders/examples/effects"},{"title":"Motion Examples","href":"/renders/examples/motion"},{"title":"Word Animation Examples","href":"/renders/examples/word-animation"},{"title":"Transition Examples","href":"/renders/examples/transitions"}]},{"group":"Recipes","pages":[{"title":"Layered Video Composition","href":"/renders/recipes/layered-video-composition"},{"title":"TikTok Captions","href":"/renders/recipes/tik-tok-captions"},{"title":"Text Behind the Speaker","href":"/renders/recipes/text-behind-speaker"}]},{"group":"Projects","pages":[{"title":"Cutting","href":"/projects/cutting"}]},{"group":"Tasks","pages":[{"title":"Tasks","href":"/tasks/overview"},{"title":"Transcribe","href":"/tasks/transcribe"}]},{"group":"Webhooks","pages":[{"title":"Webhooks","href":"/webhooks/overview"},{"title":"Events","href":"/webhooks/events"},{"title":"Signature Verification","href":"/webhooks/signature-verification"},{"title":"Retry Schedule","description":"Framelane retries failed webhook deliveries automatically on an exponential back-off schedule.","href":"/webhooks/retry-schedule"}]},{"group":"Guides","pages":[{"title":"Author a video","href":"/guides/author-video"},{"title":"Measure a reference","description":"Extract exact colors, positions, and timings from reference footage instead of authoring from memory — the cheapest way to match a look.","href":"/guides/measure-reference"},{"title":"Render Video","href":"/guides/render-video"},{"title":"Transcribe","href":"/guides/transcribe"},{"title":"Generate Social Clip","href":"/guides/generate-social-clip"},{"title":"Batch Tasks","href":"/guides/batch-tasks"},{"title":"Webhook Setup","href":"/guides/webhook-setup"}]},{"group":"SDKs","pages":[{"title":"TypeScript SDK","href":"/sdks/typescript"},{"title":"Python SDK","href":"/sdks/python"}]}]},{"tab":"Integrations","pages":[{"title":"Integrations","href":"/mcp/overview"}]},{"tab":"API Reference","pages":[{"title":"API Reference","href":"/api-reference/introduction"}]}]},"title":"Author a video - Framelane","buildId":"dpl_DFJrtvUi8HS5F9nkooKuDJsuD5BM","deploymentId":"dpl_DFJrtvUi8HS5F9nkooKuDJsuD5BM","dynamics":[],"chunks":[]}