The VideoConfig format
A video is one JSON file: an ordered list of scenes, each referencing a scene template by camelCase id with variables, plus video-level style and audio. The CLI validates it and renders it deterministically — same config, same MP4.
Skeleton
{
"format": "launch",
"orientation": "portrait",
"style": { "font": "Inter" },
"audio": { "trackId": "launch-sequence-alpha" },
"scenes": [
{
"id": "s0",
"templateId": "media",
"variables": { "texts": "Ship it today" },
"timing": {}
}
]
}scenes (non-empty) and style (with a font) are required. Everything else is optional — a config that omits orientation renders portrait.
Scenes
| Field | Description |
|---|---|
id | Unique string id for the scene (e.g. “s0”). |
templateId | A camelCase template id from the registry (e.g. media, bigNumber, terminal). Never guess ids — the validator rejects ones it doesn't know. |
variables | Per-template content. Each template's full variable schema with types, options, and defaults is in its registry item at r/<id>.json under meta.vanillasky.variableSchema. |
timing | Required object. Leave it {} to play the scene at the template's preferred duration, or pin seconds with fixedDuration. |
textArchetype | Optional text-animation treatment for the scene. One of subtle, typewriter, wordStagger, slam, cinematic, heroWord. |
backgroundEffect | Optional background motion for templates that consume it (e.g. slow-zoom-in on a media scene). |
componentSource | Custom scene body for custom_* template ids — see Templates & the registry. |
Placement matters: textArchetype and backgroundEffect are scene-level fields, not entries in variables — the validator rejects them inside variables.
Orientation
orientation picks the frame: "portrait" renders 9:16 at 1080x1920, "landscape" renders 16:9 at 1920x1080. Both are first-class — every template is responsive to either — and a config that omits the field renders portrait.
Style & fonts
style is required by the renderer. validate warns when it's missing and render injects a minimal default ({ "font": "Inter" }), but a config shared via vanillasky link gets no such safety net — always set it explicitly.
For offline renders the CLI bundles every Google font family the font picker offers, so any style.font from that list renders identically offline; families outside it fall back to system fonts.
Style presets
style.preset picks the frame-level look — background family, headline type treatment, and default title placement, moved together by one token. Set it once on style, never per scene; unset resolves to the default. The validator rejects unknown preset names instead of silently falling back.
| Preset | Use when |
|---|---|
bold | The default. Drifting two-color brand mesh, heavy tight headlines at the top. Launches, hype, product moments. |
editorial | Calm vertical wash, lighter and wider-tracked centered headlines. Reviews, thoughtful updates, premium or B2B brands. |
stark | Single hard spotlight on near-black, uppercase and tightly tracked. Dev tools, technical claims, high-contrast statements. |
Brand kit
style.brandKit carries the brand colors: accent (primary, required), secondary (the other half of the gradient — equal to accent collapses to a flat color), optional text, bg (solid background override), and logoDataUrl. Repos with a DESIGN.md get these filled automatically — see the CLI reference. Config-explicit values always win.
Audio
Two ways to reference audio, both under the top-level audio key:
"audio": { "trackId": "<id>" }— a track from the bundled library.vanillasky trackslists ids, moods, energy, and durations. Prefer high-energy tracks for launches, calmer ones for review/update formats, and a track at least as long as the video.audio.audioUrl— a direct https URL (downloaded) or a local file path.
Tracks shorter than the video loop; audio fades out over the closer by default. Offline behavior: trackId resolves against the bundled library with no network at all, and anything unresolvable renders silent with a warning — audio problems never fail a render. Silence is allowed but discouraged for social: a beat under the cut is most of the perceived production value.
Stock media
Scenes with a mediaKeyword variable resolve to stock footage automatically when PEXELS_API_KEY is set (free at pexels.com/api). Without a key, set a direct mediaUrl or the scene falls back to its brand gradient — the validator warns about this. Media URLs must be directly fetchable: they are prefetched, and the render refuses to start if any fail, so there are never black scenes.
The launch format
Declaring "format": "launch" (or passing --format launch to the CLI) makes the validator enforce the slot contract mechanically: hook → bodies → closer.
- 3–8 scenes, default 5. First scene is the hook, last is the closer, everything between is a body.
- Banned as hook (motion too slow to land in the first 2–3s):
chatWhatsapp,chatMessenger,appFeed,problemSolution,beforeAfter. - The last scene must use a closer-category template (
ctaLogoorctaMedia), and closer templates may appear only in the final slot. - Diversity: adjacent scenes must come from different template categories and must not use the same template (the
mediacategory is exempt for multiple distinct user-provided assets).