Skip to content

Transforms & Composition

Every normalized Elucim element separates what it draws (props) from how it is placed (layout). This keeps static composition inspectable and lets timelines/state machines own motion.

shared layoutlast child paints on top
0.00s / 3.97s · F0

Every element has three main categories:

CategoryControlsExamples
Primitive propsWhat to drawcx, cy, r, fill, stroke
LayoutWhere / howrotation, scale, translate
TimelinesWhen values changeopacity, translate, scale, rotate, fill, stroke tracks

These compose on a single normalized element:

{
id: 'orbit',
type: 'circle',
layout: { rotation: 45, translate: [10, 0] },
props: { type: 'circle', cx: 200, cy: 150, r: 50, stroke: '$accent', fill: 'none' },
}

All normalized elements can use these spatial fields in layout.

Rotation in degrees. By default the element rotates around its own center.

{ layout: { rotation: 30 }, props: { type: 'rect', x: 100, y: 50, width: 120, height: 80, stroke: '$accent', fill: 'none' } }

Override the pivot point with an [x, y] coordinate:

{
layout: { rotation: 45, rotationOrigin: [100, 50] },
props: { type: 'rect', x: 100, y: 50, width: 120, height: 80, stroke: '$accent', fill: 'none' },
}

A uniform number or an [sx, sy] pair. Values are unitless multipliers (1 = normal size).

{ layout: { scale: 1.5 }, props: { type: 'circle', cx: 200, cy: 150, r: 40, stroke: '$secondary', fill: 'none' } }
{ layout: { scale: [2, 0.5] }, props: { type: 'circle', cx: 200, cy: 150, r: 40, stroke: '$success', fill: 'none' } }

An [dx, dy] pixel offset applied before rotation and scale:

{ layout: { translate: [50, 20] }, props: { type: 'circle', cx: 100, cy: 100, r: 30, stroke: '$warning', fill: 'none' } }

SVG follows the painter’s model: siblings declared later paint on top of earlier siblings. In normalized documents, that means order in scene.children and each group element’s children array is the stacking order.

scene: { type: 'player', width: 800, height: 600, children: ['card', 'circle'] },
elements: {
card: { id: 'card', type: 'rect', props: { type: 'rect', x: 100, y: 60, width: 100, height: 80, fill: '$success' } },
circle: { id: 'circle', type: 'circle', props: { type: 'circle', cx: 130, cy: 100, r: 50, fill: '$accent' } },
}

Key rules:

  • Later siblings render later (on top)
  • Root stacking is controlled by scene.children
  • Group stacking is controlled by that group’s children
  • Move Objects up/down in the editor Objects panel, or use agent order commands, to change stacking

group is a container that applies layout transforms to all its children as a single unit.

elements: {
card: { id: 'card', type: 'group', layout: { rotation: 15, translate: [50, 0] }, children: ['box', 'dot'], props: { type: 'group' } },
box: { id: 'box', type: 'rect', parentId: 'card', props: { type: 'rect', x: 100, y: 50, width: 200, height: 150, stroke: '$accent', fill: 'none' } },
dot: { id: 'dot', type: 'circle', parentId: 'card', props: { type: 'circle', cx: 200, cy: 125, r: 40, stroke: '$secondary', fill: 'none' } },
}

What this does:

  • Rotate a group → rotates everything inside
  • Groups can nest — transforms compose through the hierarchy
  • Groups stack their own children by order — each group creates its own stacking context

The spatial fields described above are static. For animated transforms, create timeline tracks over translate, scale, or rotate:

timelines: {
spin: {
id: 'spin',
duration: 60,
tracks: [
{ target: 'orbit', property: 'rotate', keyframes: [{ frame: 0, value: 0 }, { frame: 60, value: 360 }] },
],
},
}

Use layout for initial positioning and timelines for motion.