Core Concepts
The Frame Model
Section titled “The Frame Model”Elucim animations are frame-based, not time-based. Every animation is defined in terms of frames:
Convert between frames and seconds:
- frames = seconds × fps → 2 seconds at 30fps = 60 frames
- seconds = frames / fps → 90 frames at 30fps = 3 seconds
The Coordinate System
Section titled “The Coordinate System”Elucim uses SVG coordinates:
- Origin
(0, 0)is the top-left corner - X increases rightward
- Y increases downward
For math primitives like <Axes>, you specify an origin point that becomes the mathematical (0,0), and a scale factor that maps math units to pixels.
Composition Model
Section titled “Composition Model”Normalized Elucim documents have three layers of composition:
1. Primitives
Section titled “1. Primitives”The visual elements: circle, line, bezierCurve, rect, text, polygon, axes, functionPlot, vector, vectorField, matrix, graph, latex, barChart.
Elements are stored by stable ID in elements. Layout metadata such as rotation, scale, and translate lives in each element’s layout object, while render-specific settings live in props. Sibling order in scene.children and group children arrays controls stacking.
2. Timelines
Section titled “2. Timelines”Timelines animate element properties with explicit tracks and keyframes. Use timelines instead of React wrapper components for JSON/YAML documents.
3. State Machines
Section titled “3. State Machines”State machines choose which timeline is active and how playback moves through Entry, states, transitions, and Exit. They can start automatically, wait for clicks/keys, reset, or auto-advance when a timeline completes.
Composition Example
Section titled “Composition Example”import { DslRenderer, type ElucimDocument } from '@elucim/dsl';
const doc: ElucimDocument = { version: '2.0', scene: { type: 'player', width: 800, height: 600, children: ['axes', 'curve', 'label'] }, elements: { axes: { id: 'axes', type: 'axes', props: { type: 'axes', origin: [400, 300], xRange: [-5, 5], yRange: [-3, 3], scale: 50 } }, curve: { id: 'curve', type: 'functionPlot', props: { type: 'functionPlot', expression: 'sin(x)', domain: [-5, 5], origin: [400, 300], scale: 50, stroke: '$accent', opacity: 0 } }, label: { id: 'label', type: 'latex', props: { type: 'latex', expression: 'f(x) = \\sin(x)', x: 600, y: 100, fontSize: 20, opacity: 0 } }, }, timelines: { intro: { id: 'intro', duration: 65, tracks: [ { target: 'curve', property: 'opacity', keyframes: [{ frame: 20, value: 0 }, { frame: 40, value: 1 }] }, { target: 'label', property: 'opacity', keyframes: [{ frame: 50, value: 0 }, { frame: 65, value: 1 }] }, ], }, }, defaultStateMachine: 'main', stateMachines: { main: { id: 'main', entry: 'intro', states: { intro: { timeline: 'intro' } }, transitions: [{ id: 'entry-start', from: 'entry', to: 'intro', trigger: 'onStart' }], }, },};
export function SineScene() { return <DslRenderer dsl={doc} />;}If you are hand-authoring React-only components, two core hooks let you create custom animated components:
useCurrentFrame()— Returns the current frame number for low-level React playbackinterpolate(frame, inputRange, outputRange, options?)— Maps a frame to a value with easing
function PulsingDot() { const frame = useCurrentFrame(); const scale = interpolate(frame, [0, 30, 60], [1, 1.5, 1]); const opacity = interpolate(frame, [0, 15], [0, 1]);
return ( <circle cx={250} cy={250} r={20 * scale} fill="#6c5ce7" opacity={opacity} /> );}Next Steps
Section titled “Next Steps”- Browse Primitives for all shape types
- Read Transforms & Composition for rotation, scale, Object order, and grouping
- Learn about Agent Documents for normalized timelines, state machines, and agent-friendly commands
- See Advanced > Player for lower-level React playback APIs