Ultron Workers

Carousel DSL

The carousel DSL is the contract between the planner and the renderer. The planner emits a CarouselSpec; the renderer turns it into PNGs. The DSL is freeform-only by design: every slide is a positioned grid of elements (text, library components, stat cards) rather than a templated kind. This gives the planner room to produce non-formulaic compositions without exploding the renderer surface.

Updated today

Overview

At a glance
Schema source
cf-workers/marketing-swarm/src/carousel/types.ts
Validator
cf-workers/marketing-swarm/src/carousel/validate.ts
Slide kind
freeform (the only allowed value)
Element kinds
text, library, stat_card
Canvas sizes
1080x1080 (square), 1080x1350 (portrait)
Slide count range
3 to 12 per deck

Freeform-only

Every slide is the same kind. There is no cover slide kind, no body slide kind, no list slide kind.

cf-workers/marketing-swarm/src/carousel/validate.tsts
1for (const [i, slide] of spec.slides.entries()) {
2 if (slide.kind !== "freeform") {
3 issues.push({
4 path: `slides[${i}].kind`,
5 message: "FORBIDDEN - every slide must be 'freeform'",
6 })
7 }
8}

Earlier versions of the DSL had templated slide kinds (cover, body, stat, quote, spotlight, list, logoStrip, compare, cta). The validator now rejects anything that is not freeform. The reasoning is that templated slides forced the planner into a small set of compositions; freeform lets the planner reach for the layout that the brief actually wants. The renderer dispatches to the right component per element, not per slide.

Slide shape

Three required fields: kind, bg, elements.

cf-workers/marketing-swarm/src/carousel/types.tsts
1interface FreeformSlide {
2 kind: "freeform"
3 bg: string // CSS-style hex or rgba
4 elements: SlideElement[] // ordered, z-index from index + explicit z
5}
6
7type SlideElement =
8 | { type: "text"; value: string; pos: Pos; style?: TextStyle; z?: number }
9 | { type: "library"; id: string; pos: Pos; props?: Record<string, unknown>; z?: number }
10 | { type: "stat_card"; value: string; label: string; pos: Pos; accent?: string; z?: number }

Element kinds

Three building blocks. Combine them to compose any slide.

TypePurposeRequired fields
textPlain or styled typography on the slidevalue, pos
libraryA registered visual component from the library (patterns, shaders, marquees, ripple effects, etc.)id, pos
stat_cardNumber + label calloutvalue, label, pos
Tip
A slide should usually have a backdrop element (a library pattern set to pos: "fullbleed" and a negative z so it sits behind everything) plus one to three foreground elements. More than that and the slide reads as cluttered.

Positioning

Every element declares its position in percentage of the canvas.

cf-workers/marketing-swarm/src/carousel/types.tsts
1type Pos =
2 | "fullbleed" // covers the entire canvas
3 | { x: string; y: string; w?: string; h?: string } // CSS-percent strings

Percentages are passed through verbatim to the renderer's CSS layer. { x: "5%", y: "20%", w: "90%" } means anchor the element at 5 percent from the left, 20 percent from the top, and span 90 percent of the canvas width. fullbleed is a shorthand for the typical backdrop position. The planner picks values that align cleanly to a 12-column grid most of the time but is not forced to.

Per-slide budgets

The validator caps character counts per element kind so a slide cannot overflow.

cf-workers/marketing-swarm/src/carousel/validate.tsts
1const BUDGETS = {
2 cover_headline: 60, // text used as a hero title
3 body_paragraph: 180,
4 stat_value: 10, // the number on a stat card
5 stat_label: 40,
6 quote: 240,
7 list_item: 80,
8 cta: 30,
9}
Warning
Budgets are hard limits. The validator rejects any slide that exceeds them. The planner prompt enumerates the budgets, but the model still occasionally tries to overrun on text-heavy briefs. When that happens the pipeline short-circuits with a structured error rather than rendering a clipped slide.

End-to-end example

One slide. One backdrop, one headline, one stat.

example-slide.jsonjson
1{
2 "kind": "freeform",
3 "bg": "#0a0a0a",
4 "elements": [
5 {
6 "type": "library",
7 "id": "magicui/dot-pattern",
8 "pos": "fullbleed",
9 "z": -1,
10 "props": { "cr": 1.2, "width": 28, "height": 28 }
11 },
12 {
13 "type": "text",
14 "value": "Revenue per AI agent",
15 "pos": { "x": "5%", "y": "20%", "w": "90%" },
16 "style": { "size": 64, "weight": 700 }
17 },
18 {
19 "type": "stat_card",
20 "pos": { "x": "10%", "y": "50%" },
21 "value": "$40K→$2K",
22 "label": "Savings",
23 "accent": "#ffcc00"
24 }
25 ]
26}

File map

cf-workers/marketing-swarm/src/carousel
types.tsCarouselSpec, FreeformSlide, SlideElement, Pos
validate.tsfreeform-only check, BUDGETS map, structural rules
plan.tsGemma prompt that emits a CarouselSpec
render-v2
index.tscompile-and-render pipeline

Glossary

DSL
The Carousel DSL. The JSON shape that connects the planner and the renderer.
Freeform
The only allowed slide kind. Each slide is a positioned grid of elements.
Element
One thing on a slide. text, library, or stat_card.
Position
Where an element lives on the canvas. Either fullbleed or percentage-based x/y/w/h.
Backdrop
A library element set fullbleed with a negative z so it sits behind the foreground.
Budget
A character cap on an element kind enforced by the validator.