Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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);