Skip to content

Scene format

A vexy-stax scene is a JSON object that describes a deck: the slide images, the camera, the transition, captions and colours. The same file works in vexy-stax-js (the three.js renderer in the browser) and in vexy-stax-py, the Python package with its own render engines. Scene files usually end in .scene.json.

parseScene checks every scene strictly: unknown keys, wrong types, values out of range and unknown enum values are errors, not warnings. See ESM API.

The JSON Schema is schema/vexy-stax-scene.schema.json in the repository and in the npm package.

Minimal example

{
  "version": 1,
  "slides": [
    { "src": "layer-0.png" },
    { "src": "layer-1.png" },
    { "src": "https://example.com/layer-2.png" }
  ]
}

version and slides are the only required fields. Everything else fills sensible defaults.

Top-level fields

Field Type Default Description
version 1 — Required. Must be the integer 1.
slides Slide[] — Required. Ordered back-to-front: index 0 is farthest from camera. At least one slide.
view "expanded" \| "compact" "expanded" Initial view to render.
size { width, height } { width: 1920, height: 1080 } Plate pixel dimensions (controls the internal plate aspect ratio).
background string (hex color) "#ffffff" Canvas background color.
captions boolean true Global captions toggle. false removes all caption plates and lowers slide plates to the floor.
camera Camera see below Camera / spacing settings.
transition Transition | null null Animation settings. Needed by createStax with mode: "playable", and by transition() and toVideo() when no kind is passed.
video Video see below Video export framing settings (overrides transition fps, adds holds).
floor Floor see below Reflective floor plane.
edge Edge { width: 0, color: "#f2f2f2" } Slide-plate border.
caption_defaults CaptionStyle inherited from edge Default style for all captions.
caption_fade CaptionFade see below Caption fade-in timing during transitions.
juicy boolean false Per-channel colour match in the Python renderer; the browser renderer ignores it.
$schema string — Optional, any string. The schema's $id is https://vexy.art/schema/vexy-stax-scene.schema.json; that address is an identifier and does not serve the file, so point your editor at the schema file itself.

Slide

{
  "src": "layer-0.png",
  "gap": 960,
  "opacity": { "expanded": 1.0, "compact": 0.6 },
  "caption": {
    "text": "Halftone fill: Dots",
    "show_in": "expanded",
    "style": { "color": "#222222", "size": 48 }
  }
}
Field Type Default Description
src string — Required. Path relative to the scene file, an absolute http(s) URL, or a data: URI. data: URIs carry base64-encoded image bytes inline — no network request, no URL resolution.
gap number \| null null Per-slide gap override (scene-point units). null inherits camera.gap.
opacity number \| { expanded, compact } 1.0 Slide opacity. A scalar stays constant; an object is interpolated during transitions.
caption Caption | null null Optional caption plate below this slide.

Camera

Controls spacing and the camera position for each view.

Field Type Default Description
gap number 1920 Points between adjacent plates in the expanded view.
distance number \| string "100%" Camera distance: an absolute scene-point value, or a viewport-fit percentage ("90%" = the deck fills 90% of the frame width).
angle number 60 Azimuth degrees (horizontal orbit) for the expanded view. 0 = head-on.
elevation number 0 Degrees above the horizon for the expanded view.
fov number 39.6 Camera vertical field of view in degrees.

Transition

{
  "kind": "expand_collapse",
  "duration": 3.0,
  "wait": 1.0,
  "fps": 30,
  "easing": "easeInOutCubic"
}
Field Type Default Description
kind string — Required when transition is present. One of "expand", "collapse", "expand_collapse", "collapse_expand".
duration number 3.0 Seconds per leg (e.g. one-way for expand; per-leg for expand_collapse).
wait number 1.0 Hold seconds at the far-end endpoint between legs.
fps integer 30 Frames per second for toVideo (overridden by video.fps when set).
easing string "easeInOutCubic" Easing function: "linear", "easeInOutCubic", "easeOutCubic", or "easeInCubic".

Video

Settings for video output. The browser's toVideo() reads fps, first_hold and last_hold; it records at the canvas size in real time, so it does not use frames, width or height; those are for the Python renderer.

Field Type Default Description
fps integer transition.fps Output frames per second (overrides transition.fps).
first_hold integer 10 Held copies of the start frame (still intro before the animation).
last_hold integer 10 Held copies of the end frame (still outro after the animation).
frames integer round(duration × fps) Transition frames per leg; omit to derive from timing.
width integer size.width Output video width in pixels.
height integer size.height Output video height in pixels.

Floor

{ "color": "#f5f5f5", "opacity": 0.05, "reflectivity": 0.15 }
Field Type Default Description
color string "#ffffff" Floor plane color.
opacity number 0.0 Floor visibility (0 = invisible pane, faint reflections only).
reflectivity number 0.1 Reflection strength [0,1].

Edge

The visible border drawn around each slide plate.

Field Type Default Description
width number 0.0 Border thickness as a fraction of plate height. 0 disables the border (and caption-plate borders).
color string "#f2f2f2" Border color. Also the default fill_color and border_color for caption plates (overridable per caption).

Caption

{
  "text": "Halftone fill: Dots",
  "show_in": "expanded",
  "style": {
    "size": 48,
    "color": "#222222",
    "font": "sans-serif",
    "fill_color": "#ffffff",
    "border_color": "#e0e0e0"
  }
}
Field Type Default Description
text string — Required. Caption text.
show_in "expanded" \| "compact" \| "both" \| "none" "expanded" Which view(s) display this caption plate.
style CaptionStyle from caption_defaults Per-caption style overrides.

CaptionStyle

Used in caption.style and caption_defaults.

Field Type Description
size number Font size in scene-point units (1 em).
color string Text color (hex).
font string Font family name or path to a TrueType / OpenType font file. Falls back to system-ui, sans-serif.
fill_color string Caption plate fill color. Defaults to edge.color.
border_color string Caption plate border color. Defaults to edge.color.

CaptionFade

Controls how captions fade in during a transition (back-to-front stagger).

Field Type Default Description
window number 0.9 Fraction of the morph [0,1] (from the end) over which captions fade in.
stagger number 0.3 Back→front succession spread as a fraction of the fade window.
stagger_frames integer — Per-caption step in transition frames (overrides stagger when set).

Full example

{
  "$schema": "https://vexy.art/schema/vexy-stax-scene.schema.json",
  "version": 1,
  "view": "compact",
  "size": { "width": 1920, "height": 1080 },
  "background": "#f8f8f8",
  "captions": true,
  "camera": {
    "gap": 960,
    "distance": "90%",
    "angle": 55,
    "fov": 39.6
  },
  "transition": {
    "kind": "expand_collapse",
    "duration": 3.0,
    "wait": 1.0,
    "fps": 30,
    "easing": "easeInOutCubic"
  },
  "video": {
    "first_hold": 10,
    "last_hold": 10
  },
  "floor": { "color": "#ffffff", "opacity": 0.0, "reflectivity": 0.1 },
  "edge": { "width": 0.0, "color": "#f2f2f2" },
  "caption_defaults": { "size": 48, "color": "#333333", "font": "sans-serif" },
  "caption_fade": { "window": 0.9, "stagger": 0.3 },
  "slides": [
    {
      "src": "layer-0.png",
      "opacity": { "expanded": 1.0, "compact": 0.5 },
      "caption": { "text": "Background layer", "show_in": "expanded" }
    },
    {
      "src": "https://example.com/layer-1.png",
      "gap": 480,
      "caption": {
        "text": "Remote image — served with CORS",
        "show_in": "both",
        "style": { "color": "#0055aa" }
      }
    },
    {
      "src": "data:image/png;base64,iVBORw0KGgo...",
      "caption": { "text": "Inline image — base64 data URI", "show_in": "expanded" }
    }
  ]
}