Shapes — the cast
Everything on screen is an entity with a name (its first argument). You declare shapes once; the name is how you address them later in the script.
The six primitives
Each line below is the whole call — copy it and tweak the numbers.
| shape | write | draws |
|---|---|---|
| circle | circle(sun, (cx, cy), 90); | a circle, radius 90 — filled + outlined by default |
| rect | rect(box, (cx, cy), 200, 120); | a rectangle — filled + outlined by default |
| line | line(edge, (100, 100), (400, 300)); | a line from point to point (stroke only) |
| arrow | arrow(v, (100, 400), (400, 400)); | a line with an arrowhead at the end (stroke only) |
| dot | dot(p, (cx, cy), 8); | a small filled disc (no outline) — hides crossings |
| text | text(cap, (cx, 640), "hello"); | a text label anchored at a point |
Plus composites: polygon / sector / annulus / pie (filled by default),
arc / brace / ellipse (stroke only), and boolean regions
(union / intersect / …).
▶ See it play: SHAPECRAFT — fill vs
outlined, the cast, palette → become, spin-wound morph, a disc that
splits into four colours → reunites → moves, and two-colour booleans.
Ordinary text is portable and shaped automatically
text, say, captions, labels, and kit-generated words use Manic’s embedded
font engine. No machine font is consulted, and there is no font-selection DSL:
choose only the semantic mono, bold, or display look. Manic handles
fallback, ligatures, combining marks, bidirectional order, wrapping, reveal,
rotation, glow, camera zoom, and recording from one shaped layout.
Arabic and Devanagari are bundled script examples, so these work unchanged in preview, backend recording, and the browser build:
text(arabic, (cx, 300), "التعلّم يجعل الأفكار واضحة");
text(hindi, (cx, 420), "ज्ञान से प्रकाश मिलता है");
Use equation or inline $…$ for mathematical typesetting. Run manic check
before rendering; an unsupported Unicode cluster names the entity and code
point instead of silently drawing a replacement box. Colour emoji remains
intentionally unsupported until colour-font rendering is portable.
Plus a few composite helpers built from those: polygon, arc/sector, brace/
bracelabel, caption (a row of words), and support(id, (cx,cy), [len], ["dir"])
— the hatched wall / ceiling / floor for mechanics & textbook diagrams ("dir" is
the open side: "down" ceiling, "up" floor, "left"/"right" walls).
Two more helpers cover scenes that would otherwise need dozens of manual dots and endpoints:
circle(glass, (400,300), 100);
particles(bubbles, glass, 24, 5, 7); // default random layout
rect(tank, (750,300), 180, 160);
particles(samples, tank, 24, 4, 11, "grid");
link(pipe, glass, tank, 35); // 35 px of curve; 0 is straight
circle(orbit, (980,300), 90);
particles(ticks, orbit, 24, 4, 13, "ring");
particles works inside circles and rectangles. Its bare id addresses every
child dot, so hidden(bubbles) and recolor(bubbles, fg) affect the whole
group. Use the optional "grid" layout for an ordered rectangular state, then
arrange(samples, tank, "random", 2, smooth) to move the same dots into a
typical random state. arrange may target a larger container for expansion and
can return to "grid" exactly. A circle plus "ring" gives the particles an
ordered radial endpoint. link meets shapes at their boundaries and stays
attached when either endpoint moves.
Points are (x, y) in pixels, origin top-left, y increasing downward. Use
cx, cy, w, h to stay canvas-independent.
For display mathematics, equation accepts LaTeX in backticks. Standard
\textcolor can emphasize individual terms with Manic’s semantic palette; the
colors automatically follow the selected template:
equation(step, (cx, 620),
`\textcolor{magenta}{\mathrm{slope}}=
\frac{\textcolor{cyan}{\mathrm{rise}}}
{\textcolor{gold}{\mathrm{run}}}`, 40);
Leave a term uncolored to use the template foreground. A single-color equation
can still be styled as one entity with color and animated with recolor.
For a derivation, keep that same equation id and animate only the change:
rewrite(step, `\mathrm{slope}=\frac{\mathrm{rise}}{\mathrm{run}}`, 0.9, smooth);
rewrite(step, `\mathrm{slope}=\frac{x}{1}`, 0.9, smooth);
Manic matches the rendered LaTeX parts; it does not perform algebra. This makes the verb generic across algebra, calculus, physics, sums, fractions, radicals, and creator scenes while keeping each settled formula exact.
// the six primitive shapes, drawn on together.
title("Shapes");
canvas("16:9");
text(t, (cx, 90), "six primitives"); color(t, cyan); size(t, 32); hidden(t);
circle(c, (240, 380), 80); color(c, cyan); stroke(c, 4); untraced(c);
rect(r, (470, 380), 150, 150); color(r, magenta); stroke(r, 4); untraced(r);
line(l, (640, 300), (820, 460)); color(l, lime); stroke(l, 4); untraced(l);
arrow(a, (900, 460), (1040, 300)); color(a, cyan); stroke(a, 4); untraced(a);
dot(d, (1110, 380), 12); color(d, magenta); hidden(d);
text(lbl, (640, 620), "circle · rect · line · arrow · dot · text");
color(lbl, dim); size(lbl, 24); hidden(lbl);
show(t, 0.5);
par { draw(c); draw(r); draw(l); draw(a); show(d); }
show(lbl, 0.5);
wait(1.2);
▶ See it play:
Modifiers — style a shape at t = 0
A shape starts plain. Modifiers change how it looks before the animation begins. They take the entity name first, then a value:
| modifier | effect | example |
|---|---|---|
color(id, c) | fill / stroke colour | color(sun, cyan); |
stroke(id, w) | line thickness | stroke(sun, 4); |
dashed(id, [dash], [gap]) | dashed path or outlined circle (defaults 16/10 px) | dashed(mark, 4, 3); |
gradient(id, c1, c2, ..., [mode]) | multi-stop gradient: along a path stroke, a linear (angle°) / radial fill, or a computed "speed"/"curvature" quantity on strokes | gradient(wave, blue, cyan, gold, 270); |
size(id, n) | text size | size(cap, 30); |
glow(id, n) | neon halo strength | glow(sun, 8); |
opacity(id, 0..1, [layer]) | transparency; add fill or stroke to set just that layer, independent of the other | opacity(box, 0.4, fill); |
filled(id) / outlined(id) | solid (fill on, rim off) / hollow (rim on, fill off) | outlined(box); |
outline(id, c) | set the rim colour (and turn the rim on) — does not drop the fill | outline(box, gold); |
hue(id, deg) | colour by an angle (0–360) — per-entity colour in loops | hue(seg, 200); |
z(id, n) | draw order (higher = on top) | z(box, 5); |
sticky(id) | pin to the screen so it stays put through a cam/zoom (a HUD) | sticky(caption); |
Textbook diagrams: prefer
outlined(or a thinarc) for angle marks and “regions of interest.” Afilleddisc or sector sitting on top of axes and construction lines hides what’s underneath — even at low opacity. Reserve solid fills for backdrop wells behind the diagram, not overlays.
circledefaults to filled + outlined, with a soft slate rim (#9AA6C9) that reads cleanly over axes. For math constructions always pairoutlined(id)—outline(id, color)alone only recolors the rim and leaves the fill on. Contact points that land on crossings should be open / dashed rings (outlined+dashed), notdot— a solid marker paints over the intersection you are trying to show.
A shape owns its fill and its rim separately.
color(id, c)sets the fill,outline(id, c)sets the rim, andopacitycan target one layer:opacity(id, 0.4, fill)gives a translucent interior under a crisp rim — a filled shape you can still see the construction through — whileopacity(id, v, stroke)fades just the rim. On a hollow shape (outlined) the fill is off, so colour the visible ring withoutline, notcolor. (Paths —line,arrow,ellipse— are stroke-only:colorsets their stroke and there is no fill layer.)
And two that decide how a shape first appears:
| modifier | pairs with | gives |
|---|---|---|
hidden(id) | show(id) | a fade-in |
untraced(id) | draw(id) | a draw-on (pen tracing the outline) |
Colours are a fixed palette:
fg,void,cyan,magenta,lime,gold,red,orange,blue,teal,violet,coral,indigo,mint,dim,panel. For a computed colour (say, one per item in a loop) usehue(id, degrees). More in Colour & style.
Naming things in a loop
When you make many shapes with a for loop, give each a unique name with
interpolation — {expr} glued to the name:
for i in 0..5 {
dot(p{i}, (200 + i*180, cy), 8); // p0, p1, p2, p3, p4
}
That’s your cast. Now let’s make it move → Verbs.