Doc, Slide and Layer in artboard pixels — array order is z-order, and framework and styleId are stamped once so analytics can attribute performance.
Doc { version: 3, id, name, width, height, palette[], media[],
group?, framework?, styleId?, archived?, slides[], createdAt, updatedAt }
Slide{ id, name, background, gradient?, layers[] }
Layer{ id, name, kind, x, y, w, h, rotation, opacity, visible, locked,
fill, gradient?, stroke, strokeWidth, radius, …kind-specific }LayerKind is text | rect | ellipse | triangle | line | icon | image.
x: 540 is at 540. Not fractions, not percentages.framework and styleId are stamped once at generation and never re-derived. They exist so analytics can attribute performance to how a carousel was built — a post cannot tell you that Problem → Solution outperforms unless something remembered which one it was.
| Thing | Default |
|---|---|
| Text layer | "Type something", sans, 64px, weight 600, line-height 1.2, align left, valign top |
| Icon | glyph star, stroke = fill, strokeWidth 2, fill: "none" |
| Line | h: 8, radius: 4 — forced, overriding whatever height you passed |
| Image | fit: "cover" |
| Slide | background #12161c |
| Doc | 1080×1350, one slide |
uid(prefix) is ${prefix}_${Date.now().toString(36)}${seq.toString(36)} with a module-level counter. Unique per process, not globally — two tabs can collide.
fontStack(id) checks uploaded faces before built-ins and falls back to FONTS[0], so an unknown id silently becomes Sans rather than undefined.