Generative & recursive
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
string-art-breath
190 straight chords (point i to point 2i) whose envelope is a cardioid caustic — an ‘eye’. It
blooms from nothing, breathes to a crimson climax, then dissolves: a differential link+turn show.
// string-art-breath — a breathing string-art caustic (the "eye"), with an arc.
//
// 190 straight chords join point i to point 2i; their envelope is a cardioid
// caustic — an "eye" — and `link` keeps each string on its two anchors. Winding an
// anchor by an amount proportional to its index (a differential turn, not a rigid
// spin) grows and shrinks the caustic. The show: the eye BLOOMS from nothing,
// BREATHES with a quickening tempo up to a CLIMAX (flushing crimson), settles, then
// DISSOLVES back to nothing — every string dead straight the whole time.
//
// manic examples/string-art-breath.manic
canvas(1000, 1000);
template("paper");
let n = 190;
let cx = 500;
let cy = 500;
let rr = 430;
let pi = 3.14159265;
for i in 0..n {
let a = i * 2 * pi / n;
dot(o{i}, (cx + rr * cos(a), cy + rr * sin(a)), 1); hidden(o{i});
dot(p{i}, (cx + rr * cos(a), cy + rr * sin(a)), 1); hidden(p{i}); // starts unwound -> blank
link(s{i}, o{i}, p{i});
color(s{i}, #101010);
stroke(s{i}, 0.5);
tag(s{i}, strings);
}
// the wordmark, waiting in the wings for the finale
text(word, (cx, cy), "manic"); size(word, 104); color(word, #101010); hidden(word);
par {
// the breath: bloom -> breathe (quickening) -> climax -> settle -> dissolve.
// every turn is on one anchor by an index-proportional amount, so the caustic
// grows and shrinks as one.
for i in 0..n {
let f = i * 360 / n; // full wind: takes point i to point 2i (the eye forms)
let d = f * 0.45; // breath depth
seq {
turn(p{i}, (cx, cy), f, 4.0, out); // BLOOM into the eye
turn(p{i}, (cx, cy), -d, 3.0, smooth); // breathe open
turn(p{i}, (cx, cy), d, 3.0, smooth); // close
turn(p{i}, (cx, cy), -d * 1.2, 2.0, smooth); // deeper, quicker
turn(p{i}, (cx, cy), d * 1.2, 2.0, smooth);
turn(p{i}, (cx, cy), -d * 1.45, 1.4, smooth); // CLIMAX
turn(p{i}, (cx, cy), d * 1.45, 1.4, smooth);
turn(p{i}, (cx, cy), -d, 3.2, smooth); // settle
turn(p{i}, (cx, cy), d, 3.2, smooth);
turn(p{i}, (cx, cy), -f, 4.5, in); // DISSOLVE back to nothing
}
}
// colour drama: an ink eye that flushes crimson through the climax, then cools,
// and finally fades right out so no wire is left for the finale.
seq {
wait(9.0);
recolor(strings, #a80028, 3.0); // blood rushes in as it quickens
recolor(strings, #101010, 6.0); // cools back to ink
wait(6.5);
fade(strings, 3.0); // every string gone by ~27.5s
}
// finale: once the wires are gone, the wordmark rises
seq {
wait(27.5);
show(word, 1.4);
recolor(word, #ff2d95, 0.8); // a brand-magenta beat
pulse(word);
wait(1.4);
}
}
wheel-radial
Sixty hollow rings breathe in a travelling wave while the whole wheel spins steady->fast->slow and the
wordmark cycles the palette — one par composing breathe + eased turn + recolor; a #hex hollow-fill trick.
// wheel-radial — a breathing radial burst that also spins with a tempo arc.
//
// 60 fixed spokes tipped with HOLLOW rings. Two things happen at once, in a
// `par { }` block:
// 1. every ring BREATHES (radius oscillates) with a phase = its position, so
// the size-wave travels around the ring (the reference-clip illusion);
// 2. the whole wheel TURNS about its centre with a steady -> fast -> slow
// tempo (three eased `turn`s), and the wordmark cycles through the palette.
// The breathing period is fixed, so the *speed* change comes from the rotation —
// `breathe` drives scale, `turn` drives position, so they compose cleanly.
//
// Rings are made truly hollow by filling them with the exact paper colour.
//
// manic examples/wheel-radial.manic
canvas(1080, 1080);
template("paper");
let n = 60;
let cx = 540;
let cy = 540;
let rin = 250; // inner radius
let len = 190; // spoke length -> outer radius = rin + len
let pi = 3.14159265;
let lobes = 5; // how many fat arcs travel around at once
// 1) the frame: spokes + hollow rings, all tagged `wheel` so `turn` spins them
for i in 0..n {
let ang = i * 2 * pi / n;
let ix = cx + rin * cos(ang);
let iy = cy + rin * sin(ang);
let ox = cx + (rin + len) * cos(ang);
let oy = cy + (rin + len) * sin(ang);
line(spoke{i}, (ix, iy), (ox, oy));
stroke(spoke{i}, 1.2);
color(spoke{i}, #3a3a4a);
tag(spoke{i}, wheel);
tag(spoke{i}, spokes);
circle(cin{i}, (ix, iy), 5);
stroke(cin{i}, 2);
color(cin{i}, #f5f2e5); // hollow: fill matches the paper background
tag(cin{i}, wheel);
circle(cout{i}, (ox, oy), 12);
stroke(cout{i}, 2.5);
color(cout{i}, #f5f2e5);
tag(cout{i}, wheel);
}
// the centre hub stays still, with the wordmark inside it
circle(hub, (cx, cy), 150);
stroke(hub, 2.5);
color(hub, #f5f2e5);
text(word, (cx, cy), "manic");
size(word, 66);
color(word, #ff2d95);
par {
// rings breathe at once, phase = position -> the wave travels
for i in 0..n {
let ph = lobes * i / n;
breathe(cin{i}, 2.4, 0.85, ph, 18);
breathe(cout{i}, 2.4, 0.92, ph, 18);
}
// the wheel spins: steady, then fast, then easing to a slow stop
seq {
turn(wheel, (cx, cy), 100, 6, linear); // steady
turn(wheel, (cx, cy), 320, 4, in); // accelerate -> fast
turn(wheel, (cx, cy), 150, 8, out); // decelerate -> slow stop
}
// and the wordmark cycles through the palette on the way
seq {
wait(6); recolor(word, #00e6ff, 1.2); recolor(spokes, #00e6ff, 1.6);
wait(4); recolor(word, #7cff6b, 1.2);
wait(3); recolor(word, #ffd166, 1.2);
}
}
wheel-square
The square sibling of wheel-radial on black — hollow SQUARES on a square. It starts DEAD STILL (the travelling breath-wave fakes rotation), then after ~6s really spins counter-clockwise; sized to stay in-frame when spun.
// wheel-square — the square sibling of wheel-radial: a breathing burst whose rings
// sit on a SQUARE, on black.
//
// The reveal: it starts DEAD STILL — only the rings breathe, and because each ring's
// phase = its position, the size-wave travels and FAKES a rotation though nothing
// moves. After ~6s the trick is dropped and the whole burst actually spins
// COUNTER-CLOCKWISE (steady -> fast -> slow), the wordmark cycling colour.
//
// Each spoke's tip is projected onto a square instead of a circle: a ray at angle
// `ang` hits a square of half-width R at distance R / max(|cos|,|sin|).
//
// manic examples/wheel-square.manic
canvas(1080, 1080);
template("mono");
let n = 64;
let cx = 540;
let cy = 540;
let rin = 195; // inner square half-width
let len = 145; // spoke length -> outer square half-width = rin + len
let pi = 3.14159265;
let lobes = 5;
// sized so the corners (at rout*sqrt(2)) stay inside the frame even when spun
// 1) the frame: spokes + hollow SQUARES on a square, tagged `wheel` so `turn` spins them
for i in 0..n {
let ang = i * 2 * pi / n;
let c = cos(ang);
let s = sin(ang);
let ca = abs(c);
let sa = abs(s);
let m = 0.5 * (ca + sa + abs(ca - sa)); // = max(|cos|,|sin|): ray -> square edge
let ix = cx + (rin / m) * c;
let iy = cy + (rin / m) * s;
let ox = cx + ((rin + len) / m) * c;
let oy = cy + ((rin + len) / m) * s;
line(spoke{i}, (ix, iy), (ox, oy));
stroke(spoke{i}, 1.2);
color(spoke{i}, #55607a);
tag(spoke{i}, wheel);
tag(spoke{i}, spokes);
rect(cin{i}, (ix, iy), 9, 9);
stroke(cin{i}, 2);
color(cin{i}, #000000); // hollow: fill matches the black background
tag(cin{i}, wheel);
rect(cout{i}, (ox, oy), 22, 22);
stroke(cout{i}, 2.5);
color(cout{i}, #000000);
tag(cout{i}, wheel);
}
// the centre hub stays still, with the wordmark inside it (a square frame too)
rect(hub, (cx, cy), 300, 300);
stroke(hub, 2.5);
color(hub, #000000);
text(word, (cx, cy), "manic");
size(word, 66);
color(word, #ff2d95);
par {
// rings breathe the WHOLE time, phase = position -> the wave travels
for i in 0..n {
let ph = lobes * i / n;
breathe(cin{i}, 2.4, 0.85, ph, 24);
breathe(cout{i}, 2.4, 0.92, ph, 24);
}
// hold still for the illusion, THEN spin COUNTER-CLOCKWISE: steady -> fast -> slow
seq {
wait(6); // just the breathing fakes rotation
turn(wheel, (cx, cy), -100, 6, linear); // now it really turns: steady
turn(wheel, (cx, cy), -320, 4, in); // accelerate -> fast
turn(wheel, (cx, cy), -150, 8, out); // decelerate -> slow stop
}
// the wordmark cycles through the palette once the spin begins
seq {
wait(6); recolor(word, #00e6ff, 1.2); recolor(spokes, #00e6ff, 1.6);
wait(5); recolor(word, #7cff6b, 1.2);
wait(4); recolor(word, #ffd166, 1.2);
}
}
wheel-duo
Four breathing bursts in a 2x2 on black with HUE’d rainbow spokes, spinning forever: circle & square
rigid (top), and circle & square counter-spinning (bottom) — the rainbow links twist into a spirograph eye.
// wheel-duo — four breathing bursts in a 2x2 grid on black, spinning forever.
//
// top row : normal spin (inner+outer together) — circle | square
// bottom row : COUNTER-spin (outer clockwise, inner counter-clockwise) — circle | square
//
// The spokes are `link`s that follow their two dots, so counter-rotation twists them
// into a spirograph "eye". Each spoke is HUE'd by its angle -> a rainbow wheel; the
// continuous rotation carries the rainbow around and never stops. Every burst
// breathes, holds still (the breath-wave fakes rotation), then keeps spinning.
//
// manic examples/wheel-duo.manic
canvas(1600, 1600);
template("black");
let n = 44;
let rin = 125;
let len = 85; // outer = rin + len = 210
let pi = 3.14159265;
let lobes = 4;
// ---- A: circle, top-left (normal spin) ----
for i in 0..n {
let ang = i * 2 * pi / n;
let ix = 440 + rin * cos(ang); let iy = 440 + rin * sin(ang);
let ox = 440 + (rin + len) * cos(ang); let oy = 440 + (rin + len) * sin(ang);
circle(a_ci{i}, (ix, iy), 5); stroke(a_ci{i}, 1.8); color(a_ci{i}, #000000); tag(a_ci{i}, a_all);
circle(a_co{i}, (ox, oy), 10); stroke(a_co{i}, 2.2); color(a_co{i}, #000000); tag(a_co{i}, a_all);
link(a_sp{i}, a_ci{i}, a_co{i}); hue(a_sp{i}, 360 * i / n); stroke(a_sp{i}, 1.3);
}
// ---- B: square, top-right (normal spin) ----
for i in 0..n {
let ang = i * 2 * pi / n;
let c = cos(ang); let s = sin(ang);
let m = 0.5 * (abs(c) + abs(s) + abs(abs(c) - abs(s))); // max(|cos|,|sin|)
let ix = 1160 + (rin / m) * c; let iy = 440 + (rin / m) * s;
let ox = 1160 + ((rin + len) / m) * c; let oy = 440 + ((rin + len) / m) * s;
rect(b_ci{i}, (ix, iy), 9, 9); stroke(b_ci{i}, 1.8); color(b_ci{i}, #000000); tag(b_ci{i}, b_all);
rect(b_co{i}, (ox, oy), 19, 19); stroke(b_co{i}, 2.2); color(b_co{i}, #000000); tag(b_co{i}, b_all);
link(b_sp{i}, b_ci{i}, b_co{i}); hue(b_sp{i}, 360 * i / n); stroke(b_sp{i}, 1.3);
}
// ---- C: circle, bottom-left (COUNTER: outer cw, inner ccw) ----
for i in 0..n {
let ang = i * 2 * pi / n;
let ix = 440 + rin * cos(ang); let iy = 1160 + rin * sin(ang);
let ox = 440 + (rin + len) * cos(ang); let oy = 1160 + (rin + len) * sin(ang);
circle(c_ci{i}, (ix, iy), 5); stroke(c_ci{i}, 1.8); color(c_ci{i}, #000000); tag(c_ci{i}, c_in);
circle(c_co{i}, (ox, oy), 10); stroke(c_co{i}, 2.2); color(c_co{i}, #000000); tag(c_co{i}, c_out);
link(c_sp{i}, c_ci{i}, c_co{i}); hue(c_sp{i}, 360 * i / n); stroke(c_sp{i}, 1.3);
}
// ---- D: square, bottom-right (COUNTER: outer cw, inner ccw) ----
for i in 0..n {
let ang = i * 2 * pi / n;
let c = cos(ang); let s = sin(ang);
let m = 0.5 * (abs(c) + abs(s) + abs(abs(c) - abs(s)));
let ix = 1160 + (rin / m) * c; let iy = 1160 + (rin / m) * s;
let ox = 1160 + ((rin + len) / m) * c; let oy = 1160 + ((rin + len) / m) * s;
rect(d_ci{i}, (ix, iy), 9, 9); stroke(d_ci{i}, 1.8); color(d_ci{i}, #000000); tag(d_ci{i}, d_in);
rect(d_co{i}, (ox, oy), 19, 19); stroke(d_co{i}, 2.2); color(d_co{i}, #000000); tag(d_co{i}, d_out);
link(d_sp{i}, d_ci{i}, d_co{i}); hue(d_sp{i}, 360 * i / n); stroke(d_sp{i}, 1.3);
}
// labels + centre title
text(title, (800, 800), "manic"); size(title, 62); color(title, #ff2d95);
text(la, (440, 720), "circle"); size(la, 26); color(la, #7f8aa3);
text(lb, (1160, 720), "square"); size(lb, 26); color(lb, #7f8aa3);
text(lc, (440, 1500), "circle counter"); size(lc, 26); color(lc, #7f8aa3);
text(ld, (1160, 1500), "square counter"); size(ld, 26); color(ld, #7f8aa3);
par {
// all four bursts breathe the whole time
for i in 0..n {
let ph = lobes * i / n;
breathe(a_ci{i}, 2.4, 0.85, ph, 38); breathe(a_co{i}, 2.4, 0.92, ph, 38);
breathe(b_ci{i}, 2.4, 0.85, ph, 38); breathe(b_co{i}, 2.4, 0.92, ph, 38);
breathe(c_ci{i}, 2.4, 0.85, ph, 38); breathe(c_co{i}, 2.4, 0.92, ph, 38);
breathe(d_ci{i}, 2.4, 0.85, ph, 38); breathe(d_co{i}, 2.4, 0.92, ph, 38);
}
// hold still, ease in, then spin CONTINUOUSLY (never stops). top: rigid; bottom: counter.
seq { wait(5); turn(a_all, (440, 440), 90, 3, in); turn(a_all, (440, 440), 1800, 30, linear); }
seq { wait(5); turn(b_all, (1160, 440), -90, 3, in); turn(b_all, (1160, 440), -1800, 30, linear); }
seq { wait(5); turn(c_out, (440, 1160), 90, 3, in); turn(c_out, (440, 1160), 1800, 30, linear); }
seq { wait(5); turn(c_in, (440, 1160), -90, 3, in); turn(c_in, (440, 1160), -1800, 30, linear); }
seq { wait(5); turn(d_out, (1160, 1160), 90, 3, in); turn(d_out, (1160, 1160), 1800, 30, linear); }
seq { wait(5); turn(d_in, (1160, 1160), -90, 3, in); turn(d_in, (1160, 1160), -1800, 30, linear); }
}
lsystem-asymptote-curves
Four canonical Asymptote rewriting systems become fitted, continuously drawable Manic paths—including a concave filled boundary and a 9,604-segment carpet curve.
// Four classic deterministic curves from the Asymptote example corpus.
// Each figure is one fitted, traceable Manic entity—even the 9,604-segment curve.
title("Four Rules, Four Infinite-Looking Curves");
canvas("16:9");
template("mono");
watermark(mark, (w*0.105, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.075), "GENERATIVE GEOMETRY · L-SYSTEMS");
text(headline, (cx, h*0.135), "A tiny rewriting rule becomes a continuous path");
text(caption, (cx, h*0.92), "One path per curve · auto-fitted · continuously drawable");
size(kicker, 20); bold(kicker); color(kicker, dim);
size(headline, 34); bold(headline);
size(caption, 20); color(caption, dim);
let left = w*0.275;
let right = w*0.725;
let upper = h*0.37;
let lower = h*0.70;
let cell = h*0.27;
lsystem(sierpinski, (left, upper), cell,
"YF", "X=YF+XF+Y;Y=XF-YF-X",
"angle=60 heading=0 iterations=7");
color(sierpinski, cyan); stroke(sierpinski, 2.5); untraced(sierpinski);
lsystem(gosper, (right, upper), cell,
"FX", "X=X+YF++YF-FX--FXFX-YF+;Y=-FX+YFYF++YF+FX--FX-Y",
"angle=60 heading=0 iterations=4");
color(gosper, magenta); stroke(gosper, 2.5); untraced(gosper);
lsystem(squareCurve, (left, lower), cell,
"F+XF+F+XF", "X=XF-F+F-XF+F+XF-F+F-X",
"angle=90 heading=45 iterations=5 closed=true fill=true");
color(squareCurve, gold); opacity(squareCurve, 0.70); stroke(squareCurve, 2.0); untraced(squareCurve);
lsystem(carpet, (right, lower), cell,
"F+F+F+F", "F=FF+F+F+F+FF",
"angle=90 heading=0 iterations=4");
color(carpet, lime); stroke(carpet, 2.0); untraced(carpet);
text(l1, (left, h*0.205), "SIERPINSKI CURVE · 2,187 SEGMENTS");
text(l2, (right, h*0.205), "PEANO–GOSPER · 2,401 SEGMENTS");
text(l3, (left, h*0.535), "SQUARE CURVE · 5,460 SEGMENTS");
text(l4, (right, h*0.535), "CARPET CURVE · 9,604 SEGMENTS");
size(l1, 18); size(l2, 18); size(l3, 18); size(l4, 18);
bold(l1); bold(l2); bold(l3); bold(l4);
color(l1, cyan); color(l2, magenta); color(l3, gold); color(l4, lime);
hidden(l1); hidden(l2); hidden(l3); hidden(l4);
step("one rule becomes a curve") {
par {
show(l1, 0.35);
draw(sierpinski, 1.8, smooth);
}
}
wait(0.30);
step("change the grammar") {
par {
show(l2, 0.35);
draw(gosper, 1.8, smooth);
}
}
wait(0.30);
step("close and fill the boundary") {
par {
show(l3, 0.35);
draw(squareCurve, 1.8, smooth);
}
}
wait(0.30);
step("thousands of segments stay one path") {
par {
show(l4, 0.35);
draw(carpet, 2.2, smooth);
}
}
wait(1.20);
creator-lsystem-fractal-curve
A creator Short follows one seven-segment rule from a four-edge square to a 9,604-segment space-filling curve, then closes with the Manic CTA.
// Creator story: one seven-segment rewriting rule grows from a square into a
// 9,604-segment space-filling curve. The rule is the story—not implementation.
title("How One Line Learns to Fill Space");
canvas("9:16");
template("mono");
creator(me, "@anish2good name=Manic_Geometry tagline=Rules_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Animate_your_idea safe=clean");
socials(me);
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
endcard(me, "title=Turn_Rules_Into_Stories cta=8gwifi.org/manic");
let u = (w+h-abs(w-h))/1080;
text(kicker, (cx, h*0.14), "MANIC · GENERATIVE GEOMETRY");
text(headline, (cx, h*0.24), "One rule. 9,604 lines.");
text(caption, (cx, h*0.79), "Start with a square.");
text(generation, (cx, h*0.69), "GENERATION 0 · 4 SEGMENTS");
text(rule, (cx, h*0.30), "F → FF + F + F + F + FF");
size(kicker, 20*u); bold(kicker); color(kicker, cyan);
size(headline, 30*u); bold(headline); wrap(headline, w*0.78);
size(caption, 23*u); bold(caption); wrap(caption, w*0.74);
size(generation, 20*u); bold(generation); color(generation, dim);
size(rule, 25*u); bold(rule); color(rule, gold);
let stageSize = (w+h-abs(w-h))*0.28;
let stageY = h*0.49;
lsystem(curve, (cx, stageY), stageSize,
"F+F+F+F", "F=FF+F+F+F+FF",
"angle=90 iterations=0");
color(curve, cyan); stroke(curve, 5);
lsystem(gen1, (cx, stageY), stageSize,
"F+F+F+F", "F=FF+F+F+F+FF",
"angle=90 iterations=1");
color(gen1, cyan); stroke(gen1, 4); hidden(gen1);
lsystem(gen2, (cx, stageY), stageSize,
"F+F+F+F", "F=FF+F+F+F+FF",
"angle=90 iterations=2");
color(gen2, cyan); stroke(gen2, 3.5); hidden(gen2);
lsystem(gen3, (cx, stageY), stageSize,
"F+F+F+F", "F=FF+F+F+F+FF",
"angle=90 iterations=3");
color(gen3, magenta); stroke(gen3, 3); hidden(gen3);
lsystem(finalCurve, (cx, stageY), stageSize,
"F+F+F+F", "F=FF+F+F+F+FF",
"angle=90 iterations=4");
gradient(finalCurve, cyan, magenta, gold);
stroke(finalCurve, 2.2); untraced(finalCurve); hidden(finalCurve);
hidden(kicker); hidden(headline); hidden(rule);
step("ask the impossible question") {
seq {
par {
show(kicker, 0.35);
show(headline, 0.50);
show(rule, 0.50);
show(curve, 0.40);
}
pulse(curve, 0.60);
}
}
wait(0.45);
step("rewrite every forward move") {
seq {
say(caption, "Replace every F with seven smaller forward moves.", 0.45, smooth);
par {
become(curve, gen1, 0.85, smooth);
say(generation, "GENERATION 1 · 28 SEGMENTS", 0.35, smooth);
}
par {
become(curve, gen2, 0.95, smooth);
say(generation, "GENERATION 2 · 196 SEGMENTS", 0.35, smooth);
}
par {
become(curve, gen3, 1.05, smooth);
say(generation, "GENERATION 3 · 1,372 SEGMENTS", 0.35, smooth);
}
}
}
wait(0.50);
step("let the path fill space") {
seq {
par {
fade(curve, 0.35);
say(caption, "Repeat once more. The same rule now draws 9,604 connected segments.", 0.45, smooth);
say(generation, "GENERATION 4 · 9,604 SEGMENTS", 0.35, smooth);
}
show(finalCurve, 0.05);
draw(finalCurve, 3.20, smooth);
pulse(finalCurve, 0.80);
}
}
wait(0.75);
step("the idea is the animation") {
seq {
say(caption, "In Manic, creators describe the rule. The engine makes it move.", 0.45, smooth);
par {
recolor(headline, gold, 0.50);
pulse(finalCurve, 0.85);
}
}
}
wait(1.00);
step("creator call to action") {
par {
fade(kicker, 0.35); fade(headline, 0.35); fade(rule, 0.35);
fade(caption, 0.35); fade(generation, 0.35); fade(finalCurve, 0.45);
fade(me.footer, 0.35);
show(me.endcard, 0.60);
}
}
wait(1.80);
asymptote-tiling-reference
One two-dimensional motif becomes hex rings, a rotated grid, an outward-facing radial
system, and a nested motif-of-motifs—all through the generic repeat foundation.
// The recurring structure behind Asymptote's tiling examples:
// author one motif, then arrange it as a hex field, grid, radial ring, or a
// repeated composition. Every generated tile remains a normal Manic entity.
title("One Motif, Four Tiling Systems");
canvas("16:9");
template("mono");
watermark(mark, (w*0.11, h*0.075), "Made With Manic");
text(kicker, (cx, h*0.07), "GENERATIVE GEOMETRY · REPEAT");
text(headline, (cx, h*0.13), "Build the motif once. Compose the field.");
size(kicker, 19); bold(kicker); color(kicker, dim);
size(headline, 34); bold(headline);
let lx = w*0.27;
let rx = w*0.73;
let uy = h*0.37;
let ly = h*0.73;
// Hex rings: a small two-part diamond becomes a honeycomb field.
polygon(hexBody, (lx,uy-16), (lx+15,uy), (lx,uy+16), (lx-15,uy));
circle(hexCore, (lx,uy), 4);
color(hexBody, cyan); color(hexCore, gold);
tag(hexBody, hexMotif); tag(hexCore, hexMotif);
repeat(hexField, hexMotif, "layout=hex rings=4 spacing=30 rotate=30 scale=0.82");
hidden(hexMotif); untraced(hexField);
// Grid: a deliberately asymmetric motif proves orientation is retained.
line(gridStem, (rx-15,uy+12), (rx+14,uy-12));
circle(gridTip, (rx+14,uy-12), 5);
color(gridStem, magenta); color(gridTip, lime);
stroke(gridStem, 3);
tag(gridStem, gridMotif); tag(gridTip, gridMotif);
repeat(gridField, gridMotif, "layout=grid rows=5 cols=7 gapx=48 gapy=42 rotate=-8");
hidden(gridMotif); untraced(gridField);
// Radial: each arrow-shaped wedge faces away from the common centre.
polygon(ray, (lx,ly-22), (lx+9,ly-5), (lx,ly+4), (lx-9,ly-5));
color(ray, gold); tag(ray, rayMotif);
repeat(sun, rayMotif, "layout=radial count=18 radius=112 face=out rotate=10 scale=0.85");
hidden(rayMotif); untraced(sun);
// Nested composition: repeat a 2x2 micro-pattern as one larger radial motif.
polygon(seed, (rx-7,ly+7), (rx+7,ly+7), (rx,ly-8));
color(seed, cyan);
repeat(micro, seed, "layout=grid rows=2 cols=2 gapx=20 gapy=20 scale=0.70");
repeat(nested, micro, "layout=radial count=10 radius=105 face=out rotate=18 scale=0.82");
hidden(seed); hidden(micro); untraced(nested);
text(l1, (lx,h*0.205), "HEX RINGS · 37 MOTIFS");
text(l2, (rx,h*0.205), "ROTATED GRID · 35 MOTIFS");
text(l3, (lx,h*0.565), "RADIAL · FACE OUT");
text(l4, (rx,h*0.565), "NESTED · MOTIFS OF MOTIFS");
size(l1,17); size(l2,17); size(l3,17); size(l4,17);
bold(l1); bold(l2); bold(l3); bold(l4);
color(l1,cyan); color(l2,magenta); color(l3,gold); color(l4,lime);
step("hexagonal rings") { draw(hexField, 1.40, smooth); }
wait(0.25);
step("rectangular repetition") { draw(gridField, 1.40, smooth); }
wait(0.25);
step("radial orientation") { draw(sun, 1.20, smooth); }
wait(0.25);
step("composition remains reusable") { draw(nested, 1.60, smooth); }
wait(1.30);
creator-one-tile-pattern-story
A creator problem asks how many tiles lie in three complete hexagonal rings. Stable repeat layers, a live total, semantic colour, and LaTeX derive 1+6+12+18 = 37.
// Creator problem: count a hexagonal mosaic without counting 37 tiles one by
// one. `repeat` constructs the exact layers; counters and semantic LaTeX turn
// the geometry into a short visual proof.
title("How Many Tiles Are in Three Hexagonal Rings?");
canvas("9:16");
template("blank");
creator(me, "@anish2good name=Manic_Geometry tagline=Patterns_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Create_without_keyframes safe=clean");
socials(me);
watermark(mark, (w*0.16, h*0.055), "Made With Manic");
endcard(me, "title=Turn_Patterns_Into_Proofs cta=8gwifi.org/manic");
let u = (w+h-abs(w-h))/1080;
let boardY = h*0.43;
let tileR = 19*u;
text(kicker, (cx,h*0.105), "MANIC · VISUAL COUNTING");
text(headline, (cx,h*0.17), "Three rings surround one tile.");
text(question, (cx,h*0.235), "How many tiles are there altogether?");
text(caption, (cx,h*0.77), "Do not count one by one. Count what each ring adds.");
size(kicker, 20*u); bold(kicker); color(kicker, cyan);
size(headline, 31*u); bold(headline); wrap(headline,w*0.80);
size(question, 25*u); bold(question); wrap(question,w*0.78); color(question,gold);
size(caption, 22*u); bold(caption); wrap(caption,w*0.78); color(caption,dim);
// One regular hexagonal tile is the only authored artwork.
polygon(tile,
(cx,boardY-tileR),
(cx+0.866*tileR,boardY-0.5*tileR),
(cx+0.866*tileR,boardY+0.5*tileR),
(cx,boardY+tileR),
(cx-0.866*tileR,boardY+0.5*tileR),
(cx-0.866*tileR,boardY-0.5*tileR));
color(tile, gold); glow(tile, 0.75);
// The question silhouette: all 37 tiles, deliberately subdued.
repeat(questionField, tile,
"layout=hex rings=4 spacing=39 rotate=30 scale=0.90");
color(questionField, dim); opacity(questionField,0.30);
untraced(questionField);
// Declare largest first and smallest last. When all four groups are visible,
// the later cumulative layers cover their shared interior, leaving each newly
// added ring in its own semantic colour.
repeat(layer4, tile,
"layout=hex rings=4 spacing=39 rotate=30 scale=0.90");
color(layer4,lime); hidden(layer4); untraced(layer4);
repeat(layer3, tile,
"layout=hex rings=3 spacing=39 rotate=30 scale=0.90");
color(layer3,magenta); hidden(layer3); untraced(layer3);
repeat(layer2, tile,
"layout=hex rings=2 spacing=39 rotate=30 scale=0.90");
color(layer2,cyan); hidden(layer2); untraced(layer2);
repeat(layer1, tile,
"layout=hex rings=1 spacing=39 rotate=30 scale=0.90");
color(layer1,gold); hidden(layer1); untraced(layer1);
hidden(tile);
counter(total, (cx,h*0.655), 1, 0, "TOTAL ", " TILES");
size(total,25*u); bold(total); color(total,gold); hidden(total);
equation(work, (cx,h*0.70),
`N=\textcolor{gold}{1}+\textcolor{cyan}{6}+\textcolor{magenta}{12}+\textcolor{lime}{18}`,
31*u);
hidden(work);
hidden(kicker); hidden(headline); hidden(question); hidden(caption);
step("pose the mosaic problem") {
par {
show(kicker,0.35);
show(headline,0.50);
show(question,0.45);
show(caption,0.40);
draw(questionField,1.25,smooth);
}
}
wait(1.10);
step("focus on the construction") {
par {
fade(questionField,0.35);
say(caption,"Begin with the single centre tile.",0.40);
cam((cx,boardY),0.45,smooth);
zoom(1.10,0.45,smooth);
show(total,0.35);
}
show(layer1,0.05);
draw(layer1,0.45,smooth);
pulse(layer1,0.55);
}
wait(0.40);
step("the first ring adds six") {
par {
show(layer2,0.05);
to(total,value,7,0.55,smooth);
say(caption,"Ring 1 adds 6 tiles: one on each side.",0.40);
}
stagger(0.055) {
for i in 0..7 { draw(layer2.i{i},0.28,smooth); }
}
}
wait(0.35);
step("the second ring adds twelve") {
par {
show(layer3,0.05);
to(total,value,19,0.65,smooth);
say(caption,"Ring 2 has twice as many positions, so it adds 12.",0.45);
}
stagger(0.035) {
for i in 0..19 { draw(layer3.i{i},0.22,smooth); }
}
}
wait(0.35);
step("the third ring adds eighteen") {
par {
show(layer4,0.05);
to(total,value,37,0.75,smooth);
say(caption,"Ring 3 adds 18 more. Every new ring contributes another six.",0.45);
}
stagger(0.022) {
for i in 0..37 { draw(layer4.i{i},0.18,smooth); }
}
}
wait(0.55);
step("write what the colors counted") {
par {
show(work,0.50);
say(caption,"The colored layers give the sum directly.",0.40);
cam((cx,cy),0.45,smooth);
zoom(1.0,0.45,smooth);
}
}
wait(0.55);
step("recognize the pattern") {
rewrite(work, `N=1+6(1+2+3)`,0.80,smooth);
say(caption,"Factor out six: the ring numbers form a triangular sum.",0.45);
}
wait(0.55);
step("solve") {
par {
rewrite(work, `N=\textcolor{lime}{37}`,0.85,smooth);
say(caption,"So the mosaic contains exactly 37 tiles.",0.45);
pulse(total,0.80);
recolor(headline,gold,0.50);
}
}
wait(1.10);
step("call to action") {
par {
fade(kicker,0.30); fade(headline,0.30); fade(question,0.30);
fade(caption,0.30); fade(total,0.30); fade(work,0.30);
fade(layer1,0.35); fade(layer2,0.35); fade(layer3,0.35); fade(layer4,0.35);
fade(me.footer,0.30); show(me.endcard,0.60);
}
}
wait(1.80);
gun-shot
A pure-imagination SCENE — no physics kit, just storytelling: a gun fires, the camera
flies along with the bullet (cam/zoom), a block drops in out of nowhere, and BOOM —
flash/shake/pulse + a for-loop spark burst. manic as a movie language.
// ============================================================================
// gun-shot.manic — a scene, not a lesson. No physics kit, just imagination.
// ----------------------------------------------------------------------------
// A gun fires · the camera races along with the bullet · a block drops in out
// of nowhere · BOOM. Built entirely from base manic — shapes, `move`, `cam`/
// `zoom` to fly the camera, `flash`/`shake`/`pulse`, and a `for`-loop spark
// burst. This is manic as a storytelling language: dream a scene, write it.
// ============================================================================
title("Gun Shot");
canvas("16:9");
// ---- the world (wide — the camera pans across it) ----
line(ground, (-300, 560), (2400, 560)); color(ground, dim); stroke(ground, 4);
// the gun: barrel + body + grip
rect(barrel, (250, 470), 96, 22); color(barrel, dim); filled(barrel);
rect(body, (206, 478), 52, 42); color(body, dim); filled(body);
polygon(grip, (186, 500), (220, 500), (212, 554), (180, 550), dim);
// the bullet at the muzzle, and a muzzle flash — both waiting
circle(bullet, (302, 470), 12); color(bullet, gold); glow(bullet, 2.2); hidden(bullet);
circle(mflash, (312, 470), 30); color(mflash, gold); glow(mflash, 3.5); hidden(mflash);
// the block — waiting above, off-screen, to drop in ahead
rect(block, (1750, 250), 130, 130); color(block, cyan); filled(block); glow(block, 1.4); hidden(block);
text(boom, (1750, 320), "BOOM!"); size(boom, 96); color(boom, magenta); bold(boom); glow(boom, 2.5); display(boom); hidden(boom);
// a ring of impact sparks around the block (revealed at the hit)
for i in 0..14 {
let ang = i * tau / 14.0;
line(spark{i}, (1700, 470), (1700 + 160*cos(ang), 470 + 160*sin(ang)));
color(spark{i}, gold); stroke(spark{i}, 5); glow(spark{i}, 2); untraced(spark{i}); tag(spark{i}, sparks);
}
// a caption pinned to the screen (rides along through the camera move)
text(cap, (cx, h - 56), ""); color(cap, fg); size(cap, 26); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
cam((440, 380), 0.4, smooth); // frame the gun
say(cap, "steady…", 0.4);
wait(0.6);
// FIRE!
say(cap, "FIRE!", 0.2);
par { show(mflash, 0.06); pulse(mflash); show(bullet, 0.08); }
fade(mflash, 0.3);
// the bullet races off — the camera flies with it — and mid-flight, out of
// nowhere, a block slams down into its path
par {
move(bullet, (1690, 470), 2.6, smooth);
cam((1560, 380), 2.6, smooth);
zoom(1.15, 2.6, smooth);
seq {
wait(1.5);
say(cap, "…wait — what's THAT?!", 0.3);
show(block, 0.1);
move(block, (1750, 470), 0.4, bounce);
}
}
// BOOM — impact
say(cap, "BOOM!", 0.15);
par {
flash(block, gold);
shake(block, 0.5);
zoom(1.5, 0.15);
show(boom, 0.12); pulse(boom);
draw(sparks, 0.35);
}
wait(0.5);
// settle — pull back
par {
fade(sparks, 0.5);
fade(boom, 0.6);
fade(bullet, 0.4);
zoom(1.0, 0.9, smooth);
}
say(cap, "…scene.", 0.4);
wait(0.8);
fractal_tree
One recursive def, drawn to depth 12.
// Fractal Tree — a recursive `def` macro draws a branching tree. Each branch
// splits into two shorter branches at a fixed angle; `if depth > 0` is the base
// case that stops the recursion. Branches are keyed by a binary-heap index
// (k -> 2k, 2k+1) so every segment gets a unique id, hued and thinned by depth.
//
// Showcases the Phase-2 language layer: `def`, recursion, `if`, comparisons.
//
// manic examples/fractal_tree.manic
// manic examples/fractal_tree.manic --record out --fps 60
title("Fractal Tree");
canvas(1280, 720);
text(head, (640, 92), "one recursive rule, drawn to depth 9");
display(head); color(head, cyan); size(head, 26); hidden(head);
// draw a branch, then recurse into two children (unless we've bottomed out)
def branch(k, x, y, ang, len, depth) {
// stop at the base depth OR once a branch is too short to see — so even a
// large `depth` self-limits (the tree is bounded by branch length)
if depth > 0 && len > 2 {
let x2 = x + len * cos(ang);
let y2 = y - len * sin(ang); // screen y grows downward
line(seg{k}, (x, y), (x2, y2));
stroke(seg{k}, 1 + depth * 0.8);
hue(seg{k}, 120 + depth * 15); // trunk bluish -> tips green
untraced(seg{k}); tag(seg{k}, tree);
branch(2*k, x2, y2, ang + 0.42, len * 0.72, depth - 1);
branch(2*k + 1, x2, y2, ang - 0.42, len * 0.72, depth - 1);
}
}
// grow from the bottom centre, pointing up (angle pi/2)
branch(1, 640, 700, 1.5708, 150, 20);
// --- script ---
show(head, 0.5);
draw(tree, 1.8);
wait(1.6);
particles-flow
Contained ambient motion and live curved connections in four generic words: particles,
wander, link, and flow. The ids supply the domain meaning.
// Generic contained motion: the ids give the dots their meaning.
// The same four words work for bubbles, dust, stars, data, or molecules.
title("Three bodies, one relation");
canvas("9:16");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
// No template call: black is the full-colour exact-black default.
circle(A, (540, 390), 105);
circle(B, (260, 760), 105);
circle(C, (820, 760), 105);
stroke(A, 5); stroke(B, 5); stroke(C, 5);
particles(insideA, A, 24, 5, 7);
particles(insideB, B, 24, 5, 17);
particles(insideC, C, 24, 5, 27);
equation(labelA, (540, 390), `A`, 64);
equation(labelB, (260, 760), `B`, 64);
equation(labelC, (820, 760), `C`, 64);
link(ab, A, B, -48);
link(bc, B, C, -56);
link(ac, A, C, 48);
stroke(ab, 5); stroke(bc, 5); stroke(ac, 5);
untraced(ab); untraced(bc); untraced(ac);
equation(relAB, (310, 445), `A\sim B`, 34);
equation(relBC, (540, 690), `B\sim C`, 34);
equation(relAC, (770, 445), `A\sim C`, 34);
color(relAB, dim); color(relBC, dim); color(relAC, dim);
hidden(A); hidden(B); hidden(C);
hidden(labelA); hidden(labelB); hidden(labelC);
hidden(insideA); hidden(insideB); hidden(insideC);
hidden(relAB); hidden(relBC); hidden(relAC);
par {
wander(insideA, 9);
wander(insideB, 9);
wander(insideC, 9);
seq {
par { show(A, 0.35); show(labelA, 0.35); show(insideA, 0.45); }
wait(0.25);
par { show(B, 0.35); show(labelB, 0.35); show(insideB, 0.45); }
show(relAB, 0.25);
par { draw(ab, 0.75); recolor(relAB, fg, 0.75); }
flow(ab, 0.9);
par { show(C, 0.35); show(labelC, 0.35); show(insideC, 0.45); }
show(relBC, 0.25);
par { draw(bc, 0.75); recolor(relBC, fg, 0.75); }
flow(bc, 0.9);
show(relAC, 0.25);
par { draw(ac, 0.75); recolor(relAC, fg, 0.75); }
par { flow(ab, 1.1); flow(bc, 1.1); flow(ac, 1.1); }
wait(0.55);
}
}
process-stream-observe
One deterministic collection journey drives two truthful views. stream progressively
moves persistent objects; observe connects the same arrival/speed measurements to a
counter and an initially empty livehistogram without callbacks or guessed keyframes.
// PROCESS FOUNDATION — the smallest complete example.
// A real persistent collection streams along a path. Both observers read the
// compiled process measurements; neither is animated with guessed values.
title("A Collection Becomes a Process");
canvas("16:9");
template("blank");
watermark(mark, (170, 58), "Made With Manic");
text(kicker, (640, 52), "MANIC · DETERMINISTIC PROCESS");
text(headline, (640, 100), "One journey. Two truthful views.");
size(kicker, 18); color(kicker, dim); bold(kicker);
size(headline, 34); bold(headline);
rect(source, (170, 300), 210, 230);
outlined(source); outline(source, dim); stroke(source, 3);
text(sourceLabel, (170, 440), "persistent collection");
size(sourceLabel, 18); color(sourceLabel, dim);
particles(packets, source, 42, 5, 17);
spline(route, (275, 300), (410, 140), (560, 470), (720, 285));
stroke(route, 4); color(route, fg); untraced(route);
livehistogram(speeds, (980, 335), 0.55, 1.05, 10, 430, 220, cyan);
text(speedLabel, (980, 190), "normalized speed");
size(speedLabel, 20); bold(speedLabel);
counter(arrivals, (640, 610), 0, 0, "arrived ", " / 42");
size(arrivals, 25); color(arrivals, dim);
text(caption, (640, 665), "stream moves real objects · observe reads the same process");
size(caption, 20); color(caption, dim);
hidden(packets); hidden(speeds); hidden(arrivals); hidden(caption);
step("introduce") {
par {
show(packets, 0.45);
draw(route, 0.65);
show(speeds, 0.45);
show(arrivals, 0.35);
show(caption, 0.35);
}
}
wait(0.35);
step("stream-and-observe") {
par {
stream(packets, route, 4.2, 34, smooth);
observe(speeds, packets, speed);
observe(arrivals, packets, arrived);
}
}
wait(1.0);
process-branching-dispatch
One source dispatches persistent requests through an authored one-of-three path network.
The destination histogram reads each request’s real seeded outcome; no service semantics
or separately timed chart animation are hidden in the engine.
// GENERIC PROCESS BRANCHING — one source, three destinations.
//
// The paths carry no service semantics. `branch` only sees a directed acyclic
// network and makes one deterministic uniform choice at every fork. The same
// foundation drives the Galton-board example.
title("Process Branching — One Source, Three Destinations");
canvas("16:9");
template("blank");
watermark(mark, (145, 70), "Made With Manic");
text(head, (cx, 72), "One collection · many truthful routes");
text(sub, (cx, 112),
"Every request keeps its identity, destination, step count, and arrival time.");
size(head, 32); bold(head);
size(sub, 19); color(sub, dim);
circle(source, (180, 340), 24);
color(source, panel); outline(source, cyan); stroke(source, 3);
particles(requests, source, 54, 4, 41);
color(requests, cyan); glow(requests, 0.7); z(requests, 8);
line(entry, (205, 340), (420, 340)); tag(entry, dispatchRoutes);
spline(upper, (420, 340), (530, 190), (680, 185)); tag(upper, dispatchRoutes);
line(middle, (420, 340), (680, 340)); tag(middle, dispatchRoutes);
spline(lower, (420, 340), (530, 490), (680, 495)); tag(lower, dispatchRoutes);
color(dispatchRoutes, dim); stroke(dispatchRoutes, 3); untraced(dispatchRoutes);
rect(worker0, (735, 185), 150, 82);
rect(worker1, (735, 340), 150, 82);
rect(worker2, (735, 495), 150, 82);
for i in 0..3 {
color(worker{i}, panel); outline(worker{i}, cyan); stroke(worker{i}, 2);
}
text(w0, (735, 185), "worker 0");
text(w1, (735, 340), "worker 1");
text(w2, (735, 495), "worker 2");
size(w0, 18); size(w1, 18); size(w2, 18);
livehistogram(destinations, (1030, 350), 0, 3, 3, 330, 300, magenta);
text(histTitle, (1030, 170), "DESTINATION OUTCOME");
size(histTitle, 19); bold(histTitle); color(histTitle, dim);
counter(arrived, (1030, 555), 0, 0, "arrived ", " / 54");
size(arrived, 21); color(arrived, dim);
text(caption, (cx, 650),
"The diagram and histogram are two views of the same seeded dispatch.");
size(caption, 21); color(caption, dim);
hidden(requests); hidden(destinations); hidden(arrived); hidden(caption);
step("network") {
par {
draw(dispatchRoutes, 0.75);
show(requests, 0.35);
show(destinations, 0.45);
show(arrived, 0.35);
show(caption, 0.35);
}
}
wait(0.35);
step("dispatch") {
par {
branch(requests, dispatchRoutes, 5.0, smooth);
observe(destinations, requests, outcome);
observe(arrived, requests, arrived);
flow(dispatchRoutes, 5.0, forward, continuous);
}
}
wait(1.0);
galton-board-process
One uncertain fork becomes eight left-or-right choices, then 180 persistent balls reveal why many more routes terminate near the center. The same real arrivals build the live bell-shaped histogram before a creator CTA closes the probability story.
// GALTON BOARD — RANDOM LOCALLY, PREDICTABLE GLOBALLY
//
// This is a probability story built from generic process vocabulary. Ordinary
// tagged lines form the board; `branch` preserves each ball through eight
// choices; `collect` and `observe` build the distribution from real arrivals.
title("How Random Choices Become a Bell Curve");
canvas("9:16");
template("blank");
watermark(mark,(w*0.16,h*0.042),"Made With Manic");
text(kicker,(cx,h*0.070),"PROBABILITY · RANDOM LOCALLY, ORDERED GLOBALLY");
text(headline,(cx,h*0.115),"Can random choices create a predictable shape?");
text(chapter,(cx,h*0.195),"1 · BEGIN WITH ONE FORK");
text(caption,(cx,h*0.855),"One ball can land almost anywhere.");
text(insight,(cx,h*0.815),"MORE ROUTES LEAD TO THE CENTER");
text(cta,(cx,h*0.930),"MAKE PROBABILITY VISIBLE → 8gwifi.org/manic");
size(kicker,18); color(kicker,dim); bold(kicker); hidden(kicker);
size(headline,31); bold(headline); wrap(headline,w*0.84); hidden(headline);
size(chapter,19); color(chapter,cyan); bold(chapter); hidden(chapter);
size(caption,21); color(caption,dim); wrap(caption,w*0.84); hidden(caption);
size(insight,19); color(insight,lime); bold(insight); hidden(insight);
size(cta,21); color(cta,cyan); bold(cta); hidden(cta);
equation(law,(cx,h*0.158),`X\sim\operatorname{Binomial}\!\left(8,\frac12\right)`,29);
hidden(law);
let levels = 8;
let boardX = cx;
let topY = h*0.235;
let dx = w*0.065;
let dy = h*0.034;
// Every directed edge joins one row to the next. Converging endpoints create
// the ordinary rooted DAG followed by `branch`.
for r in 0..levels {
for k in 0..r+1 {
let x1 = boardX + (k-r*0.5)*dx;
let y1 = topY + r*dy;
let xl = boardX + (k-(r+1)*0.5)*dx;
let xr = boardX + (k+1-(r+1)*0.5)*dx;
let y2 = topY + (r+1)*dy;
line(left{r}_{k},(x1,y1),(xl,y2));
line(right{r}_{k},(x1,y1),(xr,y2));
tag(left{r}_{k},boardRoutes); tag(right{r}_{k},boardRoutes);
color(left{r}_{k},dim); color(right{r}_{k},dim);
stroke(left{r}_{k},1.7); stroke(right{r}_{k},1.7);
opacity(left{r}_{k},0.30); opacity(right{r}_{k},0.30);
dot(peg{r}_{k},(x1,y1),4.8);
color(peg{r}_{k},fg); glow(peg{r}_{k},0.36); tag(peg{r}_{k},pegs);
}
}
untraced(boardRoutes);
for k in 0..levels+1 {
let tx = boardX + (k-levels*0.5)*dx;
let ty = topY + levels*dy;
dot(exit{k},(tx,ty),5);
color(exit{k},gold); glow(exit{k},0.45); tag(exit{k},exits);
counter(bin{k},(tx,ty+28),k,0);
size(bin{k},15); color(bin{k},dim); tag(bin{k},exitLabels);
}
text(leftChoice,(boardX-dx*0.72,topY+dy*0.72),"LEFT");
text(rightChoice,(boardX+dx*0.72,topY+dy*0.72),"RIGHT");
size(leftChoice,15); size(rightChoice,15);
color(leftChoice,cyan); color(rightChoice,magenta);
hidden(leftChoice); hidden(rightChoice);
circle(source,(boardX,topY),13);
opacity(source,0);
particles(balls,source,180,4.0,73);
color(balls,cyan); glow(balls,0.78); z(balls,9);
let histY = h*0.695;
livehistogram(outcomes,(cx,histY),0,9,9,w*0.76,h*0.155,magenta);
text(histTitle,(cx,h*0.595),"WHERE 180 BALLS ACTUALLY LANDED");
size(histTitle,18); bold(histTitle); color(histTitle,dim);
counter(landed,(cx,h*0.785),0,0,"landed "," / 180");
size(landed,21); color(landed,dim);
hidden(pegs); hidden(exits); hidden(exitLabels);
hidden(balls); hidden(outcomes); hidden(histTitle); hidden(landed);
step("introduce one uncertain choice") {
par {
show(kicker,0.30);
show(headline,0.45);
show(law,0.45);
show(chapter,0.35);
show(caption,0.40);
draw(boardRoutes,0.90);
show(pegs,0.55);
show(exits,0.45);
show(exitLabels,0.45);
show(leftChoice,0.35);
show(rightChoice,0.35);
}
}
wait(0.60);
step("repeat the choice eight times") {
par {
show(outcomes,0.50);
show(histTitle,0.35);
show(landed,0.35);
say(chapter,"2 · REPEAT LEFT OR RIGHT EIGHT TIMES",0.38);
say(caption,"At every peg, each ball makes another equally likely left-or-right choice.",0.44);
}
}
wait(0.55);
step("let the crowd reveal the pattern") {
par {
branch(balls,boardRoutes,8.20,smooth);
collect(outcomes,balls,outcome,0.34,smooth);
observe(outcomes,balls,outcome);
observe(landed,balls,arrived);
show(balls,0.15);
seq {
say(chapter,"3 · WATCH 180 INDIVIDUAL JOURNEYS",0.38);
say(caption,"One route is unpredictable. The crowd begins to expose a stable pattern.",0.44);
wait(3.40);
say(caption,"Every bar is measured from the same balls you see falling—not animated separately.",0.44);
}
}
}
wait(0.65);
step("explain why the center wins") {
par {
pulse(outcomes.bars,0.80);
show(insight,0.45);
say(chapter,"4 · ORDER EMERGES FROM MANY CHOICES",0.38);
say(caption,"Extreme bins need nearly all-left or all-right. Many more mixed sequences end near the center.",0.48);
}
}
wait(0.75);
step("create with Manic") {
par {
pulse(outcomes.bars,0.75);
show(cta,0.45);
say(caption,"Describe the choices once. Manic keeps every route, arrival, count, and live distribution connected.",0.45);
}
}
wait(1.45);
hue_wave
An animated hue wave across a grid.
// Hue Wave — a ring of dots, each with its own starting hue, all advancing
// their hue at the same rate so the rainbow *rotates* around the ring. Shows
// off `hue` as an animatable track: `to(id, hue, degrees)` cycles colour over
// time (unlike `recolor`, it travels around the colour wheel, not through grey).
//
// manic examples/hue_wave.manic
// manic examples/hue_wave.manic --record out --fps 60
title("Hue Wave");
canvas(1280, 720);
text(head, (640, 110), "an animated hue track — colour that cycles");
display(head); color(head, cyan); size(head, 26); hidden(head);
let n = 36; let cx = 640; let cy = 400; let r = 210;
// a ring of dots, rainbow-coloured by angle
for i in 0..n {
let a = tau * i / n;
dot(d{i}, (cx + r*cos(a), cy + r*sin(a)), 18);
hue(d{i}, 360 * i / n);
glow(d{i}, 1.4);
tag(d{i}, ring);
}
// --- script ---
show(head, 0.5);
// spin the whole rainbow: every dot advances its hue by 720 deg (two full
// cycles) over 6s, in parallel — the pattern rotates around the ring
par {
for i in 0..n {
to(d{i}, hue, 360*i/n + 720, 6.0, linear);
}
}
hill_run
A little scene animated with the language layer.
// Uphill / Downhill — a rate x time = distance word problem.
// "Up a hill at 4 mph, back down the same path at 6 mph, round trip = 1 hour.
// Total distance?" Answer: one-way d = 2.4 mi, round trip = 4.8 mi.
//
// The distance is SOLVED in-language: d = 1 / (1/4 + 1/6) = 2.4, total = 2d.
// The runner climbs slowly, descends faster (3s vs 2s ~ the real 0.6h : 0.4h),
// then the equation is derived and the answer counts up on a live readout.
//
// manic examples/hill_run.manic
// manic examples/hill_run.manic --record out --fps 60
title("Uphill / Downhill");
canvas("16:9");
// --- the numbers, computed the same way you'd reason it out ---
let up = 4; // mph, uphill
let down = 6; // mph, downhill
let d = 1 / (1/up + 1/down); // one-way distance = 2.4 mi (from d/4 + d/6 = 1)
let total = 2 * d; // round trip = 4.8 mi
text(head, (cx, 84), "up at 4 mph, down at 6 mph -- round trip takes 1 hour");
display(head); color(head, cyan); size(head, 24); hidden(head);
text(cap, (cx, 668), ""); color(cap, dim); size(cap, 23);
// --- the hill (a single path, run up then down) ---
line(ground, (150, 560), (700, 560)); color(ground, dim); stroke(ground, 2); untraced(ground);
line(path, (200, 560), (620, 210)); color(path, cyan); stroke(path, 4); untraced(path);
text(flag, (628, 196), "top"); color(flag, dim); size(flag, 18); hidden(flag);
dot(runner, (200, 560), 16); color(runner, lime); glow(runner, 1.7); hidden(runner);
text(uplbl, (300, 470), "4 mph"); color(uplbl, cyan); size(uplbl, 24); hidden(uplbl);
text(downlbl, (520, 320), "6 mph"); color(downlbl, magenta); size(downlbl, 24); hidden(downlbl);
// --- the derivation, on the right ---
text(e1, (960, 230), "time = distance / rate"); color(e1, dim); size(e1, 22); hidden(e1);
text(e2, (960, 300), "d/4 + d/6 = 1"); display(e2); color(e2, fg); size(e2, 30); hidden(e2);
text(e3, (960, 360), "5d/12 = 1 -> d = 2.4"); display(e3); color(e3, cyan); size(e3, 26); hidden(e3);
counter(ans, (960, 450), 0, 1, "round trip = 2d = ", " mi"); display(ans); color(ans, lime); size(ans, 30); hidden(ans);
// --- script ---
show(head, 0.5);
say(cap, "an athlete runs up a hill, then back down the same path");
par { draw(ground, 0.5); draw(path, 0.7); }
par { show(flag, 0.3); show(runner, 0.3); }
wait(0.3);
section("Up the hill");
say(cap, "uphill at 4 mph -- the slow leg");
show(uplbl, 0.3);
move(runner, (620, 210), 3.0, linear);
section("Back down");
say(cap, "downhill at 6 mph -- faster, so less time");
show(downlbl, 0.3);
move(runner, (200, 560), 2.0, linear);
wait(0.3);
section("Set up the equation");
say(cap, "let d = the one-way distance; time = distance / rate");
show(e1, 0.4);
show(e2, 0.4);
say(cap, "combine the fractions: 5d/12 = 1, so d = 2.4 miles");
show(e3, 0.5);
flash(e3, lime);
section("Total distance");
say(cap, "the round trip is 2d");
show(ans, 0.3);
to(ans, value, total, 1.4);
pulse(ans);
wait(1.6);
walk
An articulated stick figure walking down a road — legs swing, arms counter-swing, the body
bobs — built purely from the language layer (let + for + trig), no character rig.
title("A Generic Figure Walking Down the Road");
canvas("16:9");
let groundY = cy + 160;
let startX = cx - 420;
let stepDist = 15;
let swingAmp = 26;
let bobAmp = 10;
// ================= road =================
rect(road, (0, groundY), w, h - groundY);
color(road, dim);
filled(road);
untraced(road);
line(roadLine, (0, groundY + 40), (w, groundY + 40));
color(roadLine, panel);
stroke(roadLine, 2);
untraced(roadLine);
for i in 0..12 {
rect(dash{i}, (i*120 - 40, groundY + 36), 50, 8);
color(dash{i}, fg);
filled(dash{i});
untraced(dash{i});
}
// ================= stick figure as points + reflowing segments =================
point(neck, (startX, groundY - 118));
point(hip, (startX, groundY - 10));
point(handL, (startX - 30, groundY - 40));
point(handR, (startX + 30, groundY - 40));
point(footL, (startX - 30, groundY + 100));
point(footR, (startX + 30, groundY + 100));
hidden(neck);
hidden(hip);
hidden(handL);
hidden(handR);
hidden(footL);
hidden(footR);
circle(head, (startX, groundY - 140), 22);
color(head, fg);
outlined(head);
stroke(head, 3);
untraced(head);
segment(spine, neck, hip);
segment(armL, neck, handL);
segment(armR, neck, handR);
segment(legL, hip, footL);
segment(legR, hip, footR);
color(spine, fg);
color(armL, cyan);
color(armR, cyan);
color(legL, gold);
color(legR, gold);
stroke(spine, 4);
stroke(armL, 4);
stroke(armR, 4);
stroke(legL, 4);
stroke(legR, 4);
untraced(spine);
untraced(armL);
untraced(armR);
untraced(legL);
untraced(legR);
// ================= text =================
text(head_label, (cx, 55), "A Generic Figure Walking Down the Road");
color(head_label, cyan);
hidden(head_label);
text(caption, (cx, h - 30), "");
color(caption, dim);
hidden(caption);
// ================= script =================
show(head_label, 0.6);
wait(0.3);
par {
draw(road, 0.5);
draw(roadLine, 0.5);
stagger(0.03) {
for i in 0..12 {
draw(dash{i}, 0.1);
}
}
}
par {
show(neck, 0.01); show(hip, 0.01);
show(handL, 0.01); show(handR, 0.01);
show(footL, 0.01); show(footR, 0.01);
draw(head, 0.4);
draw(spine, 0.3);
draw(armL, 0.3);
draw(armR, 0.3);
draw(legL, 0.3);
draw(legR, 0.3);
}
wait(0.3);
show(caption, 0.4);
say(caption, "Camera pulls back to see the whole road");
par {
cam((cx, cy), 1.0, smooth);
zoom(0.85, 1.0, smooth);
}
wait(0.3);
// --- walk cycle: phase steps by 90 deg so sin actually alternates ---
for i in 0..28 {
let baseX = startX + i*stepDist;
let phase = i*90;
let swing = swingAmp*sin(phase*pi/180);
let legLift = bobAmp*abs(sin(phase*pi/180));
par {
move(neck, (baseX, groundY - 118 - legLift*0.4), 0.15, smooth);
move(hip, (baseX, groundY - 10), 0.15, smooth);
move(handL, (baseX - swing, groundY - 40), 0.15, smooth);
move(handR, (baseX + swing, groundY - 40), 0.15, smooth);
move(footL, (baseX + swing, groundY + 100 - legLift), 0.15, smooth);
move(footR, (baseX - swing, groundY + 100 - legLift), 0.15, smooth);
move(head, (baseX, groundY - 140 - legLift*0.4), 0.15, smooth);
}
}
wait(0.2);
say(caption, "Camera zooms in as the figure gets close");
par {
cam((startX + 420, groundY - 80), 1.4, smooth);
zoom(2.2, 1.4, smooth);
}
wait(0.4);
say(caption, "A close-up look, then pulling back out");
par {
cam((cx, cy), 1.2, smooth);
zoom(1, 1.2, smooth);
}
wait(0.4);
show(caption, 0.3);
say(caption, "A generic stick figure walking -- no specific person depicted");
two_person_walk
Two figures walk toward each other, MEET in the middle, shake hands, then continue past — a little choreographed scene from loops and arithmetic alone (the language layer as animation).
title("Two Figures Meet, Shake Hands, and Continue Walking");
canvas("16:9");
let groundY = cy + 160;
let startX1 = cx - 420;
let startX2 = cx + 420;
let stepDist = 15;
let swingAmp = 26;
let bobAmp = 10;
let meetX = cx;
let endX1 = cx + 420;
let endX2 = cx - 420;
// ================= road =================
rect(road, (0, groundY), w, h - groundY);
color(road, dim);
filled(road);
untraced(road);
line(roadLine, (0, groundY + 40), (w, groundY + 40));
color(roadLine, panel);
stroke(roadLine, 2);
untraced(roadLine);
for i in 0..14 {
rect(dash{i}, (i*120 - 40, groundY + 36), 50, 8);
color(dash{i}, fg);
filled(dash{i});
untraced(dash{i});
}
// ================= figure 1 (walks left -> right) =================
point(neck1, (startX1, groundY - 118));
point(hip1, (startX1, groundY - 10));
point(handL1, (startX1 - 30, groundY - 40));
point(handR1, (startX1 + 30, groundY - 40));
point(footL1, (startX1 - 30, groundY + 100));
point(footR1, (startX1 + 30, groundY + 100));
hidden(neck1); hidden(hip1);
hidden(handL1); hidden(handR1);
hidden(footL1); hidden(footR1);
circle(head1, (startX1, groundY - 140), 22);
color(head1, fg);
outlined(head1);
stroke(head1, 3);
untraced(head1);
segment(spine1, neck1, hip1);
segment(armL1, neck1, handL1);
segment(armR1, neck1, handR1);
segment(legL1, hip1, footL1);
segment(legR1, hip1, footR1);
color(spine1, fg);
color(armL1, cyan);
color(armR1, cyan);
color(legL1, gold);
color(legR1, gold);
stroke(spine1, 4); stroke(armL1, 4); stroke(armR1, 4);
stroke(legL1, 4); stroke(legR1, 4);
untraced(spine1); untraced(armL1); untraced(armR1);
untraced(legL1); untraced(legR1);
// ================= figure 2 (walks right -> left, mirrored) =================
point(neck2, (startX2, groundY - 118));
point(hip2, (startX2, groundY - 10));
point(handL2, (startX2 - 30, groundY - 40));
point(handR2, (startX2 + 30, groundY - 40));
point(footL2, (startX2 - 30, groundY + 100));
point(footR2, (startX2 + 30, groundY + 100));
hidden(neck2); hidden(hip2);
hidden(handL2); hidden(handR2);
hidden(footL2); hidden(footR2);
circle(head2, (startX2, groundY - 140), 22);
color(head2, fg);
outlined(head2);
stroke(head2, 3);
untraced(head2);
segment(spine2, neck2, hip2);
segment(armL2, neck2, handL2);
segment(armR2, neck2, handR2);
segment(legL2, hip2, footL2);
segment(legR2, hip2, footR2);
color(spine2, fg);
color(armL2, magenta);
color(armR2, magenta);
color(legL2, lime);
color(legR2, lime);
stroke(spine2, 4); stroke(armL2, 4); stroke(armR2, 4);
stroke(legL2, 4); stroke(legR2, 4);
untraced(spine2); untraced(armL2); untraced(armR2);
untraced(legL2); untraced(legR2);
// ================= text =================
text(head_label, (cx, 55), "Two Figures Meet, Shake Hands, and Continue Walking");
color(head_label, cyan);
hidden(head_label);
text(caption, (cx, h - 30), "");
color(caption, dim);
hidden(caption);
// ================= script =================
show(head_label, 0.6);
wait(0.3);
par {
draw(road, 0.5);
draw(roadLine, 0.5);
stagger(0.03) {
for i in 0..14 {
draw(dash{i}, 0.1);
}
}
}
par {
show(neck1, 0.01); show(hip1, 0.01);
show(handL1, 0.01); show(handR1, 0.01);
show(footL1, 0.01); show(footR1, 0.01);
draw(head1, 0.4);
draw(spine1, 0.3);
draw(armL1, 0.3);
draw(armR1, 0.3);
draw(legL1, 0.3);
draw(legR1, 0.3);
show(neck2, 0.01); show(hip2, 0.01);
show(handL2, 0.01); show(handR2, 0.01);
show(footL2, 0.01); show(footR2, 0.01);
draw(head2, 0.4);
draw(spine2, 0.3);
draw(armL2, 0.3);
draw(armR2, 0.3);
draw(legL2, 0.3);
draw(legR2, 0.3);
}
wait(0.3);
show(caption, 0.4);
say(caption, "Camera pulls back to see the whole road");
par {
cam((cx, cy), 1.0, smooth);
zoom(0.85, 1.0, smooth);
}
wait(0.3);
// --- walk cycle: both figures walk toward each other, meeting at meetX ---
for i in 0..24 {
let baseX1 = startX1 + i*stepDist;
let baseX2 = startX2 - i*stepDist;
let phase = i*90;
let swing = swingAmp*sin(phase*pi/180);
let legLift = bobAmp*abs(sin(phase*pi/180));
par {
move(neck1, (baseX1, groundY - 118 - legLift*0.4), 0.15, smooth);
move(hip1, (baseX1, groundY - 10), 0.15, smooth);
move(handL1, (baseX1 - swing, groundY - 40), 0.15, smooth);
move(handR1, (baseX1 + swing, groundY - 40), 0.15, smooth);
move(footL1, (baseX1 + swing, groundY + 100 - legLift), 0.15, smooth);
move(footR1, (baseX1 - swing, groundY + 100 - legLift), 0.15, smooth);
move(head1, (baseX1, groundY - 140 - legLift*0.4), 0.15, smooth);
move(neck2, (baseX2, groundY - 118 - legLift*0.4), 0.15, smooth);
move(hip2, (baseX2, groundY - 10), 0.15, smooth);
move(handL2, (baseX2 - swing, groundY - 40), 0.15, smooth);
move(handR2, (baseX2 + swing, groundY - 40), 0.15, smooth);
move(footL2, (baseX2 + swing, groundY + 100 - legLift), 0.15, smooth);
move(footR2, (baseX2 - swing, groundY + 100 - legLift), 0.15, smooth);
move(head2, (baseX2, groundY - 140 - legLift*0.4), 0.15, smooth);
}
}
wait(0.2);
say(caption, "They arrive face to face");
par {
cam((meetX, groundY - 80), 1.2, smooth);
zoom(1.8, 1.2, smooth);
}
// settle into a standing pose facing each other
par {
move(neck1, (meetX - 40, groundY - 118), 0.3, smooth);
move(hip1, (meetX - 40, groundY - 10), 0.3, smooth);
move(footL1, (meetX - 60, groundY + 100), 0.3, smooth);
move(footR1, (meetX - 20, groundY + 100), 0.3, smooth);
move(head1, (meetX - 40, groundY - 140), 0.3, smooth);
move(handL1, (meetX - 70, groundY - 40), 0.3, smooth);
move(neck2, (meetX + 40, groundY - 118), 0.3, smooth);
move(hip2, (meetX + 40, groundY - 10), 0.3, smooth);
move(footL2, (meetX + 60, groundY + 100), 0.3, smooth);
move(footR2, (meetX + 20, groundY + 100), 0.3, smooth);
move(head2, (meetX + 40, groundY - 140), 0.3, smooth);
move(handR2, (meetX + 70, groundY - 40), 0.3, smooth);
}
wait(0.3);
say(caption, "Reaching out to shake hands");
par {
move(handR1, (meetX - 5, groundY - 55), 0.4, smooth);
move(handL2, (meetX + 5, groundY - 55), 0.4, smooth);
}
wait(0.2);
par {
move(handR1, (meetX, groundY - 55), 0.25, smooth);
move(handL2, (meetX, groundY - 55), 0.25, smooth);
}
wait(0.2);
say(caption, "Shaking hands");
for i in 0..4 {
par {
move(handR1, (meetX, groundY - 65), 0.12, smooth);
move(handL2, (meetX, groundY - 65), 0.12, smooth);
}
par {
move(handR1, (meetX, groundY - 48), 0.12, smooth);
move(handL2, (meetX, groundY - 48), 0.12, smooth);
}
}
par {
move(handR1, (meetX, groundY - 55), 0.15, smooth);
move(handL2, (meetX, groundY - 55), 0.15, smooth);
}
wait(0.3);
flash(handR1, gold);
flash(handL2, gold);
wait(0.3);
say(caption, "Letting go and continuing on their separate ways");
par {
cam((cx, cy), 1.2, smooth);
zoom(1, 1.2, smooth);
}
// release hands back to normal swing position before resuming walk
par {
move(handR1, (meetX - 40 + 30, groundY - 40), 0.25, smooth);
move(handL2, (meetX + 40 - 30, groundY - 40), 0.25, smooth);
}
wait(0.2);
// --- resume walk cycle: figure1 continues toward endX1, figure2 toward endX2 ---
for i in 0..24 {
let baseX1 = (meetX - 40) + i*stepDist;
let baseX2 = (meetX + 40) - i*stepDist;
let phase = i*90;
let swing = swingAmp*sin(phase*pi/180);
let legLift = bobAmp*abs(sin(phase*pi/180));
par {
move(neck1, (baseX1, groundY - 118 - legLift*0.4), 0.15, smooth);
move(hip1, (baseX1, groundY - 10), 0.15, smooth);
move(handL1, (baseX1 - swing, groundY - 40), 0.15, smooth);
move(handR1, (baseX1 + swing, groundY - 40), 0.15, smooth);
move(footL1, (baseX1 + swing, groundY + 100 - legLift), 0.15, smooth);
move(footR1, (baseX1 - swing, groundY + 100 - legLift), 0.15, smooth);
move(head1, (baseX1, groundY - 140 - legLift*0.4), 0.15, smooth);
move(neck2, (baseX2, groundY - 118 - legLift*0.4), 0.15, smooth);
move(hip2, (baseX2, groundY - 10), 0.15, smooth);
move(handL2, (baseX2 - swing, groundY - 40), 0.15, smooth);
move(handR2, (baseX2 + swing, groundY - 40), 0.15, smooth);
move(footL2, (baseX2 + swing, groundY + 100 - legLift), 0.15, smooth);
move(footR2, (baseX2 - swing, groundY + 100 - legLift), 0.15, smooth);
move(head2, (baseX2, groundY - 140 - legLift*0.4), 0.15, smooth);
}
}
wait(0.3);
say(caption, "Two generic stick figures -- no specific persons depicted");
par {
cam((cx, cy), 1.0, smooth);
zoom(0.85, 1.0, smooth);
}
wait(0.4);
equal_cuts
A circle halved again and again (pizza cuts).
// Equal Cuts — a circle sliced into equal pieces, repeatedly doubled:
// 2 → 4 → 8 equal wedges. Each "cut" is a diameter traced across the circle
// at an equal angle. (manic has no sector primitive yet, so cuts are lines.)
//
// manic examples/equal_cuts.manic
// manic examples/equal_cuts.manic --record out --fps 60
title("Equal Cuts");
canvas(1280, 720);
// the circle to divide, centred at (640, 400) with radius 240
circle(pie, (640, 400), 240); stroke(pie, 3);
// four diameters through the centre at 0, 45, 90, 135 degrees.
// revealed in stages, they cut the circle into 2, then 4, then 8 equal pieces.
line(c0, (400, 400), (880, 400)); color(c0, magenta); stroke(c0, 3); untraced(c0); // 0
line(c1, (640, 160), (640, 640)); color(c1, magenta); stroke(c1, 3); untraced(c1); // 90
line(c2, (470, 230), (810, 570)); color(c2, lime); stroke(c2, 3); untraced(c2); // 135
line(c3, (810, 230), (470, 570)); color(c3, lime); stroke(c3, 3); untraced(c3); // 45
text(cap, (640, 690), ""); color(cap, dim); size(cap, 22);
text(count, (1040, 170), ""); color(count, cyan); size(count, 34); bold(count);
// --- cut in half ---
say(cap, "cut the circle in half");
draw(c0, 0.6);
say(count, "2 pieces");
wait(0.5);
// --- cut again: four equal pieces ---
say(cap, "cut again at a right angle — four equal pieces");
draw(c1, 0.6);
say(count, "4 pieces");
wait(0.5);
// --- and again: eight equal pieces ---
say(cap, "and again on both diagonals — eight equal pieces");
par {
draw(c2, 0.6);
draw(c3, 0.6);
}
say(count, "8 pieces");
pulse(pie);
wait(1.2);
archimedes_pi
Bounding pi with inscribed / circumscribed polygons.
// Approximating pi — Archimedes' method (c. 250 BC): inscribe a regular polygon
// in a circle and its perimeter closes in on the circumference. For an n-gon in
// a circle of radius R the perimeter is 2R * n*sin(pi/n), so pi ~ n*sin(pi/n),
// which -> pi as n grows. We sweep n = 6, 24, 96 (Archimedes' own 96-gon) and
// zoom in to see the last polygon nearly kiss the circle.
//
// Uses: a `for` loop per polygon, computed estimates, a live counter, and the
// camera (cam + zoom).
//
// manic examples/archimedes_pi.manic
// manic examples/archimedes_pi.manic --record out --fps 60
title("Approximating pi");
canvas("16:9");
let ox = 440; let oy = 400; let R = 240; // circle centre + radius
// the estimates, computed in-language
let e6 = 6 * sin(pi/6); // 3.000
let e24 = 24 * sin(pi/24); // 3.133
let e96 = 96 * sin(pi/96); // 3.141
text(head, (640, 78), "Archimedes: straight lines closing in on a circle");
display(head); color(head, cyan); size(head, 25); hidden(head);
text(cap, (640, 675), ""); color(cap, dim); size(cap, 22);
// the true circle (the target)
circle(circ, (ox, oy), R); outlined(circ); outline(circ, dim); stroke(circ, 2); untraced(circ);
// live pi readout
counter(est, (990, 330), 0, 3, "pi ~ ", ""); display(est); color(est, lime); size(est, 40); hidden(est);
text(truth, (990, 395), "true pi = 3.14159..."); color(truth, dim); size(truth, 20); hidden(truth);
// --- hexagon: n = 6 (magenta) ---
let n = 6;
for i in 0..n {
let a0 = tau*i/n; let a1 = tau*(i+1)/n;
line(h{i}, (ox + R*cos(a0), oy + R*sin(a0)), (ox + R*cos(a1), oy + R*sin(a1)));
color(h{i}, magenta); stroke(h{i}, 3); untraced(h{i}); tag(h{i}, p6);
}
// --- 24-gon (cyan) ---
let n = 24;
for i in 0..n {
let a0 = tau*i/n; let a1 = tau*(i+1)/n;
line(g{i}, (ox + R*cos(a0), oy + R*sin(a0)), (ox + R*cos(a1), oy + R*sin(a1)));
color(g{i}, cyan); stroke(g{i}, 3); untraced(g{i}); tag(g{i}, p24);
}
// --- 96-gon (lime), Archimedes' own ---
let n = 96;
for i in 0..n {
let a0 = tau*i/n; let a1 = tau*(i+1)/n;
line(k{i}, (ox + R*cos(a0), oy + R*sin(a0)), (ox + R*cos(a1), oy + R*sin(a1)));
color(k{i}, lime); stroke(k{i}, 2); untraced(k{i}); tag(k{i}, p96);
}
// --- script ---
show(head, 0.5);
say(cap, "how close can straight lines get to a curve?");
draw(circ, 1.0);
par { show(est, 0.3); show(truth, 0.3); }
wait(0.4);
section("6 sides");
say(cap, "start with a hexagon inside the circle");
draw(p6, 0.8);
to(est, value, e6, 1.0);
wait(0.7);
fade(p6, 0.4);
section("24 sides");
say(cap, "more sides hug the circle more tightly");
draw(p24, 1.0);
to(est, value, e24, 1.0);
wait(0.7);
fade(p24, 0.4);
section("96 sides");
say(cap, "Archimedes went to 96 sides -- around 250 BC");
draw(p96, 1.2);
to(est, value, e96, 1.0);
pulse(est);
wait(0.7);
section("Almost a circle");
say(cap, "zoom in: the polygon edge and the arc nearly touch");
par { cam((ox, oy - R), 1.5, smooth); zoom(5, 1.5, smooth); }
wait(1.4);
par { cam((cx, cy), 1.0, smooth); zoom(1, 1.0, smooth); }
wait(0.8);
pieday
A Pi Day card: a rainbow petal-flower built from a loop of circles, radial rays,
the digits of π, and the definition circumference / diameter = pi.
title("Pi Day");
canvas("16:9");
let r = h*0.23;
let centerY = cy + 25;
let n = 64;
let petalsN = 12;
text(head, (cx, 70), "Happy Pi Day");
text(bigPi, (cx, centerY - 8), "pi");
text(digits, (cx, h - 92), "3.1415926535897932384626433832795028841971...");
text(formula, (cx, h - 50), "circumference / diameter = pi");
size(head, 40);
size(bigPi, 112);
size(digits, 24);
size(formula, 26);
bold(head);
bold(bigPi);
color(head, magenta);
color(bigPi, gold);
color(digits, cyan);
color(formula, lime);
hidden(head);
hidden(bigPi);
hidden(digits);
hidden(formula);
circle(mainCircle, (cx, centerY), r);
line(diameter, (cx - r, centerY), (cx + r, centerY));
text(diamLab, (cx, centerY + 34), "diameter");
text(circLab, (cx, centerY - r - 30), "circumference");
stroke(mainCircle, 5);
stroke(diameter, 3);
color(mainCircle, cyan);
color(diameter, lime);
color(diamLab, lime);
color(circLab, cyan);
size(diamLab, 22);
size(circLab, 22);
hidden(diamLab);
hidden(circLab);
untraced(mainCircle);
untraced(diameter);
for i in 0..petalsN {
circle(petal{i}, (cx + 0.54*r*cos(tau*i/petalsN), centerY + 0.54*r*sin(tau*i/petalsN)), 0.46r);
stroke(petal{i}, 2);
hue(petal{i}, 360i/petalsN);
opacity(petal{i}, 0.34);
untraced(petal{i});
tag(petal{i}, petals);
}
for i in 0..n {
dot(spark{i}, (cx + 1.23*r*cos(tau*i/n), centerY + 1.23*r*sin(tau*i/n)), 4);
hue(spark{i}, 360*i/n);
hidden(spark{i});
tag(spark{i}, sparks);
}
for i in 0..24 {
line(ray{i}, (cx + 1.02*r*cos(tau*i/24), centerY + 1.02*r*sin(tau*i/24)), (cx + 1.18*r*cos(tau*i/24), centerY + 1.18*r*sin(tau*i/24)));
stroke(ray{i}, 3);
hue(ray{i}, 360*i/24);
untraced(ray{i});
tag(ray{i}, rays);
}
dot(centerDot, (cx, centerY), 6);
color(centerDot, gold);
hidden(centerDot);
show(head, 0.7);
par {
draw(petals, 1.4);
draw(mainCircle, 1.2);
}
par {
draw(diameter, 0.8);
show(centerDot, 0.4);
show(diamLab, 0.5);
show(circLab, 0.5);
}
par {
show(bigPi, 0.9);
draw(rays, 0.9);
}
stagger(0.018) {
for i in 0..n {
show(spark{i}, 0.25);
}
}
par {
show(digits, 0.7);
show(formula, 0.7);
}
pulse(bigPi, 0.8);
pulse(mainCircle, 0.8);
par {
spin(petals, 18, 3.0, smooth);
spin(sparks, -35, 3.0, smooth);
}
wait(1.2);