Skip to main content
An animation is a JSON document. You don’t write the JSON by hand: you write a short TypeScript script against the types in @clayzo/animation/authoring, run it, and it writes the file. The types make malformed documents hard to produce, and validation names the node and field when something is still wrong. Agents with the authoring skill do all of this. This page is for reading what they produce, or for writing your own.

A complete script

Save as author.ts and run with npx tsx author.ts. It writes animations/bouncing-dot.json.
author.ts
Render a sheet to see it move:

The parts of a document

A node is only drawn if it’s reachable from rootNodeIds. That is how a node used only as a clip path, a matte source or an effect input stays out of the frame.

Time is in ticks

ticksPerSecond is the document’s time base and 240 is conventional. frameRate is what it plays at. Every inTick, outTick and keyframe tick is in ticks. At 240 ticks per second and 30 fps a frame is 8 ticks, so keyframes off that grid land between frames and never display exactly. Author at the frame rate the piece will play at and snap keyframes to it.

Animatables

Every animated property has one of two shapes:
interpolation is "hold", "linear", "bezier" or "spring". A bezier keyframe needs easing: { x1, y1, x2, y2 }; a spring keyframe needs spring: { mass, stiffness, damping }. Interpolation describes the segment leaving that keyframe, so the last keyframe should be "hold". A track’s first value holds before its first keyframe. Keyframe ids must be unique; derive them from the node id, the property and the index. Colours are { r, g, b, a } with channels from 0 to 1, not 0 to 255.

Nodes

Every node has the same base fields: id, name, visible, inTick, outTick, opacity, blendMode and a full transform with all five of position, anchor, scale, rotation and skew. The transform is position minus anchor, scaled, rotated (rotation.z in degrees, clockwise on screen), then translated. Child transforms compose with their parent’s.

Effects

An effect node names its effect and a parameters array, matched by name. Available effects: blur, drop-shadow, depth-blur, color-matrix, brightness-contrast, glass, refraction, dithering, duotone, pixelation, mesh-gradient and custom-sksl for a shader you write. Effects chain: an effect’s inputId may be another effect. glass and refraction read what is already drawn beneath them, so they must come later in rootNodeIds than what they refract. number and point parameters need minimum and maximum. A parameter name that doesn’t match is not an error; the effect uses its default. The authoring skill carries the full parameter table for every effect.

Things that bite

  • Frames are transparent wherever nothing is drawn. Draw a full-canvas rect first, or exports come out on black.
  • Node opacity below 1 costs an offscreen layer per frame. Animate style.fill.opacity on solid shapes instead, and reserve node opacity for groups that must fade as one.
  • Overlapping children at partial opacity double up unless the group has isolation: true.
  • A composition needs width, height and durationTicks, not only nodes and rootNodeIds.
  • Write the file with writeFileSync. A top-level await fails under tsx in a CommonJS project, with an error that names esbuild rather than your script.
  • Text wrap defaults to wrapping inside the box. Set wrap: "none" on single lines.
  • A custom shader sees device pixels. Its coordinate and its point parameters arrive in device pixels; number parameters arrive as written. A shader that measures a number radius against a point centre only looks right at one render size — a preview, a phone at 3× and an export all differ. Declare a point parameter with value { x: 1, y: 1 } (it arrives as the render scale) and divide the device-pixel inputs by it, so the shader works in document units everywhere.

Validate and stamp, every time

A document’s identity is its revision. clayzo review binds its findings to one, and the players and exporter treat two documents with the same revision as the same document. A script that writes a document without stamping it hands downstream tools a revision that doesn’t match the contents.