shape#
A shape draws geometry in one of two representations, chosen by whether it carries paths. Inherits common fields.
- Primitive —
rectangle/ellipsewith optional rounded corners, gradient fill, and stroke. A GPU SDF: resolution-independent and cheap. - Path — arbitrary vector geometry via
paths: keyframeabledmorphing, per-sub-path fill/stroke, and stroke trim/draw-on. Rasterized via Canvas2DPath2D, so resolution is bound byview_box. (Absorbs the former standalonesvgelement.)
When paths is present, the primitive fields are ignored.
interface ShapeElement extends BaseElement { type: 'shape'; // Primitive form (ignored when `paths` is present): shape?: 'rectangle' | 'ellipse'; fill_color?: string; gradient?: LinearGradient | RadialGradient; stroke_color?: string; stroke_width?: number; border_radius?: number; // Path form: view_box?: [number, number, number, number]; gradients?: PathGradient[]; paths?: PathDef[]; }
Primitive form#
Geometry#
| Field | Type | Default | Description |
|---|---|---|---|
shape | 'rectangle' | 'ellipse' | 'rectangle' | The primitive. A circle is an ellipse with equal width/height. |
border_radius | number | 0 | Corner radius in pixels (rectangles). Clamped to half the shorter side. Animatable (see note below). |
fill_color, stroke_color, stroke_width, and border_radius are animatable via keyframe_animations (e.g. { property: "border_radius", keyframes: [...] }). For animating the geometry shape itself (one shape morphing into another), use the path form's keyframeable d.
Fill#
| Field | Type | Default | Description |
|---|---|---|---|
fill_color | string (hex) | '#ffffff' | Solid fill. Ignored when gradient is set. |
gradient | LinearGradient | RadialGradient | — | Gradient fill. Overrides fill_color. Up to 4 stops honored in v1. |
Linear gradient#
interface LinearGradient { type: 'linear'; angle?: number; stops: GradientStop[]; } interface GradientStop { offset: number; // 0..1 color: string; // hex }
| Field | Type | Default | Description |
|---|---|---|---|
angle | number | 0 | Direction in degrees. 0° = left→right, 90° = top→bottom, 180° = right→left, 270° = bottom→top. |
stops | GradientStop[] | required | 2–4 stops. offset is 0..1 along the gradient line. |
Radial gradient#
interface RadialGradient { type: 'radial'; cx?: number; cy?: number; radius?: number; stops: GradientStop[]; }
| Field | Type | Default | Description |
|---|---|---|---|
cx | number | 0.5 | Center X as a fraction of the shape's box. |
cy | number | 0.5 | Center Y as a fraction of the shape's box. |
radius | number | 0.5 | Outer radius as a fraction of the shape's box. |
stops | GradientStop[] | required | 2–4 stops. offset: 0 is the center; offset: 1 is the outer radius. |
Stroke#
| Field | Type | Default | Description |
|---|---|---|---|
stroke_color | string (hex) | — | Outline color. When unset, no outline. |
stroke_width | number | 0 | Outline width in pixels. |
Path form#
Set paths to draw arbitrary vector geometry — a restricted SVG-path subset rendered via Canvas2D's Path2D. Designed for motion-graphics use cases — logos, icons, animated stroke evolution, shape-to-shape morphing — without shipping a full SVG engine.
| Field | Type | Default | Description |
|---|---|---|---|
view_box | [x, y, w, h] | [0, 0, 100, 100] | Path coordinates are in viewBox units; the runtime scales the viewBox to the element's width × height. |
gradients | PathGradient[] | [] | Linear gradients addressable by id from paths[].fill / stroke via "url(#id)". |
paths | PathDef[] | — | One or more sub-paths. Drawing order: first renders first (back), last on top. |
interface PathDef { d: string | Keyframe[]; fill?: string; stroke?: string; stroke_width?: number; stroke_progress?: number | Keyframe[]; trim_start?: number | Keyframe[]; trim_end?: number | Keyframe[]; trim_offset?: number | Keyframe[]; clip_path?: string; stroke_linecap?: 'butt' | 'round' | 'square'; stroke_linejoin?: 'miter' | 'round' | 'bevel'; opacity?: number; }
| Field | Type | Default | Description |
|---|---|---|---|
d | string | Keyframe[] | required | SVG path data ("M x y L x y ..."). As keyframes of d-strings, the path morphs (see below). |
fill | string | — | Hex color ("#ff0000") or "url(#gradient-id)". |
stroke | string | — | Hex color or "url(#gradient-id)". |
stroke_width | number | 1 | Width in viewBox units. |
stroke_progress | number | Keyframe[] | 1 | Fraction of the stroke to draw, 0..1. Sugar for a trim window [0, progress]; ignored when any trim_* is set. Animatable. |
trim_start / trim_end / trim_offset | number | Keyframe[] | 0 / 1 / 0 | Stroke trim window (fractions of total length). trim_offset rotates the window with wraparound — animated, it's the traveling-dash "snake"; animated trim_end is the draw-on reveal. Stroke only. |
clip_path | string | — | Path data that clips this path. Only pixels inside the clip are visible. |
stroke_linecap | 'butt' | 'round' | 'square' | 'butt' | Endpoint style. |
stroke_linejoin | 'miter' | 'round' | 'bevel' | 'miter' | Vertex style. |
opacity | number | 1 | Per-path opacity, applied to both fill and stroke. |
Path gradients#
interface PathGradient { id: string; type: 'linear'; x1: number; y1: number; x2: number; y2: number; stops: GradientStop[]; }
| Field | Type | Description |
|---|---|---|
id | string | Gradient identifier. Reference with fill: "url(#id)" or stroke: "url(#id)". |
type | 'linear' | Only linear gradients on paths. Radial gradients live on the primitive gradient field. |
x1, y1, x2, y2 | number | Endpoint coordinates in viewBox units. |
stops | GradientStop[] | 2–4 stops. offset is 0..1 along the gradient line. |
Path morphing#
When d is a keyframe array of d-strings, the path morphs between keyframes — provided the pair is compatible: identical command-letter sequences, equal numeric-argument counts, and no arc commands (A/a). Each numeric argument interpolates with the destination keyframe's easing. An incompatible pair snaps at the destination time. No path normalization is performed: export morph targets with matching command structure.
What's not supported#
The path form renders a deliberately narrow subset. Not supported in v1:
- Radial gradients on paths (use the primitive
gradient, or a separate primitiveshape) - Patterns /
<image>references, filters, text, markers, masks, symbols, use-elements clipPathvia SVG syntax — use per-pathclip_path: "M ..."instead
For a full SVG, render to PNG ahead of time and use an image element.
Examples#
Rounded card background (primitive)#
{ "type": "shape", "shape": "rectangle", "x": 960, "y": 540, "width": 1600, "height": 800, "fill_color": "#1e293b", "border_radius": 40 }
Gradient accent (primitive)#
{ "type": "shape", "shape": "ellipse", "x": 400, "y": 300, "width": 300, "height": 300, "gradient": { "type": "radial", "stops": [ { "offset": 0, "color": "#fb923c" }, { "offset": 1, "color": "#7c3aed" } ] } }
Animated stroke evolution (path)#
stroke_progress keyframes let a path draw itself in over time — the classic logo reveal.
{ "type": "shape", "x": 960, "y": 540, "width": 400, "height": 400, "time": 0, "duration": 4, "view_box": [0, 0, 100, 100], "paths": [ { "d": "M 20 50 L 50 80 L 80 30", "stroke": "#facc15", "stroke_width": 4, "stroke_linecap": "round", "stroke_linejoin": "round", "stroke_progress": 0 } ], "keyframe_animations": [ { "property": "paths.0.stroke_progress", "keyframes": [ { "time": 0, "value": 0, "easing": "ease-out-cubic" }, { "time": 1.5, "value": 1 } ] } ] }
Logo with linear gradient (path)#
{ "type": "shape", "x": 960, "y": 540, "width": 300, "height": 300, "view_box": [0, 0, 100, 100], "gradients": [ { "id": "g1", "type": "linear", "x1": 0, "y1": 0, "x2": 100, "y2": 100, "stops": [ { "offset": 0, "color": "#ef4444" }, { "offset": 1, "color": "#facc15" } ] } ], "paths": [ { "d": "M 50 10 L 90 90 L 10 90 Z", "fill": "url(#g1)" } ] }
Notes#
- One texture per element — path-form shapes rasterize all sub-paths into one offscreen canvas and upload a single GPU texture per frame, so many-path graphics are cheap. Primitive shapes are a single SDF quad with no rasterization at all.
- Animatable from the outside —
keyframe_animationscan target any per-path field viapaths.N.fieldname(color, opacity,stroke_progress, trims).