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

    Libs — the vocabulary under the templates

    7 shared libs — curves, text fitting, brand tokens, and the higher-level motion vocabulary custom scenes are written against. Every export, documented with a worked example. Install any of them into your own project; each ships with its source.

    Animation Utils

    animation-utils

    The curve layer. Every value that moves in a VanillaSky video is some function of scene progress, and this is where those functions live.

    9 values · 1 typeused by 24 of 28 templates
    EASE.outExpo
    EASE.outBack
    EASE.inOutCubic
    EASE.anticipate
    EASE.pop
    EASE.editorial
    SPRING_SMOOTH · peak 1.00
    SPRING_SNAPPY · peak 1.11
    SPRING_CRISP · peak 1.25
    SPRING_BOUNCY · peak 1.28

    Sampled from the real functions. Easings run the full 0→1; springs are windowed to the first 30% because that is where they move — all four have settled by roughly t=0.2. Only SPRING_SMOOTH arrives without overshoot; the peak on each plot is how far past 1 it travels before settling.

    When to use it

    Reach for this whenever you need a number to change over a scene — position, opacity, scale, rotation. It is deliberately tiny and has no React dependency, so it also works in a plain render loop. Do not hand-roll easing values next to it; a scene that invents its own curve is the one that looks off next to the others.

    What it exports

    Mapping progress to values

    interpolatestagger

    interpolate maps one range onto another with optional easing and clamping. stagger turns a shared progress into a per-item progress so a list arrives in sequence.

    Curves

    EasingcubicBezier

    Easing carries the standard in/out/inOut families. cubicBezier builds a custom curve when none of them fit.

    Springs

    springSPRING_SMOOTHSPRING_CRISPSPRING_SNAPPYSPRING_BOUNCYSpringConfig

    Four presets covering the useful range. Prefer a preset over a bespoke damping/stiffness pair — they are what the templates are tuned against.

    Example — a card that slides up and settles

    tsx
    import { interpolate, spring, stagger, Easing, SPRING_CRISP } from "@/vanillasky/react-animations/animation-utils";
    
    function Card({ progress, index, total }: { progress: number; index: number; total: number }) {
      // Each card gets its own 0→1 window carved out of the shared progress.
      const local = stagger(progress, index, total);
    
      // Springs land with a little overshoot; interpolate is linear-with-easing.
      const y = interpolate(spring(local, SPRING_CRISP), [0, 1], [64, 0]);
      const opacity = interpolate(local, [0, 0.35], [0, 1], {
        easing: Easing.out(Easing.cubic),
        extrapolateRight: "clamp",
      });
    
      return <div style={{ transform: `translateY(${y}px)`, opacity }} />;
    }

    Worth knowing

    interpolate extends past its input range by default. Pass extrapolateLeft/extrapolateRight: "clamp" for anything that must not overshoot — opacity above 1 renders fine and then clips oddly in the SVG export path.

    bash
    npx shadcn add @vanillasky/animation-utils

    Copies react-animations/animation-utils.ts into your project.

    Text Utils

    text-utils

    Four small text helpers that exist because video text has constraints web text does not: a fixed frame, no reflow, and an export path that measures differently from the preview.

    4 values · 0 typesused by 16 of 28 templates
    Ship it
    26.0px
    You're the best. Google disagrees.
    13.9px
    If they can't find you, they choose your competitor.
    13.0px

    Same box, same base size — fitTextSize shrinks longer copy to fit. It stops at minScale (half the base size by default), so copy past that still overflows, as the third line shows. That floor is deliberate: shrinking further would produce text nobody can read at arm's length.

    When to use it

    Any time text goes into a container whose width you know. fitTextSize is the one that matters most — it is the difference between a headline that fits and one that clips in the exported MP4.

    What it exports

    Fitting

    fitTextSizegetResponsiveFontSize

    fitTextSize shrinks to fit a known width. getResponsiveFontSize picks a size from the frame's short edge for headline vs subtitle roles.

    Formatting

    formatCompactstripPipe

    formatCompact renders 999999 as "1M", never "1000K". stripPipe removes the | line-break convention for templates that render raw.

    Example — a headline that always fits its card

    tsx
    import { fitTextSize, formatCompact } from "@/vanillasky/scene-templates/text-utils";
    
    const CARD_W = 880;
    
    function StatCard({ label, value }: { label: string; value: number }) {
      // Shrink only as far as it takes — never below half the base size.
      const size = fitTextSize(label, 72, CARD_W, { minScale: 0.5 });
    
      return (
        <div style={{ width: CARD_W }}>
          <div style={{ fontSize: 120 }}>{formatCompact(value)}</div>
          <div style={{ fontSize: size, whiteSpace: "nowrap" }}>{label}</div>
        </div>
      );
    }

    Worth knowing

    Measurement is deterministic arithmetic, not canvas measurement — it runs identically in renderToStaticMarkup and in the browser. That is the point, but it means the estimate is approximate for very narrow or very wide typefaces. The export path rasterizes SVG text ~3-5% wider than the iframe, which is why the fit leaves slack.

    bash
    npx shadcn add @vanillasky/text-utils

    Copies scene-templates/text-utils.ts into your project.

    Tokens

    tokens

    The single derivation path from a video's style + brandKit to the resolved colors, fonts and frame-level treatment every scene renders against.

    24 values · 28 typesused by 28 of 28 templates
    accent#22c55e
    secondary#c9922e
    surface#0a0a14
    surface_elevated#14141d
    content#ffffff
    muted#a7a6b0

    Resolved live from { accent: "#22c55e", secondary: "#c9922e" } — everything else is derived, which is why a scene should read tokens rather than hardcode hex.

    When to use it

    Call resolveTokens(style) once per scene and read everything off the result. This exists because the default brand was previously defined in 19 places with three different answers — if you hardcode #00e5a0 or a font stack anywhere, you have re-created that bug.

    What it exports

    The resolver

    resolveTokensResolvedTokensResolveTokensOptionsTOKEN_DEFAULTSfontStack

    resolveTokens is the entry point. TOKEN_DEFAULTS is the only place canonical fallback values are written down.

    Frame-level look

    STYLE_PRESETSresolvePresetPRESET_IDSDEFAULT_PRESET_IDStylePresetTypeTreatmentBackgroundFamilyPresetTitlePlacement

    A preset moves background family, headline treatment and title placement together — one token, several coherent decisions.

    Tone dials

    DENSITY_SCALESMOTION_SCALESresolveDensityresolveMotionDENSITY_IDSMOTION_IDSStyleDensityStyleMotionDensityScaleMotionScale

    Orthogonal to the preset: density is how full the frame reads, motion is how hard entrances hit. Both default to normal, which is a no-op.

    Color math

    shiftHuelightendarken

    Used to derive a secondary from an accent and to build elevated surfaces. Same functions the resolver itself uses.

    Video config types (from the bundled video-config.ts)

    VideoConfigSceneConfigGlobalStyleBrandKitVideoMetaAudioConfigTimingConfigBeatMarkerBeatDetectionConfigOrientationVideoDimensionsSafeZoneVariableFieldAlternativeTemplateSceneOriginBackgroundStyleGradientConfigPatternParams

    The shape of a video. resolveTokens takes a GlobalStyle, so these ship with it rather than being a separate install.

    Frame geometry & validation

    getDimensionsgetSafeZonescaleSafeZonecreateDefaultSceneresolveFormatvalidateVideoConfigDEFAULT_DENSITY_IDDEFAULT_MOTION_ID

    getDimensions returns 1080×1920 or 1920×1080; getSafeZone the platform-aware insets, which scaleSafeZone multiplies by the density dial.

    Example — reading brand tokens in a scene

    tsx
    import { resolveTokens } from "@/vanillasky/scene-templates/tokens";
    
    function Scene({ style, width, height }: SceneTemplateProps) {
      const { accent, content, muted, font, preset } = resolveTokens(style);
    
      // preset.type is NOT css — it's deltas the text layer applies on top of a
      // computed size/weight: { weightDelta, trackingDeltaEm, sizeScale, ... }.
      const base = { size: 72, weight: 600 };
      const fontSize = base.size * preset.type.sizeScale;
      const fontWeight = Math.min(900, Math.max(100, base.weight + preset.type.weightDelta));
    
      return (
        <div style={{ width, height, fontFamily: font, color: content }}>
          <h1 style={{ color: accent, fontSize, fontWeight, textTransform: preset.type.transform }}>
            Ship it
          </h1>
          <p style={{ color: muted }}>Every value here came from one resolver.</p>
        </div>
      );
    }

    Worth knowing

    The registry item ships video-config.ts alongside tokens.ts, so installing it also gives you VideoConfig, SceneConfig, getDimensions and getSafeZone. That is deliberate — the token types reference them — but it is more surface than the name suggests.

    bash
    npx shadcn add @vanillasky/tokens

    Copies scene-templates/tokens.ts, video-config.ts into your project.

    Motion Stdlib

    motion-stdlib

    A higher-level motion vocabulary for custom (vibecoded) scenes — composed effects rather than raw curves, so a generated scene can say punch or cascade instead of deriving one.

    21 values · 17 typescustom scenes only — no built-in template uses it
    Shipittoday
    npx vanillasky re
    1,220
    42%

    cascade, typewriter, countUp and punch, each driven by the same 0→1 progress a scene gets.

    When to use it

    Inside a custom scene, or in your own project when you want the composed effect rather than the primitive curve. Built-in templates do not use it — they compose animation-utils directly.

    What it exports

    Text

    typewritercascadecountUpTypewriterOptionsTypewriterResultCascadeOptionsCascadeItemCountUpOptionsCountUpResult

    Character reveal, staggered line entrance, and number counting with formatting.

    Emphasis

    punchglowsweepmorphPunchOptionsGlowOptionsSweepDirection

    Momentary attention: a scale punch, a glow pulse, a light sweep, a shape morph.

    Ambient

    driftorbitparticlesburstgrainvignettemeshGradientsoftShadowDriftOptionsOrbitOptionsParticlesOptionsParticleBurstOptionsBurstParticle

    Background life — slow drift, orbiting elements, particle fields, film grain, vignette, animated mesh gradients.

    Timing

    EASEphasestaggerWindowcenterrand01pathDrawEasingFnStaggerWindowOptions

    EASE is a named curve set. phase splits a scene into labelled segments. rand01 is a seeded PRNG returning 0→1 — deterministic, unlike Math.random, which the validator blocks outright.

    Example — a headline that types, then punches

    tsx
    import { typewriter, punch } from "@/vanillasky/vibecode/motion-stdlib";
    
    function Component({ progress, variables }: { progress: number; variables: Record<string, unknown> }) {
      // Note the argument order: text first, then progress.
      const { text, cursorOn } = typewriter(String(variables.texts ?? ""), progress, {
        range: [0, 0.6],   // type over the first 60% of the scene
      });
    
      // A momentary scale bump centred at 80%, returning to 1 either side.
      const scale = punch(progress, { at: 0.8, width: 0.25, amount: 0.12 });
    
      return (
        <div style={{ transform: `scale(${scale})` }}>
          {text}
          {cursorOn && <span>|</span>}
        </div>
      );
    }

    Worth knowing

    Custom scenes run in the /vibeframe sandbox where these are injected as globals — you reference them by name with no import. The import above is for using the lib in your own React project.

    bash
    npx shadcn add @vanillasky/motion-stdlib

    Copies vibecode/motion-stdlib.ts into your project.

    Text Archetypes

    text-archetypes

    The six kinetic-typography lifecycles every headline in a VanillaSky video runs on, plus the timing table and the duration math that decides whether a line fits its scene.

    8 values · 8 typesused by 17 of 28 templates
    Ship it faster
    subtlein 0.5s · out 0.7s
    Ship it faster
    typewriterin 1.2s · out 0.85s
    Ship it faster 
    wordStaggerin 1.4s · out 0.7s
    Ship it faster
    slamin 0.5s · out 0.5s
    Ship it faster
    cinematicin 0.7s · out 0.8s
    it
    heroWordin 0.35s · out 0.25s

    One 4-second scene, one progress value, six lifecycles. The entrance/exit figures come from ARCHETYPE_SPECS; note how heroWord spends almost none of the scene arriving, which is what lets it show one word at a time.

    When to use it

    Reach for renderArchetype when you need text that arrives, holds and leaves on a schedule rather than a single fade. The specs matter as much as the renderer: each archetype declares an entrance, an exit, and a min/max duration, and minDurationFor scales the minimum with the actual copy — a nine-word line genuinely cannot run typewriter in 1.2s. Check canFit before committing copy to a scene length.

    What it exports

    Rendering

    renderArchetypeArchetypeRenderBlockStylePerWordStyle

    renderArchetype(name, progress, scale, text, sceneDuration, phaseScale?) returns either a single block transform or per-word styles, depending on the archetype.

    The vocabulary

    TEXT_ARCHETYPESTextArchetypenormalizeArchetypeARCHETYPE_SPECSArchetypeSpec

    Six names. normalizeArchetype maps a stale or unknown value onto a real one so an old saved config still renders. ARCHETYPE_SPECS carries each one's entrance/exit seconds, allowed canvas and size envelope.

    Fitting copy to time

    minDurationForeffectiveDurationcanFittotalMinDurationForArchetypePlayTextCanvasTextRole

    minDurationFor grows with the text; canFit answers whether a list of plays fits a scene. Use them instead of guessing — this is what stops a scene cutting mid-word.

    Example — checking copy fits, then rendering it

    tsx
    import {
      renderArchetype, minDurationFor, canFit,
    } from "@/vanillasky/scene-templates/text-archetypes";
    
    const headline = "Ship your launch video in one prompt";
    
    // Does this copy physically fit a 3s scene as a typewriter? (It does not —
    // minDurationFor scales with character count.)
    const needs = minDurationFor("typewriter", headline);   // > 3
    const ok = canFit([{ archetype: "typewriter", text: headline }], 3);
    
    function Headline({ progress, scale }: { progress: number; scale: number }) {
      const r = renderArchetype("wordStagger", progress, scale, headline, 4);
      if (r.kind !== "words") return null;
      // Each word carries its own BlockStyle under .style — the block-level
      // opacity/transform sit on the wrapper.
      return (
        <div style={{ opacity: r.blockOpacity, transform: r.blockTransform }}>
          {r.words.map((w, i) => (
            <span key={i} style={{ opacity: w.style.opacity, transform: w.style.transform }}>{w.text} </span>
          ))}
        </div>
      );
    }

    Worth knowing

    ARCHETYPE_SPECS[x].allowedCanvas is not decoration. cinematic and heroWord need an open canvas — running them on a text-dense template produces overlap, which is why the composition rules forbid it rather than the renderer clipping it.

    bash
    npx shadcn add @vanillasky/text-archetypes

    Copies scene-templates/text-archetypes.ts into your project.

    Color Utils

    color-utils

    The scene background renderer — the one function that turns a resolved brand pair into the CSS background a scene actually paints, plus the contrast helpers that decide what color text can sit on it.

    13 values · 18 typesused by 26 of 28 templates
    mesh
    wash
    spotlight

    The same accent/secondary pair through the three families a style preset can pick. Output is a plain CSS background string with no filter, which is what lets the SVG export path rasterize it identically — and it is deterministic per seed + progress, so the same config always paints the same backdrop.

    When to use it

    Call gradientBackground for any full-frame scene backdrop. It is deterministic per progress and emits pure CSS with no filter, which is what lets the same string rasterize identically in the browser preview and the SVG export path. Use autoTextColor / isColorDark rather than assuming white text.

    What it exports

    Backgrounds

    gradientBackgroundBrandGradientOverlayTOP_TEXT_AREA_RATIO

    gradientBackground({ colorA, colorB, progress, sceneDuration, seed, family }) returns a CSS string; family is the preset's mesh / wash / spotlight. BrandGradientOverlay is the React wrapper.

    Contrast

    autoTextColorisColorDarkorderDarkToLightwithOpacity

    autoTextColor picks readable text for a background; orderDarkToLight sorts a pair so the darker end anchors the gradient.

    Video config types (from the bundled video-config.ts)

    VideoConfigSceneConfigGlobalStyleBrandKitVideoMetaAudioConfigTimingConfigBeatMarkerBeatDetectionConfigOrientationVideoDimensionsSafeZoneVariableFieldAlternativeTemplateSceneOriginBackgroundStyleGradientConfigPatternParams

    Same set tokens ships — gradientBackground reads a GlobalStyle, so the shape travels with it.

    Frame geometry & validation

    getDimensionsgetSafeZonescaleSafeZonecreateDefaultSceneresolveFormatvalidateVideoConfig

    Example — a full-frame brand backdrop

    tsx
    import { gradientBackground, autoTextColor } from "@/vanillasky/scene-templates/color-utils";
    import { resolveTokens } from "@/vanillasky/scene-templates/tokens";
    
    function Backdrop({ style, progress, width, height }: SceneTemplateProps) {
      const { accent, secondary, preset } = resolveTokens(style);
    
      const background = gradientBackground({
        colorA: accent,
        colorB: secondary,
        solidBg: style.brandKit?.bg,   // when set, collapses to a flat color
        progress,
        sceneDuration: 4,
        seed: 7,                        // same seed + progress = same pixels
        family: preset.background,      // mesh | wash | spotlight
      });
    
      return (
        <div style={{ width, height, background, color: autoTextColor(accent) }} />
      );
    }

    Worth knowing

    seed is what makes the mesh deterministic — pass a stable per-scene value, never an index that shifts when a scene is inserted, or re-rendering the same config produces a different backdrop.

    bash
    npx shadcn add @vanillasky/color-utils

    Copies scene-templates/color-utils.ts, video-config.ts into your project.

    Background Effect

    background-effect

    The nine global background motions as one progress→transform function — the backgroundEffect vocabulary the chat model picks from.

    2 values · 1 typeused by 13 of 28 templates
    static
    slow-zoom-in
    slow-zoom-out
    ken-burns
    drift
    pulse
    breathe
    slow-tilt
    camera-shake

    Real getBackgroundTransform output on one striped tile — the stripes make the motion legible. The pan direction rotates with the scene index, so consecutive scenes don't all drift the same way. static is the only one that holds still.

    When to use it

    Apply to a full-frame media or gradient layer to give a still asset life. It returns a transform plus a transformOrigin string you spread onto the backdrop element; it never touches the text layer. Templates opt in by declaring usesGlobalBackgroundEffect — setting the variable on one that doesn't is silently ignored, which the tool executor now rejects rather than letting through.

    What it exports

    Everything

    BACKGROUND_EFFECTSgetBackgroundTransformBackgroundTransform

    BACKGROUND_EFFECTS is the nine names — static, slow-zoom-in, slow-zoom-out, ken-burns, drift, pulse, breathe, slow-tilt, camera-shake. getBackgroundTransform(effect, progress, beatIntensity?, sceneIndex?) returns { transform, transformOrigin }.

    Example — a drifting photo backdrop

    tsx
    import { getBackgroundTransform } from "@/vanillasky/scene-templates/background-effect";
    
    function MediaBackdrop({ url, effect, progress, sceneIndex, width, height }) {
      // sceneIndex rotates the pan direction so consecutive scenes don't all
      // drift the same way.
      const { transform, transformOrigin } = getBackgroundTransform(effect, progress, 0, sceneIndex);
    
      return (
        <div style={{ width, height, overflow: "hidden" }}>
          <img src={url} style={{ width: "100%", height: "100%", objectFit: "cover", transform, transformOrigin }} />
        </div>
      );
    }

    Worth knowing

    An undefined effect resolves to slow-zoom-in, not static — a scene that passes nothing still moves. Pass "static" explicitly when you want it still.

    bash
    npx shadcn add @vanillasky/background-effect

    Copies scene-templates/background-effect.ts into your project.

    How the install works

    This is a shadcn registry. Nothing is published to npm and there is no package to depend on — each item is a JSON manifest listing source files, and shadcn add copies those files into your repo for you to own and edit. Map the namespace once:

    components.json
    { "registries": { "@vanillasky": "https://vanillasky.ai/r/{name}.json" } }

    Then add any of the 7 libs above, or any of the 28 scene templates. Those 35 items are the whole registry — the internal scene-element modules are not published as a separate layer. They are template anatomy and advanced sandbox machinery, not an independently supported component system.

    bash
    npx shadcn add @vanillasky/animation-utils   # a lib
    npx shadcn add @vanillasky/bigNumber        # a scene template

    Installing into your app does not change what the vanillasky CLI renders — to change a scene there, eject it as componentSource. See Templates & the registry.