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

Transforms & morphing

Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).

gradient

Gradient paint, the acceptance demo: one word gradient(id, c1, c2, …, [mode]) covers a radial well, a three-stop height-colored plot, a spline colored by "curvature", an RK4 free kick colored by "speed", and an arc-length arrow whose head takes the tip color. The color is computed, not painted — and every stop stays template-aware.

// Gradient paint — the color is computed, not painted.
//   manic examples/gradient.manic
//   manic examples/gradient.manic --still 11 --scale 1.5
//
// One word, five truths (stops ≥ 2, evenly spaced; mode optional):
//   gradient(path, c1, c2)                 arc-length along a stroke
//   gradient(plot, c1, c2, c3, 270)        linear over the bounds — height, any number of stops
//   gradient(shape, c1, c2, radial)        centre → edge
//   gradient(traj, c1, c2, "speed")        a trajectory colored by its true local speed
//   gradient(path, c1, c2, "curvature")    any path colored by how hard it bends

title("Gradient Paint");
canvas(1280, 720);
template("blank");

// --- cast ---

// a radial "energy well" behind everything
circle(well, (640, 400), 260);
filled(well);
gradient(well, panel, void, radial);
opacity(well, 0.85);

// a plot colored by its own height, in three stops: blue low, cyan mid, gold high.
// The vertical linear gradient over the curve's bounds IS its y-value.
axes(ax, (640, 400), 520, 220);
plot(wave, (640, 400), 78, 150, sin, 6.6);
stroke(wave, 4);
gradient(wave, blue, cyan, gold, 270);
untraced(wave);

// a curve colored by its own curvature — the tight apex carries the color
spline(swoop, (160, 640), (400, 260), (640, 160), (880, 260), (1120, 640));
stroke(swoop, 5);
gradient(swoop, dim, magenta, "curvature");
untraced(swoop);

// an RK4 free kick (top-down pitch) colored by its true speed —
// gold off the boot, blue as drag bites (slowest stop first, fastest last)
freekick(kick, (185, 690), 30, 36, 3.5, 5.5, 120);
stroke(kick, 5);
gradient(kick, blue, gold, "speed");
untraced(kick);
hidden(kick.ball);

// an arrow whose head takes the gradient's tip color
arrow(dart, (420, 600), (1040, 600));
stroke(dart, 4);
gradient(dart, dim, lime);
untraced(dart);

// headline + caption
text(head, (640, 100), "the color is computed, not painted");
display(head);  color(head, cyan);  size(head, 30);  hidden(head);
text(cap, (640, 680), "");  color(cap, dim);  size(cap, 22);

// --- script ---

show(head, 0.5);
say(cap, "a height-colored plot: blue low, cyan mid, gold high");
draw(wave, 1.8);
wait(0.6);

say(cap, "a curve colored by its own curvature — the bend glows");
draw(swoop, 1.6);
wait(0.6);

say(cap, "a free kick colored by its true speed — gold fast, blue slow");
draw(kick, 1.6);
wait(0.6);

say(cap, "an arc-length stroke; the arrowhead takes its tip color");
draw(dart, 1.2);
wait(1.2);

svg-import

SVG vector import, the acceptance demo: svg() brings a heart (cubic béziers) and its highlight (a <circle> normalised to a path) in as NATIVE Polyline/Polygon entities — the #ff2d95 fill kept LITERAL, not re-themed by the template — then show/pulse/recolor drive the whole drawing through its {id} tag. Geometry, not a texture; the 2D twin of model3’s OBJ import.

// SVG vector import — bring artwork in as NATIVE path entities (not a texture).
// Each subpath of heart.svg becomes a traceable Polyline / filled Polygon, fitted
// to 320 px wide and centred; the pieces are tagged `heart`, so ONE verb animates
// the whole drawing. Imported colours reproduce faithfully (the #ff2d95 fill is
// kept literal, not re-themed). This is the 2D twin of `model3`'s OBJ import.
title("SVG Vector Import");
canvas("16:9");
template("neon");

svg(heart, (cx, cy), "asset:svg/heart.svg", 320);

hidden(heart);
show(heart, 0.8);          // reveal — real geometry, faithful colours
wait(0.4);
pulse(heart, 0.9);         // animate the whole drawing with one verb
wait(0.6);
recolor(heart, cyan);      // …and recolour it — imported vectors are first-class
wait(1.2);

svg-howto

“Made of Emoji” — a vector spectacle showing the full power of svg(). Thirty rainbow heart emoji explode from one point into a ring, then snap into a beating HEART placed by the parametric heart curve — dozens of imported vectors choreographed by pure math. A rapid SAMPLER then riffles the bundled catalogue (icons · flags · emoji), flags and heroes rain in with burst confetti, and a rocket launches to the title. Every actor is one svg() line; the reveal is scale + computed motion + faithful colour, with optional ElevenLabs VO.

// ============================================================================
//  svg-howto.manic — "MADE OF EMOJI"
//
//  Not a lecture — a spectacle. From one point, a storm of imported emoji
//  explodes, swirls into a galaxy, then snaps into a beating heart — dozens of
//  vectors choreographed by pure math, exploded again into a flag-lit finale.
//  Every actor is an `svg()` import; nothing is a texture; it's all computed.
//
//    manic examples/svg-howto.manic
//    manic examples/svg-howto.manic --record out --preset reel
// ============================================================================

title("Made of Emoji — a vector spectacle");
canvas("16:9");
template("blank");

voice("elevenlabs");                       // promo narration (ELEVENLABS_API_KEY)
// voice("elevenlabs", "alice");
// voice("elevenlabs", "jessica");
// voice("cartesia");  // voice("gtts");   // free fallback
// speak("…") = voice only; say(sub, "…") = on-screen text; both can run together.

// deep-space backdrop
rect(space, (cx, cy), w*2, h*2); filled(space); gradient(space, void, indigo, radial);

text(sub, (cx, h*0.9), ""); size(sub, 30); color(sub, fg); display(sub); glow(sub, 0.4);

// ---- the swarm: 30 heart emoji, rainbow-hued, all born at the centre ----
for i in 0..30 {
  svg(sw{i}, (cx, cy), "asset:svg/emoji/heart.svg", 46);
  hue(sw{i}, i*12);                 // rainbow, one hue apiece
  tag(sw{i}, swarm); z(sw{i}, 5); hidden(sw{i});
}

// ---- burst engines (gold ignition, cyan confetti) ----
rect(bseed, (cx, cy), 24, 24); hidden(bseed);
particles(spark, bseed, 30, 6, 22, "random"); color(spark, gold); glow(spark, 0.9); hidden(spark);
rect(cseed, (cx, cy), 24, 24); hidden(cseed);
particles(conf, cseed, 30, 6, 22, "random"); color(conf, cyan); glow(conf, 0.9); hidden(conf);

// ---- the finale cast (flags + hero emoji rain in) ----
svg(rk,  (cx, h + 120), "asset:svg/emoji/rocket.svg", 120); z(rk, 9); hidden(rk);
svg(hero0, (cx - 500, -80), "asset:svg/emoji/fire.svg",   80); hidden(hero0);
svg(hero1, (cx - 250, -80), "asset:svg/emoji/star.svg",   80); hidden(hero1);
svg(hero2, (cx + 250, -80), "asset:svg/emoji/party.svg",  80); hidden(hero2);
svg(hero3, (cx + 500, -80), "asset:svg/emoji/trophy.svg", 80); hidden(hero3);
svg(flag0, (cx - 420, -80), "asset:svg/flags/in.svg", 92); hidden(flag0);
svg(flag1, (cx - 140, -80), "asset:svg/flags/br.svg", 92); hidden(flag1);
svg(flag2, (cx + 140, -80), "asset:svg/flags/jp.svg", 92); hidden(flag2);
svg(flag3, (cx + 420, -80), "asset:svg/flags/de.svg", 92); hidden(flag3);

// ---- the sampler: a stack of diverse shapes to riffle through ----
svg(smp0,  (cx, cy - 40), "asset:svg/emoji/rocket.svg",       220); hidden(smp0);
svg(smp1,  (cx, cy - 40), "asset:svg/emoji/party.svg",        220); hidden(smp1);
svg(smp2,  (cx, cy - 40), "asset:svg/emoji/brain.svg",        220); hidden(smp2);
svg(smp3,  (cx, cy - 40), "asset:svg/flags/jp.svg",           240); hidden(smp3);
svg(smp4,  (cx, cy - 40), "asset:svg/emoji/trophy.svg",       220); hidden(smp4);
svg(smp5,  (cx, cy - 40), "asset:svg/lucide/cpu.svg",         200); hue(smp5, 190);  hidden(smp5);
svg(smp6,  (cx, cy - 40), "asset:svg/flags/br.svg",           240); hidden(smp6);
svg(smp7,  (cx, cy - 40), "asset:svg/emoji/fire.svg",         220); hidden(smp7);
svg(smp8,  (cx, cy - 40), "asset:svg/heroicons/bolt.svg",     200); hue(smp8, 48);   hidden(smp8);
svg(smp9,  (cx, cy - 40), "asset:svg/flags/in.svg",           240); hidden(smp9);
svg(smp10, (cx, cy - 40), "asset:svg/emoji/star.svg",         220); hidden(smp10);
svg(smp11, (cx, cy - 40), "asset:svg/lucide/globe.svg",       200); hue(smp11, 130); hidden(smp11);
svg(smp12, (cx, cy - 40), "asset:svg/emoji/earth.svg",        220); hidden(smp12);
svg(smp13, (cx, cy - 40), "asset:svg/flags/de.svg",           240); hidden(smp13);
svg(smp14, (cx, cy - 40), "asset:svg/heroicons/beaker.svg",   200); hue(smp14, 300); hidden(smp14);
svg(smp15, (cx, cy - 40), "asset:svg/emoji/heart.svg",        220); hidden(smp15);

text(mark, (cx, cy - 20), "MANIC"); size(mark, 110); color(mark, gold); bold(mark); display(mark); glow(mark, 1.2); hidden(mark);
text(tagl, (cx, cy + 78), "6,404 shapes · one file · all computed");
size(tagl, 26); color(tagl, cyan); display(tagl); hidden(tagl);

// ============================================================================
//  THE SPECTACLE  (fast, punchy)
// ============================================================================

// 1 — SINGULARITY: everything waits in one blinding point.
seq {
  zoom(1.35, 0.01);
  show(sw0, 0.3);
  par { for i in 0..30 { show(sw{i}, 0.25); } }   // a tight rainbow knot at centre
  pulse(sw0);
  say(sub, "One line…", 0.25);
  speak("This… is one line of Manic.");
  wait(0.2);
}

// 2 — BIG BANG: the storm explodes outward into a ring.
seq {
  say(sub, "…one universe.", 0.25);
  speak("From it, a whole universe of vectors explodes into being.");
  show(spark, 0.05); burst(spark, 0.7);
  par {
    for i in 0..30 {
      move(sw{i}, (cx + 300*cos(i*12*pi/180), cy + 300*sin(i*12*pi/180)), 0.7, out);
    }
    zoom(1.0, 0.7, smooth);
  }
  wait(0.15);
}

// 3 — GALAXY: the ring breathes (turn removed to test formation).
seq {
  say(sub, "computed — never drawn.", 0.3);
  speak("Import any SVG — an icon, a flag, an emoji — as living geometry. Not a picture. Real shapes you can move and colour.");
  par { for i in 0..30 { pulse(sw{i}); } }
  wait(0.2);
}

// 4 — THE HEART: dozens of vectors snap into a single shape.
seq {
  say(sub, "shape them at will.", 0.3);
  speak("Watch thirty of them snap into a single form — placed by pure math.");
  par {
    for i in 0..30 {
      move(sw{i},
        (cx + 208*sin(i*pi/15)*sin(i*pi/15)*sin(i*pi/15),
         cy - (169*cos(i*pi/15) - 65*cos(2*i*pi/15) - 26*cos(3*i*pi/15) - 13*cos(4*i*pi/15)) + 40),
        1.1, smooth);
    }
  }
  par { for i in 0..30 { pulse(sw{i}); } }         // the heart beats
  wait(0.4);
}

// 4.5 — THE LIBRARY: riffle the catalogue so they see the full range.
seq {
  say(sub, "and not just hearts — 6,404 shapes, one line each.", 0.3);
  speak("And it's not just hearts. Six thousand four hundred shapes ship inside — every icon, every flag, every emoji — each one line away.");
  par { for i in 0..30 { fade(sw{i}, 0.4); } }     // clear the heart
  stagger(0.2) {
    for i in 0..16 { seq { show(smp{i}, 0.14); fade(smp{i}, 0.3); } }
  }
  wait(0.2);
}

// 5 — CELEBRATION: flags and heroes rain in; confetti pops.
seq {
  say(sub, "every shape. every colour. one file.", 0.35);
  speak("Faithful colours. Real vectors. Recolour them, move them, explode them — all from one text file.");
  show(conf, 0.05); burst(conf, 0.9);
  stagger(0.06) {
    move(flag0, (cx - 420, cy - 250), 0.7, overshoot); show(flag0, 0.1);
    move(hero0, (cx - 500, cy + 250), 0.7, overshoot); show(hero0, 0.1);
    move(flag1, (cx - 140, cy - 250), 0.7, overshoot); show(flag1, 0.1);
    move(hero1, (cx - 250, cy + 250), 0.7, overshoot); show(hero1, 0.1);
    move(flag2, (cx + 140, cy - 250), 0.7, overshoot); show(flag2, 0.1);
    move(hero2, (cx + 250, cy + 250), 0.7, overshoot); show(hero2, 0.1);
    move(flag3, (cx + 420, cy - 250), 0.7, overshoot); show(flag3, 0.1);
    move(hero3, (cx + 500, cy + 250), 0.7, overshoot); show(hero3, 0.1);
  }
  wait(0.4);
}

// 6 — FINALE: it all bursts apart; a rocket tears through to the title.
seq {
  say(sub, "", 0.1);
  show(spark, 0.05); burst(spark, 0.8);
  par {
    fade(flag0, 0.5); fade(flag1, 0.5); fade(flag2, 0.5); fade(flag3, 0.5);
    fade(hero0, 0.5); fade(hero1, 0.5); fade(hero2, 0.5); fade(hero3, 0.5);
  }
  show(rk, 0.2);
  move(rk, (cx, cy + 40), 0.7, out);
  par { flash(rk, cyan); pulse(rk); }
  move(rk, (cx, -160), 0.6, in);                   // it launches off the top
  par { show(mark, 0.5); show(tagl, 0.5); }
  speak("This is Manic — turn plain text into vector spectacle. Try it free at eight g wifi dot org slash manic.");
  flash(mark, gold);
  wait(1.4);
}

clip-window

T1 rectangular clip: clip(id, region) scissors a big imported vector to another entity’s bounds, so a hollow spotlight window sweeps across a flag and reveals it piece by piece. The scissor is resolved every frame — move the window and the reveal tracks it. Reveal-through-a- window · crop-to-panel · region wipe · inset viewport, in one word.

// clip-window.manic — T1 rectangular clip: reveal-through-a-window / crop-to-panel.
//
// `clip(id, region)` scissors an entity (or a whole tagged group) to another
// entity's rectangular bounds. The scissor is resolved every frame, so the window
// FOLLOWS the region — move the window and the reveal tracks it. Here a spotlight
// window sweeps across a big imported vector, revealing it piece by piece.
//
//   manic examples/clip-window.manic
title("Clip — reveal through a window");
canvas("16:9");
template("blank");

// a big imported vector that overflows what one window can show
svg(art, (cx, cy), "asset:svg/flags/br.svg", 940);

// the spotlight window — hollow frame so the clipped vector shows through
// (declared BEFORE the clip that references it)
rect(win, (cx - 380, cy), 320, 300); outlined(win); outline(win, fg); stroke(win, 5); glow(win, 0.7);

// clip the whole vector to the window — nothing shows outside it
clip(art, win);

text(cap, (cx, h*0.9), "clip(id, window) — the scissor follows the window");
size(cap, 26); color(cap, dim); display(cap);

// sweep the window across → the flag is revealed only where the window is
seq {
  wait(0.4);
  move(win, (cx + 380, cy), 2.4, smooth);
  wait(0.3);
  move(win, (cx, cy), 1.0, smooth);
  pulse(win);
  wait(0.6);
}

mask-lens

T2 arbitrary-shape mask: mask(id, region) composites a vector through another entity’s SILHOUETTE — any shape masks any content (render-target multiply). A circular lens glides across a flag like a magnifier; swap the circle for a star or an imported outline and the reveal takes that shape. The general cousin of clip (rectangle-only). Lens · porthole · spotlight · reveal-through-any-shape, and the mask follows the region every frame.

// mask-lens.manic — T2 arbitrary-shape mask: reveal-through-any-shape.
//
// `mask(id, region)` composites `id` through another entity's SILHOUETTE — any
// shape (circle, polygon, star, an imported svg outline) masks any content. The
// general cousin of `clip` (which is rectangle-only). The mask follows `region`
// every frame, so a circular lens glides across a big vector like a magnifier.
//
//   manic examples/mask-lens.manic
title("Mask — reveal through any shape");
canvas("16:9");
template("blank");

// a big imported vector to peer into
svg(art, (cx, cy), "asset:svg/flags/br.svg", 940);

// a circular lens — its shape is the mask (declared BEFORE the mask call)
circle(lens, (cx - 340, cy), 150); outlined(lens); outline(lens, fg); stroke(lens, 5); glow(lens, 0.8);

// mask the whole vector to the lens — art shows only inside the circle
mask(art, lens);

text(cap, (cx, h*0.9), "mask(id, region) — any shape masks any content");
size(cap, 26); color(cap, dim); display(cap);

// glide the lens across → the flag is revealed only inside the moving circle
seq {
  wait(0.4);
  move(lens, (cx + 340, cy), 2.4, smooth);
  wait(0.3);
  move(lens, (cx, cy), 1.0, smooth);
  pulse(lens);
  wait(0.6);
}

motion-graphics-v2

The generic Motion Graphics V2 acceptance scene: one persistent marker carries an attached label along a path, becomes a declared visual blueprint, releases the label, gathers the same particles into a ring, and turns the whole arrangement around one shared pivot. Uses attach, become, and turn with no renderer flags or subject-specific vocabulary.

// motion-graphics-v2.manic — relationships, identity, and a deliberate settle

title("Motion Graphics V2 — Follow, Become, Turn");
canvas("9:16");
template("mono");
watermark(manicMark, (150, 132), "Made With Manic");

creator(me, "@anish2good name=Manic_Motion tagline=Describe_the_relationship yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social cta=Create_yours safe=reels");
socials(me);

text(kicker, (540, 155), "MOTION GRAPHICS V2");
size(kicker, 23); color(kicker, dim); bold(kicker);
text(headline, (540, 228), "Move the idea, not every coordinate");
size(headline, 40); bold(headline);
text(subhead, (540, 292), "attach · become · turn");
size(subhead, 25); color(subhead, dim);

rect(stage, (540, 865), 900, 1020);
color(stage, panel); outline(stage, dim); opacity(stage, 0.78);

// One authored route and one persistent object.
plot(route, (205, 1040), 172, 190, "0.55*sin(1.2*x)+0.18*x", (0, 4.25));
color(route, cyan); stroke(route, 6); untraced(route);

dot(marker, (205, 1040), 12);
color(marker, gold); glow(marker, 1.6);
text(markerLabel, (205, 994), "one persistent object");
size(markerLabel, 24); color(markerLabel, fg); hidden(markerLabel);

// A hidden target is a visual blueprint; it never appears as a second object.
circle(nodeBlueprint, (935, 885), 56);
color(nodeBlueprint, magenta); outlined(nodeBlueprint); stroke(nodeBlueprint, 7);
glow(nodeBlueprint, 1.2); hidden(nodeBlueprint);

// Supporting particles begin offstage, then arrive as an ordered ring.
rect(seedBox, (540, 610), 260, 180); hidden(seedBox);
particles(sparks, seedBox, 14, 7, 42, "random");
color(sparks, cyan); hidden(sparks);
circle(finalOrbit, (935, 885), 138); hidden(finalOrbit);

text(explain, (540, 1325), "The label follows because the relationship is authored.");
size(explain, 26); color(explain, dim); wrap(explain, 800); hidden(explain);

step("attach-and-travel") {
  seq {
    attach(markerLabel, marker, (0,-46));
    par {
      show(markerLabel, 0.35);
      show(explain, 0.40);
      draw(route, 2.8, smooth);
      travel(marker, route, 2.8, smooth);
    }
    wait(0.45);
  }
}

step("become-the-next-idea") {
  seq {
    par {
      become(marker, nodeBlueprint, 1.05, smooth);
      say(markerLabel, "same id · new visual state", 0.45);
      say(explain, "become preserves identity and settles on the exact blueprint.", 0.45);
    }
    wait(0.50);
  }
}

step("release-arrange-turn") {
  seq {
    attach(markerLabel, none);
    par {
      fade(markerLabel, 0.35);
      fade(route, 0.45);
      show(sparks, 0.30);
      arrange(sparks, finalOrbit, "ring", 1.15, smooth);
      say(explain, "Release the label. Arrange a system. Turn it around one pivot.", 0.45);
    }
    turn(sparks, marker, 24, 0.65, out);
    wait(1.40);
  }
}

motion-graphics-v2-story

The advanced composition example: one question travels through a field of facts, its WHY? label follows, notation rewrites into a pattern, the question becomes a model, and the same facts arrange and turn as one knowledge system. Combines attach, become, and turn with to, travel, flow, spin, arrange, wander, rewrite, seq, par, and stagger.

// motion-graphics-v2-story.manic — one idea, one continuous motion story

title("The Journey of an Idea — Motion Graphics V2 Story");
canvas("9:16");
template("mono");
watermark(manicMark, (170, 178), "Made With Manic");

creator(me, "@anish2good name=Manic_Motion tagline=Move_the_idea yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social cta=Create_yours safe=reels");
socials(me);

// ---------------------------------------------------------------------------
// ONE PERSISTENT STAGE
// The scene never cuts away. Each act reuses what the previous act established.
// ---------------------------------------------------------------------------

text(kicker, (540, 184), "A MOTION GRAPHICS STORY");
size(kicker, 22); color(kicker, dim); bold(kicker); hidden(kicker);

text(headline, (540, 248), "How an idea learns to move");
size(headline, 42); bold(headline); hidden(headline);

text(chapter, (540, 316), "ACT 1 · FOLLOW ATTENTION");
size(chapter, 24); color(chapter, cyan); bold(chapter); hidden(chapter);

rect(stage, (540, 850), 900, 990);
color(stage, panel); outline(stage, dim); opacity(stage, 0.76);

text(narration, (540, 1400), "A question appears before the answer does.");
size(narration, 28); color(narration, fg); wrap(narration, 820); hidden(narration);

text(principle, (540, 1495), "Motion should guide the eye, preserve meaning, then settle.");
size(principle, 22); color(principle, dim); wrap(principle, 820); hidden(principle);

// ---------------------------------------------------------------------------
// ACT 1 — CURIOSITY TRAVELS; ITS CONTEXT FOLLOWS
// ---------------------------------------------------------------------------

spline(questionPath, (175, 1080), (290, 980), (385, 1060), (490, 900), (540, 790));
color(questionPath, cyan); stroke(questionPath, 5); dashed(questionPath, 18, 15);
glow(questionPath, 0.8); untraced(questionPath);

dot(idea, (175, 1080), 12);
color(idea, gold); glow(idea, 1.7); hidden(idea);

text(focusLabel, (175, 1024), "WHY?");
size(focusLabel, 29); color(focusLabel, gold); bold(focusLabel); hidden(focusLabel);

// Facts begin as visual noise. They persist through every later arrangement.
rect(noiseField, (540, 820), 690, 470); hidden(noiseField);
particles(facts, noiseField, 16, 7, 29, "random");
color(facts, cyan); glow(facts, 0.9); hidden(facts);

for i in 0..16 {
  tag(facts.p{i}, knowledgeSystem);
}

// ---------------------------------------------------------------------------
// ACT 2 — THE SAME QUESTION BECOMES A MODEL
// ---------------------------------------------------------------------------

equation(work, (540, 1195), `1+2+\cdots+n=?`, 43);
color(work, fg); hidden(work);

// Hidden entities are visual blueprints. `idea` adopts this exact final state.
rect(modelBlueprint, (540, 790), 116, 116);
outlined(modelBlueprint); outline(modelBlueprint, magenta); stroke(modelBlueprint, 7);
rot(modelBlueprint, 45); glow(modelBlueprint, 1.5); hidden(modelBlueprint);

circle(knowledgeOrbit, (540, 790), 250); hidden(knowledgeOrbit);

text(observe, (540, 492), "OBSERVE");
text(connect, (842, 790), "CONNECT");
text(testIdea, (540, 1088), "TEST");
text(explain, (238, 790), "EXPLAIN");

size(observe, 20); size(connect, 20); size(testIdea, 20); size(explain, 20);
color(observe, dim); color(connect, dim); color(testIdea, dim); color(explain, dim);
bold(observe); bold(connect); bold(testIdea); bold(explain);
hidden(observe); hidden(connect); hidden(testIdea); hidden(explain);
tag(observe, knowledgeSystem); tag(connect, knowledgeSystem);
tag(testIdea, knowledgeSystem); tag(explain, knowledgeSystem);

// ---------------------------------------------------------------------------
// ACT 3 — THE MODEL BECOMES A COORDINATED SYSTEM
// ---------------------------------------------------------------------------

text(systemLabel, (540, 648), "ONE IDEA · MANY RELATIONSHIPS");
size(systemLabel, 22); color(systemLabel, magenta); bold(systemLabel); hidden(systemLabel);

text(finalLine, (540, 1320), "Don't animate layers. Animate the relationship.");
size(finalLine, 30); color(finalLine, fg); bold(finalLine); wrap(finalLine, 820); hidden(finalLine);

// ---------------------------------------------------------------------------
// TIMELINE — seq tells the story; par and stagger control the viewing rhythm.
// ---------------------------------------------------------------------------

step("a-question-pulls-attention") {
  seq {
    par {
      show(kicker, 0.35);
      show(headline, 0.55);
      show(chapter, 0.40);
      show(narration, 0.45);
      show(principle, 0.45);
      show(idea, 0.30);
    }

    // Stagger turns a cloud into a readable arrival instead of one visual shock.
    stagger(0.035) {
      for i in 0..16 {
        show(facts.p{i}, 0.22);
      }
    }

    attach(focusLabel, idea, (0,-56));
    par {
      show(focusLabel, 0.30);
      draw(questionPath, 2.65, smooth);
      travel(idea, questionPath, 2.65, smooth);
      flow(questionPath, 1.45);
      wander(facts, 2.65);
    }

    par {
      to(idea, scale, 1.55, 0.45, out);
      say(narration, "Attention follows the question while the background stays alive.", 0.45);
    }
    wait(0.55);
  }
}

step("the-question-becomes-a-model") {
  seq {
    attach(focusLabel, none);
    par {
      fade(focusLabel, 0.30);
      fade(questionPath, 0.45);
      say(chapter, "ACT 2 · PRESERVE THE IDEA", 0.35);
      say(narration, "The question changes form, but it never loses its identity.", 0.45);
      show(work, 0.40);
    }

    // Existing notation moves only where the mathematical meaning changes.
    rewrite(work, `1+2+\cdots+n=\frac{n(n+1)}{2}`, 1.00, smooth);

    par {
      become(idea, modelBlueprint, 1.05, smooth);
      arrange(facts, knowledgeOrbit, "ring", 1.20, smooth);
      say(principle, "become keeps one id · arrange keeps every particle", 0.45);
    }

    // spin is local: the new model turns around its own centre.
    spin(idea, 90, 0.65, out);

    stagger(0.10) {
      show(observe, 0.30);
      show(connect, 0.30);
      show(testIdea, 0.30);
      show(explain, 0.30);
    }
    wait(0.55);
  }
}

step("relationships-move-as-one") {
  seq {
    attach(systemLabel, idea, (0,-142));
    par {
      show(systemLabel, 0.35);
      say(chapter, "ACT 3 · MOVE THE SYSTEM", 0.35);
      say(narration, "Now every part can move together without losing the structure.", 0.45);
      show(finalLine, 0.45);
    }

    // turn is relational: particles and labels share one pivot and one motion.
    par {
      turn(knowledgeSystem, idea, 45, 1.20, smooth);
      spin(idea, -135, 1.20, smooth);
      to(facts, color, lime, 1.20, smooth);
      rewrite(work, `\text{question}\;\longrightarrow\;\text{model}\;\longrightarrow\;\text{insight}`, 1.20, smooth);
    }

    par {
      pulse(idea, 0.70);
      say(principle, "FOLLOW · TRANSFORM · COORDINATE · SETTLE", 0.45);
    }
    wait(1.80);
  }
}

reactive-math-journey

A playful vertical journey from 1+1 in Class 1 through fractions, algebra, geometry, calculus, linear algebra, probability and Fourier analysis to a PhD-level functional integral — then back to the curiosity that started it all.

// ============================================================================
// reactive-math-journey.manic — from Class 1 counting to PhD curiosity
// ----------------------------------------------------------------------------
// One equation id grows with the learner. `rewrite` keeps the journey continuous
// while a progress marker climbs through school, university and research.
// The ending returns to 1+1: advanced mathematics keeps the original curiosity.
// ============================================================================

title("From 1 + 1 to a PhD in Mathematics");
canvas("9:16");
template("shorts");
watermark(manicMark, (w*0.895-100, h*0.075+24), "Made With Manic");

creator(me, "@anish2good name=Math_Journey tagline=Stay_curious yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social cta=Keep_learning safe=reels");
socials(me);

text(kicker, (540, 135), "THE JOURNEY OF MATHEMATICS");
size(kicker, 23); color(kicker, dim); bold(kicker); hidden(kicker);

text(headline, (540, 225), "From 1 + 1 to a PhD");
size(headline, 47); color(headline, fg); bold(headline); hidden(headline);

text(subhead, (540, 292), "Same curiosity. Bigger questions.");
size(subhead, 25); color(subhead, cyan); bold(subhead); hidden(subhead);

// The learning stage leaves a slim left lane for the climbing progress marker.
rect(stage, (610, 790), 820, 760);
color(stage, panel); outline(stage, dim); opacity(stage, 0.78);

line(rail, (135, 1135), (135, 535));
color(rail, cyan); stroke(rail, 3); dashed(rail, 13, 10); opacity(rail, 0.35);
untraced(rail);

for i in 0..13 {
  dot(milestone{i}, (135, 1135 - i*50), 5);
  color(milestone{i}, dim); opacity(milestone{i}, 0.55);
}

dot(learner, (135, 1135), 12);
color(learner, cyan); glow(learner, 2.2); hidden(learner);

text(level, (610, 495), "CLASS 1 · COUNTING");
size(level, 25); color(level, cyan); bold(level); hidden(level);

equation(work, (610, 775), `1+1=2`, 58);
hidden(work);

text(note, (610, 1035), "Two apples. One idea. A lifetime of questions begins.");
size(note, 27); color(note, dim); wrap(note, 700); hidden(note);

text(altitude, (540, 1265), "SCHOOL  →  UNIVERSITY  →  RESEARCH");
size(altitude, 22); color(altitude, dim); bold(altitude); hidden(altitude);

// ---------------------------------------------------------------------------
// TIMELINE
// ---------------------------------------------------------------------------

par {
  show(kicker, 0.40); show(headline, 0.55); show(subhead, 0.55);
  show(level, 0.45); show(note, 0.45); show(altitude, 0.45);
  draw(rail, 0.90); show(learner, 0.45);
}
show(work, 0.55);
wait(0.85);

// Class 2 — repeated addition discovers multiplication.
par {
  say(level, "CLASS 2 · MULTIPLICATION", 0.35);
  say(note, "Addition gets impatient and invents a shortcut.", 0.35);
  move(learner, (135, 1085), 0.45, smooth);
}
rewrite(work, `3+3+3+3=12`, 0.80, smooth);
wait(0.35);
rewrite(work, `4\times3=\textcolor{lime}{12}`, 0.80, smooth);
wait(0.65);

// Class 5 — pieces become numbers too.
par {
  say(level, "CLASS 5 · FRACTIONS", 0.35);
  say(note, "Now even pieces of a whole can be added exactly.", 0.35);
  move(learner, (135, 1035), 0.45, smooth);
}
rewrite(work, `\frac{1}{2}+\frac{1}{3}=\textcolor{lime}{\frac{5}{6}}`, 0.90, smooth);
wait(0.70);

// Class 7 — the mystery number gets a name.
par {
  say(level, "CLASS 7 · ALGEBRA", 0.35);
  say(note, "A blank box becomes x — and arithmetic starts telling stories.", 0.35);
  move(learner, (135, 985), 0.45, smooth);
}
rewrite(work, `x+3=7`, 0.80, smooth);
wait(0.30);
rewrite(work, `x=\textcolor{lime}{4}`, 0.75, smooth);
wait(0.65);

// Class 8 — shapes reveal equations hidden inside them.
par {
  say(level, "CLASS 8 · GEOMETRY", 0.35);
  say(note, "A right triangle quietly connects three squares.", 0.35);
  move(learner, (135, 935), 0.45, smooth);
}
rewrite(work, `a^2+b^2=\textcolor{cyan}{c^2}`, 0.85, smooth);
wait(0.70);

// Class 10 — one formula solves every quadratic.
par {
  say(level, "CLASS 10 · QUADRATICS", 0.35);
  say(note, "The unknown now has two possible futures.", 0.35);
  move(learner, (135, 885), 0.45, smooth);
}
rewrite(work, `ax^2+bx+c=0`, 0.85, smooth);
wait(0.30);
rewrite(work, `x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}`, 1.00, smooth);
wait(0.75);

// Class 11 — circles turn into identities.
par {
  say(level, "CLASS 11 · TRIGONOMETRY", 0.35);
  say(note, "Sine and cosine travel differently, but always return to one.", 0.35);
  move(learner, (135, 835), 0.45, smooth);
}
rewrite(work, `\sin^2\theta+\cos^2\theta=\textcolor{lime}{1}`, 0.90, smooth);
wait(0.70);

// Class 12 — mathematics learns to describe change itself.
par {
  say(level, "CLASS 12 · CALCULUS", 0.35);
  say(note, "Not just where things are — how fast they are changing.", 0.35);
  move(learner, (135, 785), 0.45, smooth);
}
rewrite(work, `\frac{d}{dx}x^n=\textcolor{cyan}{nx^{n-1}}`, 0.95, smooth);
wait(0.75);

// University — numbers become transformations and uncertain beliefs.
par {
  say(level, "UNIVERSITY · LINEAR ALGEBRA", 0.35);
  say(note, "Some directions do not turn — they only stretch.", 0.35);
  move(learner, (135, 735), 0.45, smooth);
}
rewrite(work, `A\vec v=\textcolor{magenta}{\lambda}\vec v`, 0.90, smooth);
wait(0.70);

par {
  say(level, "UNIVERSITY · PROBABILITY", 0.35);
  say(note, "Evidence arrives, and a belief learns how to update.", 0.35);
  move(learner, (135, 685), 0.45, smooth);
}
rewrite(work, `P(A\mid B)=\frac{P(B\mid A)P(A)}{P(B)}`, 0.95, smooth);
wait(0.75);

// Graduate study — equations evolve, then decompose into waves.
par {
  say(level, "GRADUATE · DIFFERENTIAL EQUATIONS", 0.35);
  say(note, "A tiny law of change predicts an entire future.", 0.35);
  move(learner, (135, 635), 0.45, smooth);
}
rewrite(work, `\frac{dy}{dt}=ky`, 0.85, smooth);
wait(0.30);
rewrite(work, `y(t)=y_0e^{kt}`, 0.85, smooth);
wait(0.70);

par {
  say(level, "GRADUATE · FOURIER ANALYSIS", 0.35);
  say(note, "A complicated signal turns out to be a choir of simple waves.", 0.35);
  move(learner, (135, 585), 0.45, smooth);
}
rewrite(work, `f(x)=\sum_{n=-\infty}^{\infty}c_ne^{inx}`, 0.95, smooth);
wait(0.75);

// PhD — the symbols are advanced; the habit is still “what if?”
par {
  say(level, "PhD · THE EDGE OF WHAT WE KNOW", 0.40);
  say(note, "The integral now ranges over every possible field configuration.", 0.40);
  move(learner, (135, 535), 0.55, overshoot);
}
rewrite(work, `\mathcal{Z}=\int\mathcal{D}\phi\,e^{-S[\phi]}`, 1.05, smooth);
wait(1.00);

// A loopable ending: the notation grew, but the first question never left.
say(level, "THE REAL DEGREE · STAYING CURIOUS", 0.40);
say(note, "After all that mathematics, the best researchers still ask: why?", 0.40);
rewrite(work, `1+1=\textcolor{lime}{2}`, 1.00, smooth);
pulse(learner, 0.65);
wait(1.80);

reactive-math-notation

One Reels-ready stage exercises structured LaTeX across thirteen notation worlds: algebra, calculus, limits, trigonometry, logic, sums/products, physics, chemistry, biology, probability, matrices/vectors, mixed prose/math, and creator notation.

// ============================================================================
// reactive-math-notation.manic — one Reels-ready stage, thirteen notation worlds
// ----------------------------------------------------------------------------
// `rewrite` is deliberately domain-neutral. The same persistent equation moves
// through algebra, calculus, logic, physics, chemistry, biology, probability
// and linear algebra;
// only changed RaTeX parts leave or enter. Mixed prose + inline math uses normal
// `text`, and the last scene proves creator-defined notation needs no new verb.
// ============================================================================

title("Reactive Mathematics — One Language, Every Notation");
canvas("9:16");
template("shorts");
watermark(manicMark, (w*0.895-100, h*0.075+24), "Made With Manic");

creator(me, "@anish2good name=Notation_Lab tagline=Every_symbol_can_move yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social cta=Create_your_own safe=reels");
socials(me);

text(kicker, (540, 145), "ONE EQUATION · THIRTEEN SCIENTIFIC WORLDS");
size(kicker, 23); color(kicker, dim); bold(kicker); hidden(kicker);

text(headline, (540, 235), "If LaTeX can say it, Manic can move it");
size(headline, 39); color(headline, fg); bold(headline); hidden(headline);

rect(stage, (540, 755), 940, 760);
color(stage, panel); outline(stage, dim); opacity(stage, 0.72); hidden(stage);

text(topic, (540, 445), "01 · ALGEBRAIC REARRANGEMENT");
size(topic, 23); color(topic, cyan); bold(topic); hidden(topic);

equation(work, (540, 730), `2(x+3)=14`, 52);
hidden(work);

text(note, (540, 1040), "Keep the structure. Move only the mathematical change.");
size(note, 25); color(note, dim); wrap(note, 800); hidden(note);

// A normal text entity may freely mix prose and several inline formulas.
text(mixed, (540, 735), `Energy $E=mc^2$ uses mass $m$ and light speed $c$.`);
size(mixed, 31); color(mixed, fg); wrap(mixed, 780); hidden(mixed);

text(scope, (540, 1245), "RaTeX accuracy · local motion · one stable layout");
size(scope, 22); color(scope, dim); bold(scope); hidden(scope);

// A subtle dashed construction line also demonstrates that `dashed` belongs
// to Manic itself — plots and all other path-like entities can use it.
line(rule, (215, 1155), (865, 1155));
color(rule, cyan); stroke(rule, 2); dashed(rule, 12, 9); opacity(rule, 0.35);
untraced(rule);

// ---------------------------------------------------------------------------
// TIMELINE — every chapter reuses `work`; nothing is manually repositioned.
// ---------------------------------------------------------------------------

par {
  show(kicker, 0.45); show(headline, 0.55); show(stage, 0.50);
  show(topic, 0.45); show(note, 0.45); show(scope, 0.45); draw(rule, 0.85);
}
show(work, 0.55);
wait(0.70);
rewrite(work, `2x=8\quad\Rightarrow\quad \textcolor{lime}{x=4}`, 0.85, smooth);
wait(0.75);

say(topic, "02 · INTEGRALS & DERIVATIVES", 0.35);
say(note, "The Fundamental Theorem becomes a change of state, not a scene cut.", 0.35);
rewrite(work, `F(x)=\int_0^x t^2\,dt`, 0.80, smooth);
wait(0.45);
rewrite(work, `F'(x)=\frac{d}{dx}\int_0^x t^2\,dt=\textcolor{cyan}{x^2}`, 0.95, smooth);
wait(0.70);

say(topic, "03 · FRACTIONS, ROOTS, POWERS & LIMITS", 0.35);
say(note, "Nested notation is still one screen-aware equation.", 0.35);
rewrite(work, `x^2+\sqrt{x}+\frac{1}{x}`, 0.80, smooth);
wait(0.40);
rewrite(work, `\lim_{x\to0}\frac{\sqrt{1+x}-1}{x}=\frac{1}{2}`, 0.95, smooth);
wait(0.70);

say(topic, "04 · TRIGONOMETRIC IDENTITIES", 0.35);
say(note, "The unchanged terms stay; only the conclusion arrives.", 0.35);
rewrite(work, `\sin^2\theta+\cos^2\theta`, 0.80, smooth);
wait(0.40);
rewrite(work, `\sin^2\theta+\cos^2\theta=\textcolor{lime}{1}`, 0.85, smooth);
wait(0.70);

say(topic, "05 · SET NOTATION & LOGIC", 0.35);
say(note, "Membership, intersections and propositions share the same engine.", 0.35);
rewrite(work, `x\in A\cap B`, 0.80, smooth);
wait(0.40);
rewrite(work, `(x\in A)\land(x\in B)`, 0.85, smooth);
wait(0.70);

say(topic, "06 · SUMMATIONS & PRODUCTS", 0.35);
say(note, "Large operators retain their limits and baseline alignment.", 0.35);
rewrite(work, `\sum_{k=1}^{n}k`, 0.80, smooth);
wait(0.40);
rewrite(work, `\prod_{k=1}^{n}k=\textcolor{magenta}{n!}`, 0.85, smooth);
wait(0.70);

say(topic, "07 · PHYSICS FORMULAS & UNITS", 0.35);
say(note, "Symbols and dimensional units can live in one exact expression.", 0.35);
rewrite(work, `F=ma`, 0.80, smooth);
wait(0.40);
rewrite(work, `[F]=\mathrm{kg}\cdot\mathrm{m}\cdot\mathrm{s}^{-2}`, 0.90, smooth);
wait(0.70);

say(topic, "08 · CHEMISTRY · BALANCED REACTIONS", 0.35);
say(note, "Keep every molecule; introduce only the coefficients that balance atoms.", 0.35);
rewrite(work, `H_2+O_2\rightarrow H_2O`, 0.80, smooth);
wait(0.40);
rewrite(work, `\textcolor{cyan}{2}H_2+O_2\rightarrow\textcolor{cyan}{2}H_2O`, 0.90, smooth);
wait(0.70);

say(topic, "09 · BIOLOGY · HARDY–WEINBERG", 0.35);
say(note, "A population model grows from allele frequencies into genotype frequencies.", 0.35);
rewrite(work, `p+q=1`, 0.80, smooth);
wait(0.40);
rewrite(work, `p^2+2pq+q^2=\textcolor{lime}{1}`, 0.90, smooth);
wait(0.70);

say(topic, "10 · PROBABILITY EXPRESSIONS", 0.35);
say(note, "Conditioning expands into Bayes' rule without replacing the stage.", 0.35);
rewrite(work, `P(A\mid B)`, 0.80, smooth);
wait(0.40);
rewrite(work, `P(A\mid B)=\frac{P(B\mid A)P(A)}{P(B)}`, 0.95, smooth);
wait(0.70);

say(topic, "11 · MATRICES & VECTORS", 0.35);
say(note, "Rows, columns, brackets and vector marks remain genuine LaTeX.", 0.35);
rewrite(work, `\vec v=\begin{bmatrix}1\\2\end{bmatrix}`, 0.85, smooth);
wait(0.40);
rewrite(work, `A\vec v=\begin{bmatrix}a&b\\c&d\end{bmatrix}\begin{bmatrix}1\\2\end{bmatrix}`, 1.00, smooth);
wait(0.75);

say(topic, "12 · TEXT MIXED WITH MATHEMATICS", 0.35);
say(note, "Ordinary prose can carry multiple inline formulas naturally.", 0.35);
fade(work, 0.35);
show(mixed, 0.55);
wait(1.25);
fade(mixed, 0.35);

say(topic, "13 · YOUR OWN NOTATION", 0.35);
say(note, "Define the meaning in your story; Manic preserves the supported notation.", 0.35);
rewrite(work, `\mathcal{R}_{\star}(x)\equiv x^2+1`, 0.90, smooth);
wait(0.45);
rewrite(work, `\mathcal{R}_{\star}(2)=\textcolor{lime}{5}`, 0.85, smooth);
wait(1.20);

say(topic, "ONE REACTIVE LANGUAGE", 0.40);
say(note, "Math, physics, logic — creators decide what comes next.", 0.40);
pulse(work, 0.70);
wait(1.80);

quadratic-formula-continuity

The quadratic formula by completing the square with one persistent LaTeX equation. Each authored rewrite retains unchanged symbols, moves reused terms, and introduces only the new notation — the acceptance benchmark for structured formula motion.

// quadratic-formula-continuity.manic — structured LaTeX rewrite benchmark
//
// The author supplies correct mathematical states. `rewrite` does not solve
// algebra: it keeps equal RaTeX parts alive, moves them to their new roles,
// introduces only new notation, and settles on the exact target formula.

title("The Quadratic Formula by Completing the Square");
canvas("16:9");
template("plain");
watermark(manicMark, (1135, 30), "Made With Manic");

text(kicker, (cx, 72), "COMPLETING THE SQUARE · ONE CONTINUOUS EQUATION");
size(kicker, 18); color(kicker, dim); bold(kicker); hidden(kicker);

text(guide, (cx, 620), "Stable symbols stay. Only the mathematical change moves.");
size(guide, 19); color(guide, dim); hidden(guide);

equation(
  work,
  (cx, 330),
  `\textcolor{orange}{a}x^2+\textcolor{cyan}{b}x+\textcolor{lime}{c}=0`,
  48
);
hidden(work);

show(kicker, 0.45);
show(guide, 0.45);
show(work, 0.65);
wait(1.20);

// Divide through by a.
rewrite(
  work,
  `x^2+\frac{\textcolor{cyan}{b}}{\textcolor{orange}{a}}x+\frac{\textcolor{lime}{c}}{\textcolor{orange}{a}}=0`,
  1.00,
  smooth
);
wait(1.00);

// Move c/a across the equality.
rewrite(
  work,
  `x^2+\frac{\textcolor{cyan}{b}}{\textcolor{orange}{a}}x=-\frac{\textcolor{lime}{c}}{\textcolor{orange}{a}}`,
  0.95,
  smooth
);
wait(0.95);

// Split the middle coefficient into two equal halves.
rewrite(
  work,
  `x^2+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}x+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}x=-\frac{\textcolor{lime}{c}}{\textcolor{orange}{a}}`,
  1.05,
  smooth
);
wait(0.95);

// Add the completing-square term to both sides.
rewrite(
  work,
  `x^2+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}x+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}x+\left(\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}\right)^2=\left(\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}\right)^2-\frac{\textcolor{lime}{c}}{\textcolor{orange}{a}}`,
  1.10,
  smooth
);
wait(1.05);

// Simplify the right side locally.
rewrite(
  work,
  `x^2+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}x+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}x+\left(\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}\right)^2=\frac{\textcolor{cyan}{b}^2-4\textcolor{orange}{a}\textcolor{lime}{c}}{4\textcolor{orange}{a}^2}`,
  1.00,
  smooth
);
wait(1.00);

// Expose the repeated factor, then contract it into a square.
rewrite(
  work,
  `x\left(x+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}\right)+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}\left(x+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}\right)=\frac{\textcolor{cyan}{b}^2-4\textcolor{orange}{a}\textcolor{lime}{c}}{4\textcolor{orange}{a}^2}`,
  1.05,
  smooth
);
wait(0.90);

rewrite(
  work,
  `\left(x+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}\right)^2=\frac{\textcolor{cyan}{b}^2-4\textcolor{orange}{a}\textcolor{lime}{c}}{4\textcolor{orange}{a}^2}`,
  0.95,
  smooth
);
wait(1.00);

// Take square roots.
rewrite(
  work,
  `x+\frac{\textcolor{cyan}{b}}{2\textcolor{orange}{a}}=\pm\frac{\sqrt{\textcolor{cyan}{b}^2-4\textcolor{orange}{a}\textcolor{lime}{c}}}{2\textcolor{orange}{a}}`,
  1.05,
  smooth
);
wait(1.10);

// Isolate x. The fraction moves across; the discriminant remains itself.
rewrite(
  work,
  `x=\frac{-\textcolor{cyan}{b}\pm\sqrt{\textcolor{cyan}{b}^2-4\textcolor{orange}{a}\textcolor{lime}{c}}}{2\textcolor{orange}{a}}`,
  1.10,
  smooth
);
pulse(work, 0.65);
wait(2.50);

transforms

Apply a 2x2 matrix (ApplyMatrix) to a group.

// Transforms — the "animate anything" showcase.
// Named verbs (rotate, spin, scale, move) plus the general `to(id, prop, value)`
// escape hatch, composed with par / seq / stagger.
//
//   manic examples/transforms.manic
//   manic examples/transforms.manic --record out --fps 60 --crt

title("Transforms");
canvas(1280, 720);

// cast
rect(box, (330, 400), 150, 150);   outline(box, cyan);
label(box, "rotate");
rect(dia, (650, 400), 140, 140);   outline(dia, magenta);
label(dia, "to");
circle(orb, (980, 400), 62);       outline(orb, lime);
label(orb, "spin");

dot(p, (150, 620), 12);
text(cap, (640, 662), "");  color(cap, dim);  size(cap, 22);
text(head, (640, 120), "animate anything");
display(head);  color(head, cyan);  size(head, 40);  hidden(head);

// script
show(head, 0.5);

section("Named verbs");
say(cap, "rotate to an absolute angle");
rotate(box, 45, 0.7, overshoot);
say(cap, "spin by a relative angle, twice");
seq {
  spin(orb, 180, 0.6);
  spin(orb, 180, 0.6);
}
wait(0.4);

section("Animate anything");
say(cap, "to(id, property, value) reaches any property");
par {
  to(dia, angle, 45, 0.6, smooth);
  to(dia, scale, 1.4, 0.6);
  to(dia, color, lime, 0.6);
}
wait(0.4);

say(cap, "compose freely with par / seq / stagger");
stagger(0.12) {
  to(box, opacity, 0.4, 0.4);
  to(dia, opacity, 0.4, 0.4);
  to(orb, opacity, 0.4, 0.4);
}
to(p, x, 1130, 1.0, overshoot);
to(p, color, magenta, 0.4);
wait(1.0);

transform_copy

Duplicate an entity, then transform the copy.

// Copy + Winding Morph — two of the Transform family niceties. `copy(c, a)`
// duplicates a shape so the original stays while the copy transforms; `morph`
// with a spin angle winds the blend (Manim's Clockwise / Counterclockwise
// Transform). Left copy morphs clockwise, right copy counter-clockwise.
//
//   manic examples/transform_copy.manic

title("Copy + Winding Morph");
canvas("16:9");

text(head, (cx, 96), "a copy morphs while the original stays -- one CW, one CCW");
color(head, cyan);  size(head, 23);  hidden(head);

// left: original circle (dim) + a cyan copy that morphs into a square, clockwise
circle(o1, (400, 380), 120);  color(o1, dim);  stroke(o1, 3);  hidden(o1);
rect(t1, (400, 380), 220, 220);  hidden(t1);
copy(c1, o1);  color(c1, cyan);  stroke(c1, 5);  glow(c1, 1.6);  hidden(c1);
morph(c1, t1, 200);          // +200 deg = clockwise wind

// right: same idea, counter-clockwise into a triangle-ish (use another square)
circle(o2, (900, 380), 120);  color(o2, dim);  stroke(o2, 3);  hidden(o2);
rect(t2, (900, 380), 220, 220);  hidden(t2);
copy(c2, o2);  color(c2, magenta);  stroke(c2, 5);  glow(c2, 1.6);  hidden(c2);
morph(c2, t2, -200);         // -200 deg = counter-clockwise

// --- script ---
show(head, 0.5);
par { show(o1, 0.4);  show(o2, 0.4);  show(c1, 0.4);  show(c2, 0.4); }
wait(0.5);

section("Morph the copies");
par { to(c1, morph, 1, 1.8, smooth);  to(c2, morph, 1, 1.8, smooth); }
wait(0.9);
par { to(c1, morph, 0, 1.8, smooth);  to(c2, morph, 0, 1.8, smooth); }
wait(1.2);

morph

A sampled-point shape morph from A to B.

// Shape Morph — a circle's outline blends smoothly into a square's and back
// (Manim's Transform). `morph(a, b)` samples both outlines to the same number
// of points; `to(a, morph, t)` interpolates between them (t = 0 is `a`'s shape,
// 1 is `b`'s).
//
//   manic examples/morph.manic

title("Shape Morph");
canvas("16:9");

text(head, (cx, 110), "a circle becomes a square -- and back");
display(head);  color(head, cyan);  size(head, 26);  hidden(head);

circle(sh, (cx, cy), 150);  color(sh, cyan);  stroke(sh, 5);  glow(sh, 1.6);  hidden(sh);
rect(target, (cx, cy), 290, 290);  hidden(target);   // defines the square outline
morph(sh, target);                                    // set sh up to morph into it

// --- script ---
show(head, 0.5);
show(sh, 0.6);
wait(0.5);

section("Morph");
to(sh, morph, 1, 1.6, smooth);      // circle -> square
wait(0.7);
to(sh, morph, 0, 1.6, smooth);      // square -> circle
wait(0.7);
to(sh, morph, 1, 1.1, overshoot);   // and back, with a bounce
wait(1.4);

morph-icons

Morph imported svg() vectors: one emoji reshapes into another and back. A bare svg tag resolves to its dominant path, filled Regions sample along their outer ring, and the two outlines are winding-matched + rotated for a clean shape-to-shape blend — no tangle through the centre. Paired with a colour tween so the heart warms to gold as it becomes a star.

// morph-icons.manic — morph imported SVG vectors from one shape into another.
//
// `morph(a, b)` samples both outlines and blends `a` into `b`'s shape; drive it
// with `to(a, morph, 1, dur)`. It now works on imported `svg()` art: a bare svg
// tag resolves to its dominant path, filled Regions sample along their outer ring,
// and the two loops are winding-matched + rotated for a clean shape-to-shape blend
// (no tangle through the centre). Here one emoji reshapes into another and back.
//
//   manic examples/morph-icons.manic
title("Morph — imported vectors reshape");
canvas("16:9");
template("blank");

svg(heart, (cx, cy), "asset:svg/emoji/heart.svg", 260);
svg(star,  (cx, cy), "asset:svg/emoji/star.svg", 260); hidden(star);

text(cap, (cx, h*0.86), "morph(heart, star) — one imported icon reshapes into another");
size(cap, 24); color(cap, dim); display(cap);

// set the heart up to morph into the star's outline
morph(heart, star);

seq {
  wait(0.5);
  par { to(heart, morph, 1, 1.6, smooth); to(heart, color, gold, 1.6); }
  wait(0.5);
  par { to(heart, morph, 0, 1.6, smooth); to(heart, color, magenta, 1.6); }
  wait(0.5);
}

shapes-howto

SHAPECRAFT — Manic shape power in one film: outlined vs filled (the axes trap), the cast with particles/travel, the full palette that becomes squares then triangles, spin-wound morph, then one coral disc that splits into four independent colours, reunites as one fill, and moves — beside two-colour boolean regions (A−B / B−A / A∩B). Docs-in-motion for the Shapes chapter.

// ============================================================================
//  shapes-howto.manic — SHAPECRAFT
//  A power demo that also teaches the rules:
//    fills hide constructions · outlined reveals · dashed rings for contacts
//    circle/rect/polygon/sector fill by default · morph / become change identity
//
//    manic examples/shapes-howto.manic
//    manic examples/shapes-howto.manic --record out --preset studio
// ============================================================================

title("Shapecraft — Manic Shape Power");
canvas("16:9");
template("blank");

text(cap, (cx, h - 34), ""); size(cap, 22); color(cap, dim); display(cap);
text(head, (cx, 58), ""); size(head, 32); color(head, gold); display(head); bold(head);

// ---- stage energy ----
circle(well, (cx, cy), 420);
filled(well); gradient(well, panel, void, indigo, radial); opacity(well, 0.7);

spline(orbit, (180, 520), (340, 220), (640, 160), (940, 220), (1100, 520));
stroke(orbit, 2.5); gradient(orbit, dim, violet, teal, "curvature");
untraced(orbit); hidden(orbit); opacity(orbit, 0.55);

// ---- hero: one persistent outlined circle ----
circle(hero, (cx, cy), 120);
outlined(hero); color(hero, cyan); stroke(hero, 6); glow(hero, 1.4);
gradient(hero, cyan, mint, teal, 45);
untraced(hero); hidden(hero);

// fill veil — fades over the hero to show the trap without destroying morph
circle(veil, (cx, cy), 120);
filled(veil); color(veil, indigo); opacity(veil, 0.92); hidden(veil); z(veil, 5);

rect(asSquare, (cx, cy), 220, 220); hidden(asSquare);
polygon(asTri, (cx, 250), (cx - 140, 520), (cx + 140, 520)); hidden(asTri);
sector(asPie, (cx, cy), 130, -30, 300); color(asPie, magenta); hidden(asPie);
polygon(asDiamond, (cx, cy - 130), (cx + 130, cy), (cx, cy + 130), (cx - 130, cy));
color(asDiamond, gold); hidden(asDiamond);

circle(finalForm, (cx, cy), 130);
outlined(finalForm); color(finalForm, cyan); stroke(finalForm, 6);
gradient(finalForm, cyan, mint, violet, 90); glow(finalForm, 1.6); hidden(finalForm);

morph(hero, asSquare, 180);   // spin-wound morph ready

text(heroTag, (cx, cy - 170), "one persistent id");
size(heroTag, 22); color(heroTag, fg); hidden(heroTag);

// ---- axes for the fill-trap punch ----
line(axX, (cx - 200, cy), (cx + 200, cy));
color(axX, dim); stroke(axX, 2); untraced(axX); hidden(axX);
line(axY, (cx, cy - 160), (cx, cy + 160));
color(axY, dim); stroke(axY, 2); untraced(axY); hidden(axY);

// ---- the cast (draw-on parade, then orbit travel) ----
circle(castC, (cx - 280, 280), 48);
outlined(castC); color(castC, teal); stroke(castC, 3.5); untraced(castC); hidden(castC);
rect(castR, (cx - 100, 250), 90, 70);
outlined(castR); color(castR, coral); stroke(castR, 3.5); untraced(castR); hidden(castR);
polygon(castP, (cx + 80, 230), (cx + 30, 310), (cx + 130, 310));
outlined(castP); color(castP, violet); stroke(castP, 3.5); untraced(castP); hidden(castP);
sector(castS, (cx + 260, 280), 52, -40, 240);
outlined(castS); color(castS, gold); stroke(castS, 3.5); untraced(castS); hidden(castS);
arc(castA, (cx - 220, 480), 50, -20, 250);
color(castA, mint); stroke(castA, 4); untraced(castA); hidden(castA);
arrow(castV, (cx + 40, 500), (cx + 180, 430));
color(castV, magenta); stroke(castV, 4); untraced(castV); hidden(castV);
line(castL, (cx + 240, 500), (cx + 340, 430));
color(castL, lime); stroke(castL, 4); untraced(castL); hidden(castL);

// sparks that ring the hero, then settle
rect(seed, (cx, cy), 60, 60); hidden(seed);
particles(sparks, seed, 12, 6, 19, "random");
color(sparks, cyan); glow(sparks, 0.5); hidden(sparks);
circle(orbitRing, (cx, cy), 230); hidden(orbitRing);

// ---- palette chips (fill OK — colour IS the subject) ----
let py = 400;
circle(chip0,  (140, py), 26);  filled(chip0);  color(chip0, fg);      hidden(chip0);
circle(chip1,  (220, py), 26);  filled(chip1);  color(chip1, cyan);    hidden(chip1);
circle(chip2,  (300, py), 26);  filled(chip2);  color(chip2, magenta); hidden(chip2);
circle(chip3,  (380, py), 26);  filled(chip3);  color(chip3, lime);    hidden(chip3);
circle(chip4,  (460, py), 26);  filled(chip4);  color(chip4, gold);    hidden(chip4);
circle(chip5,  (540, py), 26);  filled(chip5);  color(chip5, red);     hidden(chip5);
circle(chip6,  (620, py), 26);  filled(chip6);  color(chip6, orange);  hidden(chip6);
circle(chip7,  (700, py), 26);  filled(chip7);  color(chip7, blue);    hidden(chip7);
circle(chip8,  (780, py), 26);  filled(chip8);  color(chip8, teal);    hidden(chip8);
circle(chip9,  (860, py), 26);  filled(chip9);  color(chip9, violet);  hidden(chip9);
circle(chip10, (940, py), 26);  filled(chip10); color(chip10, coral);  hidden(chip10);
circle(chip11, (1020,py), 26);  filled(chip11); color(chip11, indigo); hidden(chip11);
circle(chip12, (1100,py), 26);  filled(chip12); color(chip12, mint);   hidden(chip12);
circle(chip13, (1180,py), 26);  filled(chip13); color(chip13, dim);    hidden(chip13);

rect(sq0,  (140, py), 48, 48); color(sq0, fg);      filled(sq0);  hidden(sq0);
rect(sq1,  (220, py), 48, 48); color(sq1, cyan);    filled(sq1);  hidden(sq1);
rect(sq2,  (300, py), 48, 48); color(sq2, magenta); filled(sq2);  hidden(sq2);
rect(sq3,  (380, py), 48, 48); color(sq3, lime);    filled(sq3);  hidden(sq3);
rect(sq4,  (460, py), 48, 48); color(sq4, gold);    filled(sq4);  hidden(sq4);
rect(sq5,  (540, py), 48, 48); color(sq5, red);     filled(sq5);  hidden(sq5);
rect(sq6,  (620, py), 48, 48); color(sq6, orange);  filled(sq6);  hidden(sq6);
rect(sq7,  (700, py), 48, 48); color(sq7, blue);    filled(sq7);  hidden(sq7);
rect(sq8,  (780, py), 48, 48); color(sq8, teal);    filled(sq8);  hidden(sq8);
rect(sq9,  (860, py), 48, 48); color(sq9, violet);  filled(sq9);  hidden(sq9);
rect(sq10, (940, py), 48, 48); color(sq10, coral);  filled(sq10); hidden(sq10);
rect(sq11, (1020,py), 48, 48); color(sq11, indigo); filled(sq11); hidden(sq11);
rect(sq12, (1100,py), 48, 48); color(sq12, mint);   filled(sq12); hidden(sq12);
rect(sq13, (1180,py), 48, 48); color(sq13, dim);    filled(sq13); hidden(sq13);

polygon(tri0,  (140, py-22), (114, py+22), (166, py+22)); color(tri0, fg);      filled(tri0);  hidden(tri0);
polygon(tri1,  (220, py-22), (194, py+22), (246, py+22)); color(tri1, cyan);    filled(tri1);  hidden(tri1);
polygon(tri2,  (300, py-22), (274, py+22), (326, py+22)); color(tri2, magenta); filled(tri2);  hidden(tri2);
polygon(tri3,  (380, py-22), (354, py+22), (406, py+22)); color(tri3, lime);    filled(tri3);  hidden(tri3);
polygon(tri4,  (460, py-22), (434, py+22), (486, py+22)); color(tri4, gold);    filled(tri4);  hidden(tri4);
polygon(tri5,  (540, py-22), (514, py+22), (566, py+22)); color(tri5, red);     filled(tri5);  hidden(tri5);
polygon(tri6,  (620, py-22), (594, py+22), (646, py+22)); color(tri6, orange);  filled(tri6);  hidden(tri6);
polygon(tri7,  (700, py-22), (674, py+22), (726, py+22)); color(tri7, blue);    filled(tri7);  hidden(tri7);
polygon(tri8,  (780, py-22), (754, py+22), (806, py+22)); color(tri8, teal);    filled(tri8);  hidden(tri8);
polygon(tri9,  (860, py-22), (834, py+22), (886, py+22)); color(tri9, violet);  filled(tri9);  hidden(tri9);
polygon(tri10, (940, py-22), (914, py+22), (966, py+22)); color(tri10, coral);  filled(tri10); hidden(tri10);
polygon(tri11, (1020,py-22),(994, py+22),(1046,py+22)); color(tri11, indigo); filled(tri11); hidden(tri11);
polygon(tri12, (1100,py-22),(1074,py+22),(1126,py+22)); color(tri12, mint);   filled(tri12); hidden(tri12);
polygon(tri13, (1180,py-22),(1154,py+22),(1206,py+22)); color(tri13, dim);    filled(tri13); hidden(tri13);

// ---- CENTER: one filled disc → 4 independent colours → one colour → move ----
circle(disc, (cx, cy), 140);
filled(disc); color(disc, coral); outline(disc, cyan); stroke(disc, 4);
glow(disc, 0.6); hidden(disc);

pie(q, (cx, cy), 140, 4);   // q0..q3 — same centre/radius as disc
color(q0, teal);    filled(q0);  outline(q0, fg); stroke(q0, 2); untraced(q0); hidden(q0);
color(q1, coral);   filled(q1);  outline(q1, fg); stroke(q1, 2); untraced(q1); hidden(q1);
color(q2, violet);  filled(q2);  outline(q2, fg); stroke(q2, 2); untraced(q2); hidden(q2);
color(q3, gold);    filled(q3);  outline(q3, fg); stroke(q3, 2); untraced(q3); hidden(q3);

// reunited whole — one colour, one id that can move
circle(whole, (cx, cy), 140);
filled(whole); color(whole, cyan); outline(whole, mint); stroke(whole, 4);
glow(whole, 0.8); hidden(whole);

// ---- RIGHT: boolean → TWO regions, TWO colours ----
let bx = 1000;
let by = 400;
rect(bBox, (bx - 35, by - 35), 130, 130);
outlined(bBox); outline(bBox, cyan); opacity(bBox, 0.4); hidden(bBox);
circle(bCirc, (bx + 40, by + 20), 78);
outlined(bCirc); outline(bCirc, magenta); opacity(bCirc, 0.4); hidden(bCirc);
difference(onlyBox, bBox, bCirc, teal);   hidden(onlyBox); glow(onlyBox, 0.6);
difference(onlyCirc, bCirc, bBox, coral); hidden(onlyCirc); glow(onlyCirc, 0.6);
intersect(bHit, bBox, bCirc, gold);       hidden(bHit); glow(bHit, 0.5);
text(labBool, (bx, by + 155), "A−B · B−A · A∩B"); size(labBool, 16); color(labBool, dim); hidden(labBool);

// ---- contact truth ----
line(crossH, (cx - 160, cy), (cx + 160, cy));
color(crossH, dim); stroke(crossH, 2.5); dashed(crossH, 12, 8); untraced(crossH); hidden(crossH);
line(crossV, (cx, cy - 120), (cx, cy + 120));
color(crossV, dim); stroke(crossV, 2.5); dashed(crossV, 12, 8); untraced(crossV); hidden(crossV);
dot(badDot, (cx, cy), 16); color(badDot, coral); glow(badDot, 0.5); hidden(badDot);
circle(goodRing, (cx, cy), 18);
outlined(goodRing); color(goodRing, mint); stroke(goodRing, 2.8); dashed(goodRing, 4, 3);
untraced(goodRing); hidden(goodRing);

// ---- traveler on the orbit ----
dot(rider, (180, 520), 9); color(rider, gold); glow(rider, 1.2); hidden(rider);

// ============================================================================
//  SCRIPT — one continuous shapecraft story
// ============================================================================

step("birth") {
  seq {
    say(head, "SHAPECRAFT", 0.35);
    say(cap, "one shape. every identity. the visual stays true.", 0.35);
    show(hero, 0.05);
    par {
      draw(hero, 1.2, smooth);
      zoom(1.15, 1.2, smooth);
    }
    attach(heroTag, hero, (0, -160));
    show(heroTag, 0.35);
    wait(0.35);
  }
}

step("truth") {
  seq {
    say(head, "outlined — constructions stay readable", 0.3);
    say(cap, "axes through the hero: the rim never hides the truth.", 0.3);
    par {
      show(axX, 0.05); show(axY, 0.05);
      draw(axX, 0.55); draw(axY, 0.55);
      cam((cx, cy), 0.8, smooth); zoom(1.35, 0.8, smooth);
    }
    pulse(hero);
    wait(0.45);

    say(head, "filled — the trap", 0.25);
    say(cap, "a filled disc sits on the axes and deletes them visually.", 0.25);
    show(veil, 0.45);
    flash(veil, coral);
    wait(0.7);

    say(head, "outlined — axes return", 0.25);
    say(cap, "outline(id, c) only recolors the rim. outlined drops the fill.", 0.25);
    fade(veil, 0.45);
    flash(hero, mint);
    wait(0.55);
    par {
      fade(axX, 0.35); fade(axY, 0.35); fade(heroTag, 0.35); fade(hero, 0.35);
      cam((cx, cy), 0.7, smooth); zoom(1.0, 0.7, smooth);
    }
  }
}

step("cast") {
  seq {
    say(head, "the cast", 0.3);
    say(cap, "circle · rect · polygon · sector · arc · line · arrow", 0.3);
    show(sparks, 0.25);
    arrange(sparks, orbitRing, "ring", 1.0, smooth);
    wait(0.2);
    stagger(0.1) {
      show(castC, 0.05); draw(castC, 0.55);
      show(castR, 0.05); draw(castR, 0.55);
      show(castP, 0.05); draw(castP, 0.55);
      show(castS, 0.05); draw(castS, 0.55);
      show(castA, 0.05); draw(castA, 0.55);
      show(castV, 0.05); draw(castV, 0.55);
      show(castL, 0.05); draw(castL, 0.55);
    }
    wait(0.2);
    show(orbit, 0.05); draw(orbit, 1.0, smooth);
    show(rider, 0.2);
    travel(rider, orbit, 2.0, smooth);
    wait(0.25);
    par {
      fade(sparks, 0.4);
      fade(castC, 0.4); fade(castR, 0.4); fade(castP, 0.4); fade(castS, 0.4);
      fade(castA, 0.4); fade(castV, 0.4); fade(castL, 0.4);
      fade(orbit, 0.4); fade(rider, 0.4);
    }
  }
}

step("palette") {
  seq {
    say(head, "the whole palette — then change the shape", 0.3);
    say(cap, "filled chips when colour is the subject. become keeps the id.", 0.3);
    stagger(0.05) {
      show(chip0, 0.22);  show(chip1, 0.22);  show(chip2, 0.22);  show(chip3, 0.22);
      show(chip4, 0.22);  show(chip5, 0.22);  show(chip6, 0.22);  show(chip7, 0.22);
      show(chip8, 0.22);  show(chip9, 0.22);  show(chip10, 0.22); show(chip11, 0.22);
      show(chip12, 0.22); show(chip13, 0.22);
    }
    wait(0.35);
    say(cap, "become → square", 0.2);
    stagger(0.035) {
      for i in 0..14 { become(chip{i}, sq{i}, 0.65, smooth); }
    }
    wait(0.25);
    say(cap, "become → triangle", 0.2);
    stagger(0.035) {
      for i in 0..14 { become(chip{i}, tri{i}, 0.65, smooth); }
    }
    wait(0.45);
    par {
      for i in 0..14 { fade(chip{i}, 0.35); }
    }
  }
}

step("morph") {
  seq {
    say(head, "morph · become · spin", 0.3);
    say(cap, "same hero id — circle winds into a square, then becomes new geometry.", 0.3);
    show(hero, 0.3);
    to(hero, morph, 1, 1.5, smooth);     // → square (with 180° wind)
    wait(0.25);
    become(hero, asDiamond, 0.95, smooth);
    pulse(hero);
    wait(0.2);
    become(hero, asPie, 0.95, smooth);
    spin(hero, 40, 0.7, out);
    wait(0.2);
    become(hero, asTri, 0.9, smooth);
    wait(0.45);
  }
}

step("pieces") {
  seq {
    say(head, "one fill → four fills → one fill → move", 0.3);
    say(cap, "a filled disc can split into independent colours, reunite, then travel as one.", 0.3);
    fade(hero, 0.3);

    // 1 — the coral disc (same look as the fill-trap)
    par {
      show(axX, 0.05); show(axY, 0.05);
      draw(axX, 0.4); draw(axY, 0.4);
      cam((cx, cy), 0.6, smooth); zoom(1.25, 0.6, smooth);
    }
    show(disc, 0.45);
    flash(disc, coral);
    wait(0.5);

    // 2 — cut into 4 independent colours
    say(cap, "cut: each quadrant keeps its own fill.", 0.25);
    fade(disc, 0.3);
    stagger(0.1) {
      show(q0, 0.05); draw(q0, 0.45);
      show(q1, 0.05); draw(q1, 0.45);
      show(q2, 0.05); draw(q2, 0.45);
      show(q3, 0.05); draw(q3, 0.45);
    }
    // explode slightly so the independence is obvious
    par {
      move(q0, (cx + 22, cy - 22), 0.45, overshoot);
      move(q1, (cx - 22, cy - 22), 0.45, overshoot);
      move(q2, (cx - 22, cy + 22), 0.45, overshoot);
      move(q3, (cx + 22, cy + 22), 0.45, overshoot);
    }
    wait(0.45);

    // 3 — reunite as ONE colour, then ONE circle
    say(cap, "reunite: every slice takes one colour, then they become one disc.", 0.25);
    par {
      recolor(q0, cyan, 0.5);
      recolor(q1, cyan, 0.5);
      recolor(q2, cyan, 0.5);
      recolor(q3, cyan, 0.5);
    }
    // snap slices back to centre
    par {
      move(q0, (cx, cy), 0.5, smooth);
      move(q1, (cx, cy), 0.5, smooth);
      move(q2, (cx, cy), 0.5, smooth);
      move(q3, (cx, cy), 0.5, smooth);
    }
    wait(0.15);
    // swap the four pieces for one solid disc (same colour)
    par {
      fade(q0, 0.3); fade(q1, 0.3); fade(q2, 0.3); fade(q3, 0.3);
      show(whole, 0.35);
    }
    pulse(whole);
    wait(0.4);

    // 4 — move as one unit
    say(cap, "now it moves as a single identity.", 0.25);
    par {
      fade(axX, 0.35); fade(axY, 0.35);
      cam((cx + 120, cy), 0.9, smooth); zoom(1.05, 0.9, smooth);
      move(whole, (cx + 260, cy), 1.1, smooth);
    }
    wait(0.35);

    // side boolean still lands on the right as the disc arrives
    say(head, "boolean keeps two colours", 0.25);
    say(cap, "A−B teal · B−A coral · then A∩B gold — never one muddy fill.", 0.25);
    par { show(bBox, 0.3); show(bCirc, 0.3); }
    wait(0.3);
    par { fade(bBox, 0.2); fade(bCirc, 0.2); }
    par { show(onlyBox, 0.4); show(onlyCirc, 0.4); }
    flash(onlyBox, teal); flash(onlyCirc, coral);
    show(labBool, 0.25);
    wait(0.4);
    show(bHit, 0.45); flash(bHit, gold);
    wait(0.7);

    par {
      fade(whole, 0.35);
      fade(onlyBox, 0.35); fade(onlyCirc, 0.35); fade(bHit, 0.35); fade(labBool, 0.35);
      cam((cx, cy), 0.6, smooth); zoom(1.0, 0.6, smooth);
    }
  }
}

step("contact") {
  seq {
    say(head, "contact points", 0.3);
    say(cap, "a solid dot hides the crossing. a dashed ring keeps it.", 0.3);
    par {
      show(crossH, 0.05); show(crossV, 0.05);
      draw(crossH, 0.5); draw(crossV, 0.5);
      cam((cx, cy), 0.7, smooth); zoom(1.55, 0.7, smooth);
    }
    show(badDot, 0.3);
    flash(badDot, coral);
    wait(0.55);
    fade(badDot, 0.25);
    show(goodRing, 0.05); draw(goodRing, 0.55);
    pulse(goodRing);
    wait(0.7);
    par {
      fade(crossH, 0.35); fade(crossV, 0.35); fade(goodRing, 0.35);
      cam((cx, cy), 0.7, smooth); zoom(1.0, 0.7, smooth);
    }
  }
}

step("settle") {
  seq {
    say(head, "fills for subjects. outlines for truth.", 0.35);
    say(cap, "shapecraft: the visual is true — never hide what you're proving.", 0.35);
    show(hero, 0.2);
    become(hero, finalForm, 1.0, smooth);
    pulse(hero);
    wait(1.6);
  }
}