View raw

shape#

A shape draws geometry in one of two representations, chosen by whether it carries paths. Inherits common fields.

  • Primitiverectangle / ellipse with optional rounded corners, gradient fill, and stroke. A GPU SDF: resolution-independent and cheap.
  • Path — arbitrary vector geometry via paths: keyframeable d morphing, per-sub-path fill/stroke, and stroke trim/draw-on. Rasterized via Canvas2D Path2D, so resolution is bound by view_box. (Absorbs the former standalone svg element.)

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#

FieldTypeDefaultDescription
shape'rectangle' | 'ellipse''rectangle'The primitive. A circle is an ellipse with equal width/height.
border_radiusnumber0Corner 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#

FieldTypeDefaultDescription
fill_colorstring (hex)'#ffffff'Solid fill. Ignored when gradient is set.
gradientLinearGradient | RadialGradientGradient 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
}
FieldTypeDefaultDescription
anglenumber0Direction in degrees. = left→right, 90° = top→bottom, 180° = right→left, 270° = bottom→top.
stopsGradientStop[]required2–4 stops. offset is 0..1 along the gradient line.

Radial gradient#

interface RadialGradient {
  type: 'radial';
  cx?: number;
  cy?: number;
  radius?: number;
  stops: GradientStop[];
}
FieldTypeDefaultDescription
cxnumber0.5Center X as a fraction of the shape's box.
cynumber0.5Center Y as a fraction of the shape's box.
radiusnumber0.5Outer radius as a fraction of the shape's box.
stopsGradientStop[]required2–4 stops. offset: 0 is the center; offset: 1 is the outer radius.

Stroke#

FieldTypeDefaultDescription
stroke_colorstring (hex)Outline color. When unset, no outline.
stroke_widthnumber0Outline 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.

FieldTypeDefaultDescription
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.
gradientsPathGradient[][]Linear gradients addressable by id from paths[].fill / stroke via "url(#id)".
pathsPathDef[]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;
}
FieldTypeDefaultDescription
dstring | Keyframe[]requiredSVG path data ("M x y L x y ..."). As keyframes of d-strings, the path morphs (see below).
fillstringHex color ("#ff0000") or "url(#gradient-id)".
strokestringHex color or "url(#gradient-id)".
stroke_widthnumber1Width in viewBox units.
stroke_progressnumber | Keyframe[]1Fraction 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_offsetnumber | Keyframe[]0 / 1 / 0Stroke 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_pathstringPath 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.
opacitynumber1Per-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[];
}
FieldTypeDescription
idstringGradient 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, y2numberEndpoint coordinates in viewBox units.
stopsGradientStop[]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 primitive shape)
  • Patterns / <image> references, filters, text, markers, masks, symbols, use-elements
  • clipPath via SVG syntax — use per-path clip_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 outsidekeyframe_animations can target any per-path field via paths.N.fieldname (color, opacity, stroke_progress, trims).