We use anonymous analytics to improve VanillaSky. No personal data is collected. Learn more

    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

    video.json
    {
      "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

    FieldDescription
    idUnique string id for the scene (e.g. “s0”).
    templateIdA camelCase template id from the registry (e.g. media, bigNumber, terminal). Never guess ids — the validator rejects ones it doesn't know.
    variablesPer-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.
    timingRequired object. Leave it {} to play the scene at the template's preferred duration, or pin seconds with fixedDuration.
    textArchetypeOptional text-animation treatment for the scene. One of subtle, typewriter, wordStagger, slam, cinematic, heroWord.
    backgroundEffectOptional background motion for templates that consume it (e.g. slow-zoom-in on a media scene).
    componentSourceCustom 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.

    PresetUse when
    boldThe default. Drifting two-color brand mesh, heavy tight headlines at the top. Launches, hype, product moments.
    editorialCalm vertical wash, lighter and wider-tracked centered headlines. Reviews, thoughtful updates, premium or B2B brands.
    starkSingle 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 tracks lists 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 (ctaLogo or ctaMedia), 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 media category is exempt for multiple distinct user-provided assets).