manic — the manual
manic is a declarative language for visual explanation. You write a readable text file; Manic turns it into a smooth, deterministic animation. No timeline scrubbing and no hand-managed keyframes — you describe the objects, the relationships, and the story beats while the engine handles motion, continuity, layout, rendering, and repeatable output.
Manic powers visual explanations, animated lessons, simulations, technical diagrams, and creator-ready videos from one readable language. Use it for mathematics, olympiad geometry, calculus, linear algebra, statistics, probability, algorithms and data structures; physics and optics; machine learning and transformer internals; systems architecture and flowcharts; fractals, tilings, fields, and particle processes; SVG artwork and bundled assets; narrated lessons; or polished Reels and Shorts. The same language moves naturally between responsive 2D, textbook-style spatial diagrams, and genuine 3D scenes.
The vocabulary is broad, but the mental model stays deliberately small enough for a non-programmer to read: name what exists, state what it depends on, and describe what should happen. Creators can begin with a circle and a caption, then keep the same model as they progress to live proofs, simulations, neural networks, or cinematic 3D stories.
Manic was created by Anish Nath. Follow its development, examples, and new visual-language experiments on X: @anish2good.
The whole idea in 30 seconds
A manic file has two parts:
- The cast — the shapes on screen (a circle, a line, some text). You give each one a name.
- The script — what happens over time, called out by name: draw this, move that, flash it green.
title("Hello");
canvas("16:9");
circle(sun, (640, 360), 90); // the cast: a circle named `sun`
color(sun, cyan);
show(sun, 0.6); // the script: fade it in over 0.6s
pulse(sun); // then give it a little pulse
That’s the entire model. The rest of this guide builds outward from it—first ordinary shapes and motion, then reactive relationships, creator production, specialist kits, systems, simulations, and 3D. The complete generated vocabulary is available in the API reference.
How to read this book
Every section has a runnable sample and a short video of it playing, so
you see exactly what each word does. Copy any sample into a .manic file and:
manic yourfile.manic # live preview window
manic yourfile.manic --record out # render to out/out.mp4
Ready? Start with your first animation →
Making vertical social content? Take the production path directly: Create a polished Reel →
Choosing the visual surface? Manic defaults to full semantic colour on an exact black canvas; compare every option in Templates →.
Getting started
Let’s make the smallest real animation: a title fades in, a circle draws itself, and it pulses once.
Here primarily for vertical social video? Jump to Create a polished Reel, the complete Creator Kit workflow. Then use Reactive stories to keep equations, plots, diagrams, and captions synchronized across each explanation beat. Want spoken narration? See Voice — optional, one
voice(...)plusspeak("…")lines.
// getting started — one shape, drawn on, then a pulse.
title("Hello, manic");
canvas("16:9");
text(head, (cx, 140), "hello, manic");
color(head, cyan); size(head, 40); hidden(head);
circle(sun, (cx, cy), 110);
color(sun, magenta); stroke(sun, 5); glow(sun, 8); untraced(sun);
show(head, 0.5); // fade the title in
draw(sun, 1.2); // trace the circle on
pulse(sun); // a friendly pulse
wait(1.0);
▶ See it play:
What each line is doing
| line | meaning |
|---|---|
title("Hello, manic") | the window/file title (metadata) |
canvas("16:9") | the frame size — 16:9 is 1280×720 (see Colour & style) |
text(head, (cx, cy)…) | cast: a text entity named head at the canvas centre |
color / size / hidden | modifiers — style head, and start it invisible |
circle(sun, …) | cast: a circle named sun |
untraced(sun) | start with the stroke undrawn, ready to trace on |
show(head, 0.5) | script: fade head in over 0.5s |
draw(sun, 1.2) | script: trace sun’s outline on over 1.2s |
pulse(sun) | script: grow-and-settle attention pulse |
wait(1.0) | hold for a second at the end |
There is no template(...) line in this first sample, so it uses the full-colour
black template automatically. See Templates when you want
mono, paper, blueprint, terminal, or a tinted surface.
Two things worth internalising right away:
cx,cyare the canvas centre. manic gives youw,h,cx,cyfor free so you can place things without hard-coding pixels.(cx, cy)is always the middle.- The order of the cast doesn’t matter, but the script runs top-to-bottom.
show, thendraw, thenpulseplay one after another. To make things happen at the same time, you wrap them inpar { … }— that’s the Timing chapter.
Two ways to appear
Notice head uses hidden + show, but sun uses untraced + draw. That’s
the one gotcha worth learning early:
hidden+show→ a fade-in (good for text and filled shapes).untraced+draw→ a draw-on, like a pen tracing the outline (good for strokes, lines, plots).
Get those two pairs right and everything else clicks. Next: the shapes you can put on screen →
Create a polished Reel
Creator Kit v2 is the fastest path from an idea to a finished vertical video. It owns the responsive layout, safe areas, question hierarchy, answer cards, timer, reveal, creator footer, and end card. You provide the content and choose how restrained or energetic it should feel.
This chapter gives one dependable production recipe. Start here, then use the Creator reference and examples when you need every option.
The production recipe
| step | recommended starting choice | why |
|---|---|---|
| Format | canvas("9:16") | native vertical composition |
| Surface | omit the call or use template("black") | exact-black full-colour default; use mono for intentional grayscale |
| Safe area | safe=reels on the creator and quiz | protects important content from platform UI |
| Hierarchy | studio layout=media-first density=comfortable | question first, one focal visual, readable answers |
| Motion | motion=calm or motion=studio | purposeful movement without visual noise |
| Timing | 9–13 seconds for one quiz beat | enough time to read, think, and absorb the reveal |
| Close | signature footer plus one end-card CTA | consistent identity and one clear next action |
For Shorts or TikTok, change both safe-area declarations to safe=shorts or
safe=tiktok. The same source can also be reframed for 4:5, square, or
16:9; the Creator regions reflow automatically. Use --canvas 4:5,
--canvas square, or --canvas 16:9 at preview/record time so the file stays
unchanged. The Reactive stories
chapter shows the full workflow and the responsive manual-layout pattern.
1. Write for a phone, not a slide
A strong first Reel usually has:
- one question or promise that fits in one or two lines;
- one visual idea—an equation, diagram, image, chart, or simulation;
- three or four short answer choices;
- one accent colour and one motion personality;
- a brief explanation and a single call to action.
Prefer direct wording. Move supporting context into explain; do not make the
viewer read a paragraph before the timer starts. Use LaTeX for mathematical
notation so formulas stay crisp and compact.
Gold pattern: let one equation evolve
For a solution Reel, do not stack five complete formulas or cut to a blank screen between steps. Declare one equation and rewrite it in place:
equation(work, (cx, 520), `x^2+2x=3`, 54);
rewrite(work, `x^2+2x+1=4`, 0.85, smooth);
wait(0.8);
rewrite(work, `(x+1)^2=4`, 0.85, smooth);
The viewer’s eye can follow the terms that move while unchanged symbols stay
put. Manic matches rendered LaTeX by reading order, mathematical layout role,
and nested math-style depth; an exponent therefore cannot become a denominator
or a different level of an exponent tower merely because both use the same
digit. When a fraction, radical, or grouping changes the structure of one
side, that side cleanly leaves and reforms while the compatible side remains.
Simple replacements such as 2 → 3 are staged as leave-then-enter, avoiding a
temporary 23; pure additions can appear immediately. You still supply and own
every correct step. For phone video, use one meaningful
rewrite per narration beat, keep the equation in the media safe region, pause
after the important result, and place any related plot or diagram motion beside
the rewrite in par.
2. Let the format own layout
canvas("9:16");
template("black"); // optional: black is the DSL default
creator(me, "@anish2good name=Proof_Daily tagline=Think_then_prove \
yt=zarigatongy x=@anish2good web=8gwifi.org/manic \
accent=cyan secondary=magenta footer=signature cta=Save_and_share safe=reels");
quiz(q, "Your short question",
"studio layout=media-first density=comfortable motion=calm safe=reels accent=cyan");
Use layout=media-first when the visual is part of the question, stack for up
to four text-led answers, grid for four to six compact answers, and auto
when you want manic to decide. Pass the visual to figure(id) instead of tuning
its coordinates for each aspect ratio.
Temporarily add safezone(guide,"reels") while previewing a new design. It is a
visible diagnostic overlay, so remove it before the final record. The
safe=reels layout setting should remain.
3. Give reading, thinking, and payoff separate beats
The default run(q,10) is a good first draft. For a repeatable series, declare
the exact beat:
timing(q, "calm ask=1.1 options=1 think=5.5 reveal=0.75 hold=2.15 stagger=0.06");
timerstyle(q, "look=ring position=below direction=drain color=cyan finish=pulse");
run(q);
A useful starting range is 1–1.5 seconds for the hook, 4–6 seconds to think, 0.6–0.9 seconds for the reveal, and 1.5–2.5 seconds to hold the explanation. Adjust for the actual reading load; the right pace is the one that remains clear on a phone.
timing owns choreography and timerstyle owns appearance. Change the ring to
bar, segments, ticks, number, pulse, or none without moving a single
beat. With explicit phases, always use run(q)—adding run(q,dur) would create
a second competing duration.
Design shortcut: keep the default ring for most educational Reels. Use a bar for a longer process, segments for staged challenges, ticks for technical work, and pulse only for genuinely urgent moments.
4. Build a recognisable close
Call socials(profile) before the main animation so the selected footer is
present throughout. endcard(profile) creates a hidden final lockup; fade the
content, then reveal it for about one second or more.
Keep the CTA singular: “Save this”, “Try the next one”, or “Follow for more”. A
creator profile is reusable across a whole series, so brand decisions live in
one line rather than every scene. Inside a profile spec, use underscores where
a value needs spaces: name=Proof_Daily cta=Save_and_share.
The endcard deliberately uses an editorial action row rather than a fully neon-filled button: a quiet inset surface keeps the creator name and final idea dominant, while a small two-colour rail and arrow chip carry the accent palette. On a black template, reserve bright colour for those details and keep CTA copy short enough to read in one glance.
For a channel-link row without external assets, choose footer=social and set
the platform values directly: yt=zarigatongy x=@anish2good web=8gwifi.org/manic. Manic draws matching normalized icons and keeps the text
inside the responsive footer. Use two or three identities for the clearest
phone-size close; a larger platform set automatically becomes icon-only.
Complete Reel
This example uses exact phases, LaTeX media, the Reels safe area, a restrained native timer, a signature footer, timeline markers, and a final end card:
// A production-ready Creator v2 Reel: safe layout, exact pacing, one focal
// equation, restrained motion, reusable branding, markers, and an end card.
title("Perfect Reel — Angle Ratio");
canvas("9:16");
template("mono");
watermark(manicMark, (w*0.895-100, h*0.075+24), "Made With Manic");
creator(me, "@anish2good name=Proof_Daily tagline=Think_then_prove yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Save_and_share safe=reels");
quiz(q, "Triangle angles: 2:3:4. What is the largest?",
"studio layout=media-first reveal=fade density=comfortable motion=calm safe=reels accent=cyan");
option(q, `$60^\circ$`);
option(q, `$80^\circ$`, correct);
option(q, `$90^\circ$`);
option(q, `$100^\circ$`);
explain(q, `The parts total $9$, so $x=20^\circ$ and $4x=80^\circ$.`, "Angle sum");
equation(prompt, (cx, 650), `2x+3x+4x=180^\circ`, 70);
color(prompt, cyan);
figure(prompt);
hidden(prompt);
// Exact 10.5-second quiz beat. The timer look can change independently.
timing(q, "calm ask=1.1 options=1 think=5.5 reveal=0.75 hold=2.15 stagger=0.06");
timerstyle(q, "look=ring position=below number=inside direction=drain size=medium thickness=1.1 color=cyan track=dim label=THINK font=mono finish=pulse");
socials(me);
endcard(me, "cta=Save_and_share");
mark("hook");
par {
run(q);
show(prompt, 0.5);
}
mark("endcard");
par {
fade(q.parts, 0.45);
fade(prompt, 0.45);
fade(me.footer, 0.45);
}
show(me.endcard, 0.6);
wait(1.4);
Preview and export
manic check examples/perfect-reel.manic
manic check examples/perfect-reel.manic --canvas all
manic examples/perfect-reel.manic
# Inspect representative phone frames: hook, countdown, and answer hold.
manic examples/perfect-reel.manic --still 0.8
manic examples/perfect-reel.manic --still 6.0
manic examples/perfect-reel.manic --still 10.0
# Vertical render with only your Creator profile branding.
manic examples/perfect-reel.manic --record out --preset reel --no-brand
Omit --no-brand if you also want the Manic intro and watermark. Recording
writes markers.json; use its mark(...) timestamps to align narration, music,
or captions in an editor.
Final phone-size review
Before publishing, check:
- Does
manic check your-file.manic --canvas allpass portrait, feed, square, and landscape? See Visual checks for fixes. - Can the hook be understood during the first beat?
- Is every important word and the CTA inside the selected safe area?
- Is there one obvious focal point, with no competing motion?
- Are the choices short, distinct, and marked with exactly one correct answer?
- Does the timer contrast with its track without overpowering the content?
- Does the explanation remain visible long enough to read aloud?
- Does the final card ask for one action and hold long enough to register?
- Are logos, photos, fonts, narration, and music yours or licensed for use?
Once those pass, keep the same profile, motion, colour, and closing pattern across the series. Consistency will make the next Reel both faster to produce and easier to recognise.
Next: Turn one polished scene into a reactive story →
Reference: Explore every Creator control and format variant.
Reactive stories — change the idea, not the scene
A polished Reel feels continuous: the equation changes, the point moves, the
graph responds, and the caption explains the same idea without cutting to a
blank screen. In Manic, build that as one persistent visual world and move it
forward with named step blocks.
This is the creator mental model:
- Declare the cast once — equation, plot, diagram, labels, caption, profile.
- Give each story beat a short, meaningful name.
- Inside that step, describe only what changes.
- Leave everything else alone; it remains on screen automatically.
The result is easier to write, easier to revise, and easier for a viewer’s eye to follow.
One story, three layers
Think of a reactive video as three simple layers:
| layer | Manic vocabulary | creator decision |
|---|---|---|
| World | shapes, equations, plots, text, Creator profile | What should remain recognisable throughout? |
| Story | step("question"), step("explain"), step("result") | What changes in the viewer’s understanding? |
| Motion | rewrite, to, draw, say, show, fade | How should that change become visible? |
step does not introduce a new kind of animation. It gives ordinary Manic
verbs a named conceptual boundary. Its children begin together, its duration is
the longest child, and any entity not mentioned persists.
step("measure-slope") {
rewrite(work, `f'(x)=0.70x`, 0.90, smooth);
to(tangent, x, 2.8, 3.20, smooth);
to(rate, x, 2.8, 3.20, smooth);
say(caption, "The tangent and its slope update together.", 0.40);
}
wait(0.60);
Here the formula, tangent, live value, and explanation share one beat. The curve and every other unmentioned object remain exactly where they were.
A creator-first workflow
1. Start with the promise
Before writing motion, write the story as four or five beat names:
question → measure-slope → find-the-flat-point → see-the-derivative → takeaway
Use names that describe understanding, not implementation. show-blue-line is
fragile; see-the-derivative still makes sense after the design changes.
For a short educational Reel, a reliable shape is:
- Hook: pose one visual question in the first two seconds.
- Explore: change one or two connected representations.
- Payoff: make the important relationship visible.
- Takeaway: hold one memorable sentence or result.
The same structure works for geometry proofs, physics simulations, chemistry reactions, data stories, algorithms, and product explainers. Reactive is a story pattern, not a mathematics-only feature.
2. Declare the world once
Give every continuing visual a stable id. Do not create equation1,
equation2, and equation3 when the viewer should perceive one evolving
equation. Keep work and rewrite it.
equation(work, (cx, 390), `f(x)=0.35x^2`, 51);
plot(curve, (cx, 1050), 115, 62, "0.35*x*x", (-3.5, 3.5));
text(caption, (cx, 1450), "How steep is this curve?");
Stable identity is what makes continuity possible. It also means a later style change, position adjustment, or wording fix happens in one place.
3. Change only what matters
Inside a step, pair the representations that explain one another:
step("find-the-flat-point") {
rewrite(work, `f'(0)=\textcolor{lime}{0}`, 0.85, smooth);
to(tangent, x, 0, 1.80, smooth);
to(rate, x, 0, 1.80, smooth);
show(vertex, 0.40);
say(caption, "At the vertex the tangent is flat.", 0.40);
}
Do not fade and rebuild the whole screen for every sentence. Preserve context,
move the smallest meaningful parts, and let the viewer compare before and
after. For equations, rewrite retains safely matched LaTeX pieces and brings
only the changed pieces in or out. Matching understands coarse mathematical
layout roles and nested math-style depth, so the 2 in x^2 cannot jump into
the denominator of \frac{b}{2a}, and one level of an exponential tower cannot
become another. The same rule protects logarithm bases, integral limits, and
derivative orders. Repeated symbols keep their reading order, so matrices and
formulas containing several zeros or variables do not send copies across one
another. When one equation side gains a fraction, radical, or grouping
structure, that side leaves before its replacement enters; the compatible
side and equality stay visible. For a globally incompatible change, Manic uses
a staged whole-equation dissolve with only a short, dim overlap. Ordinary glyph
replacements follow the same reading rule—the old value leaves before its
successor becomes clear—while pure additions can still enter immediately.
There is no extra mode to choose.
Manic animates the states you author; it does not solve or verify the maths. Write each intermediate LaTeX state exactly as it should appear.
For the cleanest teaching motion, keep genuinely unchanged work unchanged in the next LaTeX string. A stable right-hand side, fraction, or matrix remains recognisable while the edited side changes. Large changes are still valid—the engine will favour a clean dissolve over a misleading glyph scramble.
4. Give the result room to land
A step ends when its longest animation ends. Add a short wait after an
important step so the viewer can read the settled frame:
step("result") {
rewrite(work, `\text{slope of }f=\textcolor{magenta}{f'}`, 0.95, smooth);
pulse(curve, 0.70);
pulse(derivative, 0.70);
say(caption, "The derivative is the curve of all local slopes.", 0.40);
}
wait(1.80);
Use seq { ... } inside a step only when that conceptual beat genuinely needs
an internal order. Use par for anonymous choreography; use step when the
beat should have a name and meaning.
Creator polish that matters
- Keep one focal change per step. Several synchronized views are fine when they all explain that one change.
- Reuse semantic colours: for example, cyan for the original function, magenta for its derivative, and lime for the key result.
- Put captions in a stable safe region. Let the words change with
sayinstead of making the viewer search for a new text block. - Prefer smooth movement and local equation rewrites over full-screen fades.
- Write for the final phone size. Short captions and one- or two-line equations beat dense slides.
- Keep the Creator profile and social footer consistent across a series, but do not let branding compete with the explanatory stage.
- Preview the hook, busiest transition, result, and end hold as still frames before recording the full video.
Named steps are editing markers
Every step name is automatically exported at the step’s start in
markers.json and becomes a first-class preview/recording boundary. Inspect the
story or open one beat directly:
manic stages examples/reactive-world.manic
manic examples/reactive-world.manic --stage find-the-flat-point
Those semantic boundaries make it easier to:
- align narration, music, captions, and sound effects;
- review or seek directly to a conceptual beat;
- identify a clean hook, explanation, result, or takeaway;
- export one stage or an inclusive stage range without copying timestamps.
Names must be non-empty, unique, and top-level. If you only need a timestamp
without a reactive state change, use mark("name") instead.
Publish one story in four formats
A named-step story can now be reframed at render time without editing its
source. Use --canvas to override the logical canvas before Manic calculates
w, h, cx, cy, macros, and build-time layout branches:
manic examples/reactive-multiformat.manic --canvas portrait --record out-reel --preset reel
manic examples/reactive-multiformat.manic --canvas 4:5 --record out-feed
manic examples/reactive-multiformat.manic --canvas square --record out-square
manic examples/reactive-multiformat.manic --canvas 16:9 --record out-lesson
The story’s named steps, equation continuity, timing, and identity remain the
same. Only the logical layout changes. --canvas is separate from --preset:
the canvas controls composition; the preset controls recording quality, frame
rate, container, and engine branding.
Creator Kit regions already read the active canvas. For a hand-composed scene,
prefer w, h, cx, and cy, then use a small build-time branch when the
composition should genuinely change shape:
if h > 1.45*w {
// vertical stack for a Reel or Short
}
else if w > 1.25*h {
// explanation left, visual stage right
}
else {
// compact square / 4:5 feed composition
}
An override cannot make hard-coded coordinates responsive by itself. The author still decides the useful composition for each shape of screen; Manic ensures every branch receives the correct dimensions before construction and keeps the semantic story unchanged.
Complete examples to learn from
- Reactive world — the best compact starting point: one curve, one equation, one tangent, and five named steps.
- Reactive multi-format — one source and one named timeline, with vertical, feed, square, and landscape compositions.
- Parameter journeys — one visible value drives a plot, analysis views, geometry, and a live readout without rebuilding the world.
- Reactive integral — an equation, integrand, antiderivative, and visual verification in one continuous world.
- Reactive notation — mathematics, physics, chemistry, biology, logic, and custom LaTeX notation.
- Math journey — a playful long-form progression from Class 1 arithmetic to PhD-level notation.
- Quadratic continuity — a focused benchmark for retaining unchanged equation pieces.
Start by copying reactive-world.manic, rename its steps around your idea, and
replace one representation at a time. Keep the world; change the understanding.
Next: Navigate and export the story by stage →
Story stages — edit the idea, not the timestamp
A creator usually thinks in beats—question, intuition, experiment,
proof, takeaway—rather than 6.35 seconds. Manic uses the names already
written in step(...) as the editing and publishing structure for the movie.
There is no second timeline file to maintain.
step("question") {
show(prompt, 0.4);
say(caption, "Where does the slope become zero?", 0.35);
}
wait(0.8);
step("experiment") {
to(tangent, x, 0, 1.8, smooth);
to(rate, x, 0, 1.8, smooth);
}
wait(0.7);
step("takeaway") {
rewrite(work, `f'(0)=0`, 0.8, smooth);
}
wait(1.5);
The wait after a step belongs to that stage. This makes a stage export include
the transition and the time the viewer needs to absorb its settled result.
The next stage begins exactly where its next step begins.
See the story before opening a window
manic stages examples/reactive-world.manic
The report lists every stage’s start, end, and complete duration:
stages — examples/reactive-world.manic (5 stages, 13.55s authored)
# stage start end duration
1 question 0.00s 2.15s 2.15s
2 measure-slope 2.15s 5.95s 3.80s
3 find-the-flat-point 5.95s 8.40s 2.45s
4 see-the-derivative 8.40s 10.80s 2.40s
5 takeaway 10.80s 13.55s 2.75s
Use this before recording to catch a rushed hook, an overlong setup, or a takeaway without enough reading time.
Preview one stage
manic examples/reactive-world.manic --stage find-the-flat-point
Manic evaluates the complete persistent world at the stage’s real start, then limits playback and scrubbing to that stage. Nothing is reconstructed as a separate scene, so incoming equation, plot, diagram, and parameter state remain correct.
The live player adds a stage strip above the transport bar:
- click a stage segment to jump to it;
- use
1–9to jump to visible stages; - press
Rto restart at the selected range’s beginning; - drag the progress bar to scrub only inside the selected range;
- read the current stage and selected range in the HUD.
Record a single stage
The same flag selects the recording range:
manic examples/reactive-world.manic \
--stage see-the-derivative \
--record out-derivative \
--preset reel
For a clean editing clip without the preset’s branding intro/outro:
manic examples/reactive-world.manic \
--stage see-the-derivative \
--record out-derivative \
--preset test \
--no-brand
Export several stages
Use an inclusive named range when a clip should carry a short story arc:
manic examples/reactive-world.manic \
--from-stage measure-slope \
--to-stage takeaway \
--record out-slope-to-takeaway
--to-stage takeaway includes the whole takeaway stage. With only
--from-stage, recording continues through the authored ending. With only
--to-stage, it begins at the start of the movie.
Named ranges and numeric --from/--to are intentionally separate. Use stage
names for story editing and numeric seconds only for a deliberately precise
technical trim. Unknown names report the available stages and suggest a close
match.
Recording metadata stays useful
markers.json now includes:
- the selected source range;
- clipped stage intervals with relative
t,end, andduration; - each stage’s original
source_t; - marks and sections filtered to the exported clip and shifted to clip time.
That means narration, captions, music cues, and downstream editing tools can work from zero-based clip timing without losing the position in the source story.
Combine stages with output formats
Stage selection and canvas selection are independent:
manic examples/parameter-journeys.manic --stage takeaway --canvas portrait --record out-reel
manic examples/parameter-journeys.manic --stage takeaway --canvas square --record out-square
manic examples/parameter-journeys.manic --stage takeaway --canvas landscape --record out-lesson
The named idea stays the same while the responsive layout changes. Before the
batch render, run
manic check examples/parameter-journeys.manic --canvas all to review every
stage in every supported shape.
Choosing useful names
- Name the change in understanding:
see-the-derivative, notshow-pink-line. - Keep a short video to roughly three to six stages.
- Put several synchronized changes inside one stage only when they explain the same idea.
- Add a deliberate
waitafter the important result. - Use
mark("cue")for an editor timestamp that is not a story stage. - Use
section("Part Two")when the video itself needs a visible chapter card.
Start with Reactive world, run
manic stages examples/reactive-world.manic, and preview its
find-the-flat-point stage.
Next: Review every format before publishing →
Visual checks — review every format before publishing
A responsive Manic story can share its content and timing across a Reel, feed post, square video, and landscape lesson. Before recording those versions, run one publishing audit:
manic check examples/reactive-multiformat.manic --canvas all
Manic rebuilds the source at four logical canvases—portrait, 4:5 feed, square, and 16:9 landscape—then checks the settled frame of every named story stage. A clean result looks like this:
ok — examples/reactive-multiformat.manic [portrait]: visual checks passed
ok — examples/reactive-multiformat.manic [feed]: visual checks passed
ok — examples/reactive-multiformat.manic [square]: visual checks passed
ok — examples/reactive-multiformat.manic [landscape]: visual checks passed
ok — examples/reactive-multiformat.manic: visual audit passed all 4 formats
Use this after the story and layout feel right, but before spending time on the final render.
What the first audit catches
Visual-check v1 reports four common publishing problems:
| check | what it protects |
|---|---|
| Canvas bounds | text, equations, images, or primary objects outside the frame |
| Creator safe area | content underneath platform controls or too close to a protected edge |
| Content overlap | two substantial text, equation, or image boxes competing for the same space |
| Readability | text or rendered notation that is too small for the selected format |
Each message names the format, story stage, time, and responsible entity, then suggests a practical fix:
warning [square · takeaway @ 8.45s]: `caption` overlaps `result`
entities: `caption` and `result`
suggestion: separate the entities, shorten/wrap the text, or reflow this format
The command exits unsuccessfully when it finds either an error or warning, so
it can guard a publishing script or CI job. Ordinary manic check file.manic
remains the fast parse-and-validation check. To validate only one responsive
layout, keep using manic check file.manic --canvas square.
Why named steps improve the result
The audit reviews the settled state at the end of each step("name") (or
marker-defined stage), not every frame of a transition. That avoids treating a
deliberate entrance, exit, or equation rewrite crossfade as a layout failure.
It also makes a diagnosis useful: takeaway tells you which idea needs fixing,
not merely that something happened at 8.45 seconds.
If a file has no named stage, Manic checks its final authored frame. For a
creator story, prefer meaningful names such as question, experiment,
proof, and takeaway; they help editing, seeking, publishing, and review.
Fixes that stay reusable
When the audit finds a problem, preserve one responsive source:
- Position with
w,h,cx, andcyinstead of copying pixel coordinates between files. - Give continuing content stable ids so a message points to the same entity in every stage.
- Shorten copy or use
wrap(id, width)before reducing the type size. - Use one small
if h > 1.45*w { ... } else if w > 1.25*h { ... } else { ... }layout branch when the composition genuinely needs to reflow. - Choose the correct Creator
safe=shorts|reels|tiktok|cleanprofile. Usesafezone(...)while previewing the protected area; it is a design guide, not content for the finished video. - Leave enough room for the busiest settled stage, not only the opening frame.
The gold reference is
reactive-multiformat.manic. It uses one
timeline and passes the audit in all four formats.
What still needs human review
Visual-check v1 is intentionally conservative. It does not yet judge collision paths during transitions, camera-transformed world or 3D bounds, detached labels and links, reading speed, or whether an equation agrees mathematically with a plot. It also cannot decide whether the hierarchy is beautiful or the hook is compelling.
After the automated audit passes, preview the hook, busiest transition, result, and end hold at phone size. The command catches mechanical layout mistakes; the creator still owns clarity, rhythm, and taste.
Next: Animate one value through many connected views →
Parameter journeys — one value, many connected views
A parameter journey shows how an idea behaves across a family of cases. Instead of rebuilding the scene for “negative”, “zero”, and “positive”, declare one visible value, connect it to the existing world once, and animate only that value.
parameter(a, (cx, 140), -1.2, -1.5, 1.5, "a", 2);
plot(curve, (cx, 720), 110, 58, "x*x", (-3.3,3.3));
circle(point, (cx, 1200), 22);
counter(magnitude, (cx, 1360), 0, 2, "a² = ", "");
bind(a, curve, formula, "0.22*p*x*x");
bind(a, point, x, w*0.18, w*0.82);
bind(a, point, scale, "0.78+0.22*abs(p)");
bind(a, magnitude, value, "p*p");
step("flatten") {
to(a, value, 0, 2.2, smooth);
}
step("opens-up") {
to(a, value, 1.25, 2.4, smooth);
}
The plot, point, scale, number, and native parameter control move on the same continuous clock. Nothing is replaced and no scene construction is duplicated.
The three-part mental model
| part | vocabulary | creator decision |
|---|---|---|
| Expose | parameter | Which value should the viewer follow? |
| Connect | bind | Which representations explain that value? |
| Travel | step + to(parameter,value,…) | Which cases tell the clearest story? |
This pattern is domain-neutral. The parameter can be a quadratic coefficient, damping factor in a formula plot, probability, sample-size readout, geometric position, opacity comparison, colour phase, or any other scalar that makes the idea easier to see.
Declare the visible parameter
parameter(id, (x,y), initial, min, max, ["label"], [decimals]);
parameter produces a typeset numeric readout and a compact track/dot widget.
The initial value must be inside a finite min..max range. Animated values are
clamped to that range, which prevents an accidental journey from leaving the
meaningful domain.
The whole widget is tagged id.widget, so it can enter with the rest of the
story:
hidden(a.widget);
show(a.widget, 0.4);
It is an authored animation control, not an interactive slider in the exported
video. Use ordinary to to move it; stateless evaluation keeps preview seeking
and recording deterministic.
Connect with a responsive range
The simplest binding maps the parameter’s declared minimum and maximum onto two output values:
bind(a, point, x, w*0.18, w*0.82);
bind(a, label, opacity, 0.25, 1);
bind(a, arrow, angle, -25, 25);
This form is especially useful for positions because the endpoints are normal
Manic expressions. w, h, cx, and cy are evaluated before the binding is
created, so the same source can give each output format an appropriate range.
Connect with a formula
Use a string formula for a nonlinear relationship:
bind(a, point, scale, "0.8+0.2*abs(p)");
bind(a, result, value, "p*p");
bind(damping, wave, formula, "exp(-p*abs(x))*cos(4*x)");
Inside a binding formula, p is the live parameter. A plot-formula binding also
provides x as the plot coordinate. The usual arithmetic, constants, and
functions—sin, cos, exp, sqrt, abs, round, and others—work here.
Bindable properties are x, y, opacity, scale, angle, hue, value,
trace, and a plot’s formula. A value target must be a counter. A formula
target must be a plot.
When a plot formula changes, its existing tangent, normal, slope, area,
integral, and moving mark views receive the new function before they recompute.
That is the important reactive promise: the analysis stays mathematically tied
to the curve the viewer sees.
Choose cases, not frames
Use named steps to describe meaningful cases:
step("underdamped") {
to(damping, value, 0.12, 2.0, smooth);
say(caption, "The oscillation survives for longer.", 0.35);
}
step("strong-damping") {
to(damping, value, 0.85, 2.0, smooth);
say(caption, "The same response now settles quickly.", 0.35);
}
The stage names should explain the cases to a human. The parameter supplies the
continuous motion between them; bind keeps every connected view synchronized.
Creator design tips
- Follow one primary parameter per short scene. Several controls are possible, but the audience should know which value is changing.
- Connect two or three complementary representations—a formula and plot, a geometry and measurement, or a simulation curve and readout.
- Give the parameter a meaningful range. Avoid travelling through singular or irrelevant values merely because the engine allows them.
- Use responsive range bindings for positions and formula bindings for meaning.
- Hold the important cases briefly. Smooth motion reveals the trend; the hold lets the viewer name it.
- Keep stable ids. Parameter journeys are an extension of the persistent-world model, not a reason to create replacement scenes.
- Run
manic check FILE.manic --canvas allafter the journey is complete.
Complete example
Parameter journeys is the polished reference:
one coefficient drives a quadratic, live tangent and slope, moving point,
changing scale, and a² readout across portrait, feed, square, and landscape.
Current bindings target 2D entity properties and formula plots. Constructors that perform expensive build-time work—such as re-simulating a full physics system or changing the number of generated objects—remain build-time choices; represent those journeys with a formula/readout today rather than implying that Manic silently recomputed the model.
Next: Meet the shapes that make up the persistent world →
Shapes — the cast
Everything on screen is an entity with a name (its first argument). You declare shapes once; the name is how you address them later in the script.
The six primitives
Each line below is the whole call — copy it and tweak the numbers.
| shape | write | draws |
|---|---|---|
| circle | circle(sun, (cx, cy), 90); | a circle, radius 90 — filled + outlined by default |
| rect | rect(box, (cx, cy), 200, 120); | a rectangle — filled + outlined by default |
| line | line(edge, (100, 100), (400, 300)); | a line from point to point (stroke only) |
| arrow | arrow(v, (100, 400), (400, 400)); | a line with an arrowhead at the end (stroke only) |
| dot | dot(p, (cx, cy), 8); | a small filled disc (no outline) — hides crossings |
| text | text(cap, (cx, 640), "hello"); | a text label anchored at a point |
Plus composites: polygon / sector / annulus / pie (filled by default),
arc / brace / ellipse (stroke only), and boolean regions
(union / intersect / …).
▶ See it play: SHAPECRAFT — fill vs
outlined, the cast, palette → become, spin-wound morph, a disc that
splits into four colours → reunites → moves, and two-colour booleans.
Ordinary text is portable and shaped automatically
text, say, captions, labels, and kit-generated words use Manic’s embedded
font engine. No machine font is consulted, and there is no font-selection DSL:
choose only the semantic mono, bold, or display look. Manic handles
fallback, ligatures, combining marks, bidirectional order, wrapping, reveal,
rotation, glow, camera zoom, and recording from one shaped layout.
Arabic and Devanagari are bundled script examples, so these work unchanged in preview, backend recording, and the browser build:
text(arabic, (cx, 300), "التعلّم يجعل الأفكار واضحة");
text(hindi, (cx, 420), "ज्ञान से प्रकाश मिलता है");
Use equation or inline $…$ for mathematical typesetting. Run manic check
before rendering; an unsupported Unicode cluster names the entity and code
point instead of silently drawing a replacement box. Colour emoji remains
intentionally unsupported until colour-font rendering is portable.
Plus a few composite helpers built from those: polygon, arc/sector, brace/
bracelabel, caption (a row of words), and support(id, (cx,cy), [len], ["dir"])
— the hatched wall / ceiling / floor for mechanics & textbook diagrams ("dir" is
the open side: "down" ceiling, "up" floor, "left"/"right" walls).
Two more helpers cover scenes that would otherwise need dozens of manual dots and endpoints:
circle(glass, (400,300), 100);
particles(bubbles, glass, 24, 5, 7); // default random layout
rect(tank, (750,300), 180, 160);
particles(samples, tank, 24, 4, 11, "grid");
link(pipe, glass, tank, 35); // 35 px of curve; 0 is straight
circle(orbit, (980,300), 90);
particles(ticks, orbit, 24, 4, 13, "ring");
particles works inside circles and rectangles. Its bare id addresses every
child dot, so hidden(bubbles) and recolor(bubbles, fg) affect the whole
group. Use the optional "grid" layout for an ordered rectangular state, then
arrange(samples, tank, "random", 2, smooth) to move the same dots into a
typical random state. arrange may target a larger container for expansion and
can return to "grid" exactly. A circle plus "ring" gives the particles an
ordered radial endpoint. link meets shapes at their boundaries and stays
attached when either endpoint moves.
Points are (x, y) in pixels, origin top-left, y increasing downward. Use
cx, cy, w, h to stay canvas-independent.
For display mathematics, equation accepts LaTeX in backticks. Standard
\textcolor can emphasize individual terms with Manic’s semantic palette; the
colors automatically follow the selected template:
equation(step, (cx, 620),
`\textcolor{magenta}{\mathrm{slope}}=
\frac{\textcolor{cyan}{\mathrm{rise}}}
{\textcolor{gold}{\mathrm{run}}}`, 40);
Leave a term uncolored to use the template foreground. A single-color equation
can still be styled as one entity with color and animated with recolor.
For a derivation, keep that same equation id and animate only the change:
rewrite(step, `\mathrm{slope}=\frac{\mathrm{rise}}{\mathrm{run}}`, 0.9, smooth);
rewrite(step, `\mathrm{slope}=\frac{x}{1}`, 0.9, smooth);
Manic matches the rendered LaTeX parts; it does not perform algebra. This makes the verb generic across algebra, calculus, physics, sums, fractions, radicals, and creator scenes while keeping each settled formula exact.
// the six primitive shapes, drawn on together.
title("Shapes");
canvas("16:9");
text(t, (cx, 90), "six primitives"); color(t, cyan); size(t, 32); hidden(t);
circle(c, (240, 380), 80); color(c, cyan); stroke(c, 4); untraced(c);
rect(r, (470, 380), 150, 150); color(r, magenta); stroke(r, 4); untraced(r);
line(l, (640, 300), (820, 460)); color(l, lime); stroke(l, 4); untraced(l);
arrow(a, (900, 460), (1040, 300)); color(a, cyan); stroke(a, 4); untraced(a);
dot(d, (1110, 380), 12); color(d, magenta); hidden(d);
text(lbl, (640, 620), "circle · rect · line · arrow · dot · text");
color(lbl, dim); size(lbl, 24); hidden(lbl);
show(t, 0.5);
par { draw(c); draw(r); draw(l); draw(a); show(d); }
show(lbl, 0.5);
wait(1.2);
▶ See it play:
Modifiers — style a shape at t = 0
A shape starts plain. Modifiers change how it looks before the animation begins. They take the entity name first, then a value:
| modifier | effect | example |
|---|---|---|
color(id, c) | fill / stroke colour | color(sun, cyan); |
stroke(id, w) | line thickness | stroke(sun, 4); |
dashed(id, [dash], [gap]) | dashed path or outlined circle (defaults 16/10 px) | dashed(mark, 4, 3); |
gradient(id, c1, c2, ..., [mode]) | multi-stop gradient: along a path stroke, a linear (angle°) / radial fill, or a computed "speed"/"curvature" quantity on strokes | gradient(wave, blue, cyan, gold, 270); |
size(id, n) | text size | size(cap, 30); |
glow(id, n) | neon halo strength | glow(sun, 8); |
opacity(id, 0..1) | transparency | opacity(sun, 0.5); |
filled(id) / outlined(id) | turn fill / outline on | filled(box); |
hue(id, deg) | colour by an angle (0–360) — per-entity colour in loops | hue(seg, 200); |
z(id, n) | draw order (higher = on top) | z(box, 5); |
sticky(id) | pin to the screen so it stays put through a cam/zoom (a HUD) | sticky(caption); |
Textbook diagrams: prefer
outlined(or a thinarc) for angle marks and “regions of interest.” Afilleddisc or sector sitting on top of axes and construction lines hides what’s underneath — even at low opacity. Reserve solid fills for backdrop wells behind the diagram, not overlays.
circledefaults to filled + outlined (neon UI). For math constructions always pairoutlined(id)—outline(id, color)alone only recolors the rim and leaves the fill on. Contact points that land on crossings should be open / dashed rings (outlined+dashed), notdot— a solid marker paints over the intersection you are trying to show.
And two that decide how a shape first appears:
| modifier | pairs with | gives |
|---|---|---|
hidden(id) | show(id) | a fade-in |
untraced(id) | draw(id) | a draw-on (pen tracing the outline) |
Colours are a fixed palette:
fg,void,cyan,magenta,lime,gold,red,orange,blue,teal,violet,coral,indigo,mint,dim,panel. For a computed colour (say, one per item in a loop) usehue(id, degrees). More in Colour & style.
Naming things in a loop
When you make many shapes with a for loop, give each a unique name with
interpolation — {expr} glued to the name:
for i in 0..5 {
dot(p{i}, (200 + i*180, cy), 8); // p0, p1, p2, p3, p4
}
That’s your cast. Now let’s make it move → Verbs.
Verbs — bringing it to life
Verbs are the script. Each one names an entity and animates it. They run
top-to-bottom, one after another (use par for simultaneous).
Almost every verb takes two optional trailing arguments:
move(sun, (900, 400), 0.8, smooth);
// ^dur ^easing
[dur]— how long, in seconds (there’s a sensible default).[ease]— the motion curve:linear,smooth,in,out,back,bounce,elastic,spring(see Colour & style).
Reveal & hide
draw(sun, 1.2); // -> trace a stroke on (needs `untraced` first)
erase(sun); // -> the reverse: un-draw it
show(cap, 0.5); // -> fade in (needs `hidden` first)
fade(cap); // -> fade out
type(cap); // -> typewriter: reveal text character by character
Attention
flash(sun, cyan); // -> flash to a colour, then restore
pulse(sun); // -> quick grow-and-settle "look here"
shake(sun); // -> horizontal shake, an "error/no" gesture
spin(sun, 360); // -> spin about its centre
Motion
move(p, (900, 400)); // -> glide to an absolute point
shift(p, (0, -120)); // -> move by a delta (relative)
scale(r, 1.4); // -> animate uniform scale to 1.4x
rotate(r, 45); // -> rotate by 45 degrees
grow(arrow, (500, 200));// -> animate a line/arrow endpoint (draws or retargets)
cycle(x, y, z, 0.8, 90, smooth); // -> x→y→z→x along arcs
cycle(a, b, c, …, [dur], [arc], [ease]) moves every entity into the next
one’s position and the last into the first. The path arc is in degrees and
defaults to 90; pass 0 for straight paths. Repeated calls compose, making it
useful for symbol rearrangements, card carousels, and CyclicReplace-style moves.
Relationship motion
Three verbs cover motion that would otherwise require many coordinated keyframes:
attach(label, marker, (0,-36)); // label follows marker
travel(marker, route, 2, smooth);
become(marker, nodeBlueprint, 0.8); // same id, new visual state
attach(label, none); // release at the settled position
turn(nodeGroup, marker, 20, 0.6, out);// shared group pivot
attach is persistent until released. become interpolates compatible shapes
and safely crossfades other pairs, always settling on the exact declared
blueprint. turn accepts one entity or a tag and rotates its members around a
point or entity pivot. See Motion graphics for the full
design rules and a copyable V2 Reel.
Ambient motion and path flow
particles(bubbles, glass, 24, 5, 7);
link(pipe, glass, tank, 35);
untraced(pipe);
par {
wander(bubbles, 6); // always stays inside glass
seq { draw(pipe); flow(pipe, 1); }
}
wander is deterministic: the same optional particle seed gives the same
placement and motion in preview and in the final recording. It occupies the
duration you give it, so run it in par with the story it should accompany.
flow sends a temporary luminous pulse over any line, arrow, curve, spline,
arc, or tracked link; it is useful for a signal, energy, traffic, or simply
directing attention.
travel(marker, path, dur, smooth) moves the actual marker once along that
same kind of path and leaves it at the endpoint. Use flow for temporary
emphasis and travel when object identity and the stopping point matter:
plot(curve, (180,620), 90, 140, "1-exp(-x)", (0,4));
dot(marker, (180,620), 7);
par { draw(curve, 2, out); travel(marker, curve, 2, out); }
For a persistent layout change, keep the same particle ids:
rect(chamber, (540,500), 600, 260);
particles(gas, chamber, 48, 5, 17, "grid");
arrange(gas, chamber, "random", 2.5, smooth);
arrange(gas, chamber, "grid", 2.5, smooth); // exact ordered return
circle(orbit, (900,500), 150);
arrange(gas, orbit, "ring", 1.2, smooth); // move and stop radially
arrange also accepts a different circle/rectangle container, which makes a
free expansion readable without moving every dot by hand. A random layout
uses independent seeded curved routes: the result is reproducible and
scrubbable, but does not look like every particle followed the same ruler.
For a complete creator-oriented treatment of persistent motion, arrival,
settling, and the difference between travel and flow, see
Motion graphics — move ideas, not layers.
For shape continuity, morph(a,b) preserves topology: graph/line paths remain
open, while circles and other closed outlines remain closed. This prevents an
unwanted diagonal chord when a plot becomes a connector or guide line.
For a persistent comparison style, use dashed(id, [dash], [gap]) before the
timeline. It is a base Manic modifier—not a calculus feature—so the same
16/10-pixel default pattern works on a plot, guide line, link, arrow, curve,
spline, coil, or plain arc. Increase both values for a calmer large-format dash;
keep the gap smaller than the dash when the curve must remain easy to follow.
Content & colour
say(cap, "next step"); // -> crossfade a text entity to new words
recolor(sun, lime, 0.5); // -> permanently animate the colour
Rewrite an equation without rebuilding it
Declare one real LaTeX equation, then supply each mathematically correct state:
equation(work, (cx, 300), `x^2+2x=3`, 54);
rewrite(work, `x^2+2x+1=4`, 0.9, smooth);
rewrite(work, `(x+1)^2=4`, 0.9, smooth);
rewrite(work, `x=-1\pm2`, 0.9, smooth);
rewrite is visual, not a computer-algebra system: it never invents or verifies
a step. Equal RaTeX parts retain identity and travel smoothly. Matching respects
reading order, coarse layout roles—main line, exponent, numerator, denominator,
fraction rule, and other structure—and RaTeX math-style depth. An identical
character therefore cannot change mathematical jobs or jump between levels of
a nested exponent. This also protects logarithm bases, operator limits, and
derivative orders. If one side gains or loses fraction, radical, or
grouping structure, that side leaves before the replacement enters while the
compatible side and equality remain continuous. Globally incompatible states,
such as a matrix becoming a compact formula, use a staged whole-equation
dissolve with only a short, dim overlap. When unmatched source and target glyphs
are a replacement, the old glyph leaves before the new one becomes readable;
pure additions still enter immediately.
Semantic \textcolor roles follow the authored formula, and the final frame is
always the exact target LaTeX. No flag or alternate verb is required. Keep one
equation id for the whole derivation, write readable steps, and use wait
between them when the viewer needs time to absorb a result.
For a plot or diagram that changes with the same step, place rewrite and the
related motion in par. Existing equations are unaffected unless this verb is
used.
The escape hatch — to / set
Named verbs are shortcuts. to animates any single property, for whatever
isn’t pre-named:
to(sun, opacity, 0.3, 0.5); // animate opacity to 0.3 over 0.5s
to(sun, rot, 90); // rotation to 90 degrees
Properties: pos, color, opacity, scale, rot, trace, hue.
Move the camera
cam((300, 200), 1.0); // pan the camera centre
zoom(1.6, 0.8); // zoom to 1.6x
The camera moves the whole world, so a caption or counter would slide off with it.
Mark it sticky(id) to pin it to the screen and keep it readable through the move.
One verb, a whole group
If a name refers to a tag (a group) instead of a single entity, the verb runs on every member at once. This is how you animate a whole graph, table, or loop-generated set in one line:
hidden(g.nodes); // every node, at t=0
draw(g.edges); // trace every edge on together
flash(a.cells, cyan);// flash all array cells
More on grouping in the Kits chapter. Next, control when things happen → Timing.
Motion graphics — move ideas, not layers
Motion Graphics V2 is built around continuity. Keep one object alive while it moves, follows another object, changes visual form, joins a layout, or turns as part of a system. The timeline remains deterministic and freely scrubbable.
There is no V2 mode or production flag. Write an ordinary .manic file and use
the relationship only where the story needs it.
The complete everyday vocabulary
| Creator intent | Manic | Result |
|---|---|---|
| Keep one object beside another | attach(child, target, [(dx,dy)]) | the child follows every resolved target position |
| Stop following | attach(child, none) | the child releases at the settled position without a second verb |
| Change visual identity | become(source, blueprint, [dur], [ease]) | the source keeps its id and settles exactly on the blueprint |
| Turn a whole arrangement | turn(id_or_tag, pivot, degrees, [dur], [ease]) | every member follows the same circular pivot motion |
| Move a real object on a path | travel(object, path, dur, ease) | the object arrives and remains at the endpoint |
| Send temporary or sustained path emphasis | `flow(path, dur, [forward | reverse |
| Add contained ambient life | wander(particles, dur) | seeded, repeatable motion within the container |
| Reorganize the same particles | `arrange(particles, region, “random | grid |
| Progressively move a collection | stream(particles, path, dur, spread, ease) | seeded persistent items leave in a natural order and retain arrival/speed measurements |
| Release particles with individual speeds | emit(particles, path, uniform|maxwell, dur, spread, ease) | every persistent item receives a deterministic sampled speed; faster items traverse sooner |
| Follow a visual field | advect(particles, vectorfield, dur, rate) | every persistent item follows the same bounded formula field with seekable deterministic motion |
| Route a collection through choices | branch(particles, path_tag, dur, ease) | every persistent item makes seeded choices in one authored acyclic network and retains destination/step/arrival measurements |
| Catch particles by a real measurement | collect(livehistogram, particles, speed|arrival|outcome|steps, dur, ease) | each item visibly settles in the bin selected by the same measurement used by observers |
| Show what the process measured | observe(counter_or_livehistogram, particles, arrived|speed|arrival|outcome|steps) | counters and bars stay connected to the same motion instead of being faked separately |
| Change an ordinary property | to(id, property, value, dur, ease) | the general escape hatch remains available |
The three V2 words add relationships; they do not replace move, travel,
flow, arrange, spin, transform, rewrite, or morph.
One process, several truthful views
Use stream when the authored objects themselves should leave progressively.
Use flow when only a temporary signal should pass. A process can drive more
than one observer without manually synchronizing keyframes:
rect(source, (170,300), 210,230);
particles(packets, source, 42, 5, 17);
spline(route, (275,300), (410,140), (560,470), (720,285));
livehistogram(speeds, (980,335), 0.55, 1.05, 10, 430, 220, cyan);
counter(arrivals, (640,610), 0, 0, "arrived ", " / 42");
par {
stream(packets, route, 4.2, 34, smooth);
observe(speeds, packets, speed);
observe(arrivals, packets, arrived);
}
Source order inside the par matters only for the build contract: write
stream first so the following observers can read its compiled measurements.
Playback still starts all three clips together. Seeking remains deterministic;
the collection seed reproduces the same start order, travel times, endpoints,
counter, and histogram.
speed uses normalized authored-path lengths per second rather than screen
pixels, so changing the canvas or render scale does not change the measurement.
arrival is elapsed seconds inside the process beat. A livehistogram requires
an explicit range; observations outside it clamp to the end bins instead of
silently changing the axis.
The minimal complete example is:
// 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);
Branching networks: author paths, keep the evidence
branch is the reusable one-of-many process. Tag ordinary paths into one
network; the first and last points establish each edge’s direction:
line(entry, (100,260), (250,260)); tag(entry, routes);
line(a, (250,260), (430,120)); tag(a, routes);
line(b, (250,260), (430,260)); tag(b, routes);
line(c, (250,260), (430,400)); tag(c, routes);
livehistogram(destinations, (680,300), 0, 3, 3, 300, 240, cyan);
par {
branch(requests, routes, 4, smooth);
observe(destinations, requests, outcome);
}
The network must be connected and acyclic, with one root and at least two
terminals. Paths may converge before splitting again. Each item makes the same
seeded uniform choices in preview, recording, and direct frame seeking.
Terminals are numbered left-to-right, then top-to-bottom; outcome is that
number and steps is the number of edges traversed.
This is intentionally subject-neutral. The minimal example dispatches requests to workers; the polished example uses the same word for repeated left/right choices on a Galton board:
// 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 — 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);
The first scientific acceptance story uses exactly the same generic words for molecular motion:
// MAXWELL–BOLTZMANN PROCESS FOUNDATION
// First acceptance story for deterministic evolving collections. The emitted
// particles, arrival count, and speed histogram all come from one compiled
// process. The formula is authored truth; Manic is not pretending to be a CAS.
title("Maxwell–Boltzmann — From Motion to Distribution");
canvas("9:16");
template("blank");
watermark(mark, (155, 92), "Made With Manic");
text(kicker, (540, 80), "STATISTICAL MECHANICS · PROCESS VIEW");
text(headline, (540, 145), "How does molecular motion become a distribution?");
size(kicker, 19); color(kicker, dim); bold(kicker);
size(headline, 33); bold(headline); wrap(headline, 850);
equation(law, (540, 245),
`f(v)=4\pi\left(\frac{m}{2\pi k_BT}\right)^{3/2}v^2e^{-mv^2/(2k_BT)}`, 36);
rect(reservoir, (410, 500), 560, 330);
outlined(reservoir); outline(reservoir, dim); stroke(reservoir, 4);
line(divider, (630, 340), (630, 660));
color(divider, dim); stroke(divider, 3);
text(gasLabel, (330, 685), "seeded molecules");
size(gasLabel, 20); color(gasLabel, dim);
particles(molecules, reservoir, 72, 5, 29);
// The route starts at the aperture and bends into the measurement region.
spline(nozzle, (630, 500), (735, 430), (850, 570), (900, 735));
stroke(nozzle, 4); color(nozzle, fg); untraced(nozzle);
text(nozzleLabel, (850, 395), "effusive path");
size(nozzleLabel, 19); color(nozzleLabel, dim);
livehistogram(speedBins, (540, 1160), 0.35, 0.95, 14, 760, 300, cyan);
text(histTitle, (540, 970), "EMPIRICAL SPEEDS");
size(histTitle, 23); bold(histTitle);
text(axisUnit, (540, 1355), "normalized path lengths per second");
size(axisUnit, 18); color(axisUnit, dim);
counter(arrived, (540, 1460), 0, 0, "detected molecules ", " / 72");
size(arrived, 24); color(arrived, dim);
text(caption, (540, 1545),
"Each bar grows only when a molecule from the same process arrives.");
size(caption, 22); color(caption, dim); wrap(caption, 820);
hidden(molecules); hidden(speedBins); hidden(arrived); hidden(caption);
hidden(nozzleLabel);
step("reservoir") {
par {
show(molecules, 0.55);
show(speedBins, 0.45);
show(arrived, 0.35);
show(caption, 0.35);
}
}
wait(0.45);
step("open-the-aperture") {
par {
draw(nozzle, 0.55);
show(nozzleLabel, 0.35);
}
}
wait(0.25);
step("motion-becomes-data") {
par {
stream(molecules, nozzle, 5.2, 42, smooth);
observe(speedBins, molecules, speed);
observe(arrived, molecules, arrived);
say(caption,
"The motion and the distribution are two views of one deterministic experiment.",
0.45);
}
}
wait(0.7);
step("takeaway") {
par {
pulse(speedBins.bars, 0.8);
say(caption,
"Manic can now connect a changing collection to measurements without frame callbacks.",
0.45);
}
}
wait(1.2);
V2: emission becomes evidence
stream is ideal when a collection should share one authored journey. Use
emit when individual sampled speeds are the explanation. Add collect when
the particles themselves should settle into the measured bins:
particles(molecules, source, 180, 3.2, 73);
livehistogram(empirical, (730,560), 0, 2.2, 14, 540, 240, cyan);
par {
emit(molecules, flight, maxwell, 7.0, 48, smooth);
collect(empirical, molecules, speed, 0.48, smooth);
observe(empirical, molecules, speed);
observe(arrivals, molecules, arrived);
}
The calls are written in dependency order because each later call reads the
compiled process produced before it. They still play simultaneously inside the
par. maxwell is a normalized speed profile, not a thermodynamics solver:
the creator supplies temperature, units, and explanatory notation. Manic
truthfully samples the distribution, preserves particle identity, makes faster
samples travel sooner, catches each item in its matching bin, and reproduces
the same result from the collection seed.
Decorative fidelity remains separate from process truth. The acceptance story uses the bundled native-vector reservoir asset for the apparatus while its particles, theoretical curve, empirical bars, and counters remain ordinary Manic entities:
// EXPRESSIVE PROCESS V2 — MAXWELL–BOLTZMANN EFFUSION
//
// A polished 2.5D acceptance story inspired by engine-test-6.mov. The
// apparatus is a bundled native SVG, while every molecule, detector bin,
// arrival count, and empirical distribution remains a normal Manic entity.
// `emit`, `collect`, and `observe` all read one deterministic process.
title("Maxwell–Boltzmann Effusion — Motion Becomes Evidence");
canvas("9:16");
template("blank");
watermark(mark, (150, 90), "Made With Manic");
text(kicker, (540, 84), "STATISTICAL MECHANICS · EXPRESSIVE PROCESS V2");
text(headline, (540, 142), "A distribution you can watch forming");
size(kicker, 18); color(kicker, dim); bold(kicker);
size(headline, 34); bold(headline);
equation(law, (540, 242),
`f(v)=4\pi\left(\frac{m}{2\pi k_BT}\right)^{3/2}v^2e^{-mv^2/(2k_BT)}`, 34);
// The decorative shell is an asset; the changing evidence stays native.
svg(apparatus, (240, 540), "asset:svg/physics/effusion-reservoir.svg", 330);
text(reservoirLabel, (225, 705), "THERMAL RESERVOIR");
size(reservoirLabel, 17); bold(reservoirLabel); color(reservoirLabel, gold);
rect(detectorPanel, (730, 560), 570, 300);
filled(detectorPanel); color(detectorPanel, panel); opacity(detectorPanel, 0.92);
outlined(detectorPanel); outline(detectorPanel, dim); stroke(detectorPanel, 3);
text(detectorTitle, (730, 382), "PARTICLE SPEED v");
size(detectorTitle, 18); bold(detectorTitle); color(detectorTitle, dim);
// The invisible source is precisely at the SVG nozzle.
circle(source, (348, 540), 20);
opacity(source, 0);
particles(molecules, source, 180, 3.2, 73);
color(molecules, magenta); glow(molecules, 0.8); z(molecules, 8);
// Individual lanes diverge around this authored route before being collected.
spline(flight, (350, 540), (415, 500), (465, 515), (500, 545));
hidden(flight);
livehistogram(empirical, (730, 560), 0, 2.2, 14, 540, 240, cyan);
z(empirical, 3); hidden(empirical.count);
// Same horizontal range as the live histogram: 0 .. 4*scale = 2.2.
distribution(theory, (730, 560), "maxwell", 0.55, 0, gold);
hidden(theory.axis); hidden(theory.name);
untraced(theory.curve); z(theory, 7);
text(empiricalKey, (610, 750), "● empirical arrivals");
text(theoryKey, (850, 750), "— theoretical curve");
size(empiricalKey, 17); color(empiricalKey, cyan);
size(theoryKey, 17); color(theoryKey, gold);
counter(arrivals, (540, 850), 0, 0, "detected molecules ", " / 180");
size(arrivals, 24); color(arrivals, dim);
text(caption, (540, 920),
"A thermal source emits many molecular speeds—not one average speed.");
size(caption, 23); wrap(caption, 880); color(caption, dim);
rect(evidencePanel, (540, 1110), 880, 230);
filled(evidencePanel); color(evidencePanel, panel); opacity(evidencePanel, 0.95);
outlined(evidencePanel); outline(evidencePanel, dim); stroke(evidencePanel, 2);
text(evidenceTitle, (540, 1030), "ONE PROCESS · THREE VIEWS");
size(evidenceTitle, 18); bold(evidenceTitle); color(evidenceTitle, dim);
equation(processLaw, (540, 1100),
`\text{motion}\;\longrightarrow\;\text{arrival bin}\;\longrightarrow\;\hat f(v)`, 32);
text(truth, (540, 1190),
"The dots, bars, and count are derived from the same seeded experiment.");
size(truth, 20); color(truth, dim); wrap(truth, 790);
hidden(apparatus); hidden(reservoirLabel); hidden(detectorPanel);
hidden(detectorTitle); hidden(molecules); hidden(empirical);
hidden(empiricalKey); hidden(theoryKey); hidden(arrivals);
hidden(caption); hidden(evidencePanel); hidden(evidenceTitle);
hidden(processLaw); hidden(truth);
step("build-the-apparatus") {
par {
show(apparatus, 0.65);
show(reservoirLabel, 0.45);
show(detectorPanel, 0.55);
show(detectorTitle, 0.35);
show(empirical.axis, 0.55);
show(empirical.bars, 0.55);
show(empirical.min, 0.35);
show(empirical.max, 0.35);
show(empiricalKey, 0.35);
show(theoryKey, 0.35);
show(arrivals, 0.35);
show(caption, 0.40);
}
}
wait(0.45);
step("the-prediction") {
par {
draw(theory.curve, 0.9, smooth);
say(caption,
"Maxwell predicts a characteristic spread with a most probable speed.",
0.40);
par {
cam((cx, cy), 0.75, smooth);
zoom(1.025, 0.75, smooth);
}
}
}
wait(0.35);
step("run-the-experiment") {
par {
emit(molecules, flight, maxwell, 7.0, 48, smooth);
collect(empirical, molecules, speed, 0.48, smooth);
observe(empirical, molecules, speed);
observe(arrivals, molecules, arrived);
show(molecules, 0.15);
say(caption,
"Each molecule keeps its sampled speed. Faster molecules cross the apparatus sooner.",
0.45);
}
}
wait(0.55);
step("motion-becomes-evidence") {
par {
pulse(empirical.bars, 0.8);
show(evidencePanel, 0.40);
show(evidenceTitle, 0.35);
show(processLaw, 0.45);
show(truth, 0.40);
say(caption,
"Collection does not fake the chart: every arrival lands in the bin selected by its speed.",
0.45);
par {
cam((cx, cy), 0.65, smooth);
zoom(1.0, 0.65, smooth);
}
}
}
wait(1.4);
Parameterized fields: change the rule, then release the tracers
A field is ordinary visible Manic geometry. Keep a familiar named field, or replace its components with formulas in mathematical coordinates:
vectorfield(flow, (640,365), 375,190, uniform, 17);
parameter(mix, (640,610), 0, 0, 1, "vortex mix", 2);
bind(mix, flow, formula, "(1-p)*1.15-p*y", "p*x");
step("wind-becomes-vortex") {
to(mix, value, 1, 2.5, smooth);
}
step("release-tracers") {
advect(tracers, flow, 4.4, 0.78);
}
Here x and y are field coordinates and p is the parameter. Every arrow
updates from the same formula. advect then compiles each persistent tracer’s
journey through the settled field, so preview, recording, and direct seeking
agree. The optional rate controls distance travelled without changing the beat
duration. Reaching the field boundary safely stops a tracer.
This first slice intentionally does not infer fluid mechanics, collision behavior, obstacles, or domain meaning. It gives creators one reusable visual relationship.
// PARAMETERIZED VECTOR FIELD — one value changes the whole field.
// The same settled field then moves a persistent tracer collection. No frame
// callbacks and no manually authored particle paths are involved.
title("From Wind to Vortex");
canvas("16:9");
template("mono");
watermark(mark, (170, 55), "Made With Manic");
text(kicker, (640, 48), "MANIC · PARAMETERIZED FIELD");
text(headline, (640, 94), "One parameter reshapes every arrow");
size(kicker, 18); color(kicker, dim); bold(kicker);
size(headline, 34); bold(headline);
rect(chamber, (640, 365), 780, 410);
outlined(chamber); outline(chamber, dim); stroke(chamber, 3);
// The initial named field supplies a clear first state. `bind` then gives it
// formula components where x,y are field coordinates and p is the parameter.
vectorfield(flow, (640, 365), 375, 190, uniform, 17);
parameter(mix, (640, 610), 0, 0, 1, "vortex mix", 2);
bind(mix, flow, formula, "(1-p)*1.15-p*y", "p*x");
circle(seed, (640, 365), 128);
outlined(seed); outline(seed, dim); dashed(seed, 10, 9);
particles(tracers, seed, 32, 5, 31, "random");
color(tracers, fg); glow(tracers, 0.55);
text(caption, (640, 678), "p = 0: steady wind");
size(caption, 20); color(caption, dim);
untraced(flow); hidden(seed); hidden(tracers);
step("reveal-the-field") {
par {
draw(flow, 0.9);
show(seed, 0.4);
show(tracers, 0.55);
}
}
wait(0.5);
step("wind-becomes-vortex") {
par {
to(mix, value, 1, 2.5, smooth);
say(caption, "p = 1: the same field turns into a vortex", 0.4);
}
}
wait(0.55);
step("release-tracers") {
par {
advect(tracers, flow, 4.4, 0.78);
say(caption, "The dots follow the settled field — their paths were never drawn.", 0.4);
}
}
wait(1.2);
The same vocabulary can tell an optimization story without teaching the engine what “gradient descent” means:
// GRADIENT DESCENT AS A LEARNING STORY
// Twelve guesses begin without the answer. Every arrow supplies one local
// correction, and the same authored field carries all of them to one minimum.
title("How a Wrong Guess Learns");
canvas("9:16");
template("blank");
watermark(mark,(w*0.16,h*0.045),"Made With Manic");
text(kicker,(cx,h*0.075),"GRADIENT DESCENT · LEARNING AS MOTION");
text(headline,(cx,h*0.125),"How does a wrong guess find the minimum?");
text(chapter,(cx,h*0.265),"1 · START ANYWHERE");
text(caption,(cx,h*0.78),"Twelve guesses begin with no map of the whole landscape.");
text(result,(cx,h*0.715),"12 STARTING POINTS → 1 SHARED MINIMUM");
text(cta,(cx,h*0.91),"MAKE LEARNING VISIBLE → 8gwifi.org/manic");
size(kicker,18); color(kicker,dim); bold(kicker); hidden(kicker);
size(headline,32); 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(result,19); color(result,lime); bold(result); hidden(result);
size(cta,21); color(cta,cyan); bold(cta); hidden(cta);
equation(lossRule,(cx,h*0.205),`L(x,y)=\frac{1}{2}x^2+y^2`);
size(lossRule,34); color(lossRule,fg); hidden(lossRule);
let fieldY = h*0.49;
rect(domain,(cx,fieldY),w*0.82,h*0.40);
outlined(domain); outline(domain,dim); stroke(domain,3);
hidden(domain);
// -grad L = (-x,-2y). The gentler vertical coefficient keeps the motion
// readable while preserving the elliptical convergence of the loss bowl.
vectorfield(descent,(cx,fieldY),w*0.37,h*0.175,"-x","-1.6*y",17);
untraced(descent);
circle(starts,(cx,fieldY),w*0.255);
outlined(starts); outline(starts,dim); dashed(starts,12,10);
hidden(starts);
particles(guesses,starts,12,8,73,"ring");
color(guesses,magenta); glow(guesses,0.78);
hidden(guesses);
circle(minimum,(cx,fieldY),14);
filled(minimum); color(minimum,lime); glow(minimum,1.0);
hidden(minimum);
circle(targetRing,(cx,fieldY),44);
outlined(targetRing); outline(targetRing,lime); stroke(targetRing,3);
hidden(targetRing);
text(minLabel,(cx,fieldY+74),"minimum");
size(minLabel,19); color(minLabel,lime); bold(minLabel); hidden(minLabel);
step("begin with imperfect guesses") {
par {
show(kicker,0.30);
show(headline,0.45);
show(lossRule,0.45);
show(chapter,0.35);
show(caption,0.40);
show(domain,0.45);
show(starts,0.45);
show(guesses,0.60);
}
}
wait(0.65);
step("reveal one local clue everywhere") {
par {
draw(descent,1.10);
fade(starts,0.45);
say(chapter,"2 · READ THE LOCAL SLOPE",0.35);
say(caption,"Every arrow gives one instruction: move opposite the local gradient.",0.42);
}
}
wait(0.55);
step("improve one small step at a time") {
par {
advect(guesses,descent,6.80,0.32);
seq {
say(chapter,"3 · FOLLOW THE CORRECTIONS",0.35);
say(caption,"Far away, the correction is large. Near the bottom, each adjustment becomes smaller.",0.42);
wait(3.05);
say(caption,"Different starting points take different journeys—but obey the same rule.",0.42);
}
}
}
wait(0.55);
step("discover the shared destination") {
par {
show(minimum,0.35);
show(targetRing,0.40);
show(minLabel,0.35);
show(result,0.45);
say(chapter,"4 · ARRIVE WITHOUT TELEPORTING",0.35);
say(caption,"Gradient descent is not a jump to the answer. It is repeated, measured improvement.",0.42);
seq {
pulse(targetRing,0.70);
pulse(guesses,0.70);
}
}
}
wait(0.75);
step("create with Manic") {
par {
pulse(minimum,0.75);
show(cta,0.45);
say(caption,"Describe the rule once. Manic keeps every guess, arrow, correction, and learning step connected.",0.45);
}
}
wait(1.45);
Manic does not infer the subject
An icon, label, or filename never changes motion semantics. Manic does not know that an object represents a load balancer, queue, topic, photon, vehicle, blood cell, or decorative spark. The creator supplies that meaning through ordinary composition:
// One selected route
par { travel(packet, lane2, 1.2, smooth); flow(lane2, 1.2); }
// Three authored deliveries together
par {
travel(copy1, lane1, 1.0, smooth);
travel(copy2, lane2, 1.0, smooth);
travel(copy3, lane3, 1.0, smooth);
}
// A path connected to nothing—motion used purely as design
flow(ribbon, 4.0, both, continuous);
seq creates an authored order. par creates simultaneous motion. travel
moves any ordinary 2-D entity and preserves its identity. flow requires only
a path; it does not require endpoints, architecture metadata, or an object to
carry. Tags let one flow address several paths when the creator wants them to
act together.
attach — author the relationship
dot(marker, (180,620), 8);
text(readout, (180,580), "sample A");
plot(curve, (180,620), 90,140,"1-exp(-x)",(0,4));
attach(readout, marker, (0,-40));
travel(marker, curve, 2, smooth);
attach(readout, none); // release at the settled endpoint
The child follows after normal tracks, reactive bindings, derived geometry, links, particle layouts, and path travel have resolved. Its opacity is multiplied by the target opacity, so a label naturally disappears with the thing it explains.
Use an offset to keep the child readable. Release at the end of a movement,
then move, fade, or reuse the child normally. Attachment cycles are rejected
while building the movie rather than failing during rendering.
become — preserve identity across a visual change
Declare the destination like any other entity and hide it when it is only a blueprint:
circle(seed, (220,700), 16); color(seed, cyan);
circle(node, (820,700), 62); color(node, magenta);
outlined(node); stroke(node, 7); hidden(node);
become(seed, node, 0.9, smooth);
Compatible geometry interpolates continuously, including circle, rectangle, line, arrow, curve, coil, arc, and equal-topology polygon/polyline pairs. Unsupported pairs use a local fade/swap/fade instead of producing broken geometry. Both paths settle on the exact target geometry and styling while the source id remains alive. A hidden blueprint does not make the transformed source disappear, and the blueprint’s own visibility is never changed.
Use rewrite for equations because it understands matching LaTeX visual parts.
After the mathematical transformation has settled, disintegrate(eq, 1.2) can
release the rendered equation as deterministic dust. It samples the actual
alpha silhouette; it is not a rectangular confetti overlay. The same treatment
also works on images and vector shapes, so it remains motion-graphics
vocabulary rather than a math-only effect.
equation(work,(cx,cy),`\int_0^1 3x^2\,dx`,64);
rewrite(work,`\left[x^3\right]_0^1=1`,1.0,smooth);
disintegrate(work,1.3);
Use morph plus to(..., morph, ...) when you explicitly need a fraction-driven
point morph or winding angle.
turn — rotate the system, not every member
circle(orbit, (540,700), 220); hidden(orbit);
particles(dots, orbit, 16, 7, 42, "ring");
turn(dots, orbit, 24, 0.65, out);
The first argument may be one entity or a tag. The pivot may be a point or an
entity. Positions follow circular paths, path endpoints and curve controls turn
with their paths, and ordinary shapes retain their group-local orientation.
Because the target is resolved from the latest authored state, turn composes
after move, travel, arrange, or an earlier turn without snapping back.
Use spin for one object’s in-place rotation. Use transform when the actual
idea is a matrix, shear, reflection, or other precise linear map.
travel versus flow
par {
draw(curve, 2, out);
travel(marker, curve, 2, out); // the marker moves and stays
flow(curve, 1); // temporary travelling emphasis
}
Use travel for a vehicle, probe, token, particle, or graph marker. Use flow
for energy, attention, traffic, or a signal that should disappear after passing.
The default remains one forward pulse. For sustained activity use
flow(curve, 4, forward, continuous); Manic chooses length-aware complete
cycles, so the stream begins empty and drains cleanly at the end. Use
flow(curve, 1, reverse, once) for generic reverse motion and both for two
independent opposing streams.
Motion-flow foundation example
This example proves the same vocabulary across four different intentions: one selected path, three motions authored in order, three motions authored together, and a free spline used only as visual design.
// motion-flow-foundation.manic — objects, paths, timing, and composition
title("Motion Flow Foundation — The Creator Decides");
canvas("9:16");
template("mono");
let u = (w+h-abs(w-h))/2160;
let left = w*0.17;
let right = w*0.83;
let upper = h*0.36;
let middle = h*0.48;
let lower = h*0.60;
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC · DOMAIN-NEUTRAL MOTION");
text(headline, (cx, h*0.13), "The path has no meaning until you give it one.");
text(mode, (cx, h*0.20), "ONE OBJECT · ONE SELECTED PATH");
text(caption, (cx, h*0.82), "Three relationships are possible. The creator activates only the middle path.");
size(kicker, 21*u); color(kicker, dim); bold(kicker);
size(headline, 36*u); bold(headline); wrap(headline, w*0.82);
size(mode, 25*u); color(mode, cyan); bold(mode);
size(caption, 24*u); color(caption, dim); wrap(caption, w*0.80);
rect(stage, (cx, h*0.49), w*0.88, h*0.56);
color(stage, panel); outline(stage, dim); opacity(stage, 0.72);
// Scene 1: one authored object chooses one authored route.
line(cold1a, (left,middle), (right,upper)); dashed(cold1a); color(cold1a, dim); stroke(cold1a, 3*u);
line(cold1b, (left,middle), (right,middle)); dashed(cold1b); color(cold1b, dim); stroke(cold1b, 3*u);
line(cold1c, (left,middle), (right,lower)); dashed(cold1c); color(cold1c, dim); stroke(cold1c, 3*u);
line(hot1b, (left,middle), (right,middle)); color(hot1b, cyan); stroke(hot1b, 7*u); untraced(hot1b);
circle(choiceA, (right,upper), 32*u); outlined(choiceA); color(choiceA, dim); stroke(choiceA, 3*u);
circle(choiceB, (right,middle), 32*u); outlined(choiceB); color(choiceB, cyan); stroke(choiceB, 4*u);
circle(choiceC, (right,lower), 32*u); outlined(choiceC); color(choiceC, dim); stroke(choiceC, 3*u);
rect(parcel, (left,middle), 34*u,34*u); color(parcel, gold); glow(parcel, 1.5);
tag(cold1a, scene1); tag(cold1b, scene1); tag(cold1c, scene1); tag(hot1b, scene1);
tag(choiceA, scene1); tag(choiceB, scene1); tag(choiceC, scene1); tag(parcel, scene1);
// Scene 2: the creator makes the same three routes active in sequence.
line(cold2a, (left,middle), (right,upper)); dashed(cold2a); color(cold2a, dim); stroke(cold2a, 3*u); hidden(cold2a);
line(cold2b, (left,middle), (right,middle)); dashed(cold2b); color(cold2b, dim); stroke(cold2b, 3*u); hidden(cold2b);
line(cold2c, (left,middle), (right,lower)); dashed(cold2c); color(cold2c, dim); stroke(cold2c, 3*u); hidden(cold2c);
line(hot2a, (left,middle), (right,upper)); color(hot2a, cyan); stroke(hot2a, 7*u); untraced(hot2a);
line(hot2b, (left,middle), (right,middle)); color(hot2b, cyan); stroke(hot2b, 7*u); untraced(hot2b);
line(hot2c, (left,middle), (right,lower)); color(hot2c, cyan); stroke(hot2c, 7*u); untraced(hot2c);
circle(roundA, (right,upper), 32*u); outlined(roundA); color(roundA, dim); stroke(roundA, 3*u); hidden(roundA);
circle(roundB, (right,middle), 32*u); outlined(roundB); color(roundB, dim); stroke(roundB, 3*u); hidden(roundB);
circle(roundC, (right,lower), 32*u); outlined(roundC); color(roundC, dim); stroke(roundC, 3*u); hidden(roundC);
circle(r1, (left,middle), 13*u); color(r1, gold); hidden(r1);
rect(r2, (left,middle), 25*u,25*u); color(r2, magenta); hidden(r2);
text(r3, (left,middle), "◆"); size(r3, 34*u); color(r3, cyan); hidden(r3);
tag(cold2a, scene2structure); tag(cold2b, scene2structure); tag(cold2c, scene2structure);
tag(roundA, scene2structure); tag(roundB, scene2structure); tag(roundC, scene2structure);
tag(cold2a, scene2); tag(cold2b, scene2); tag(cold2c, scene2);
tag(hot2a, scene2); tag(hot2b, scene2); tag(hot2c, scene2);
tag(roundA, scene2); tag(roundB, scene2); tag(roundC, scene2);
tag(r1, scene2); tag(r2, scene2); tag(r3, scene2);
// Scene 3: parallel composition makes every authored route active.
line(cold3a, (left,middle), (right,upper)); dashed(cold3a); color(cold3a, dim); stroke(cold3a, 3*u); hidden(cold3a);
line(cold3b, (left,middle), (right,middle)); dashed(cold3b); color(cold3b, dim); stroke(cold3b, 3*u); hidden(cold3b);
line(cold3c, (left,middle), (right,lower)); dashed(cold3c); color(cold3c, dim); stroke(cold3c, 3*u); hidden(cold3c);
line(hot3a, (left,middle), (right,upper)); color(hot3a, cyan); stroke(hot3a, 7*u); untraced(hot3a);
line(hot3b, (left,middle), (right,middle)); color(hot3b, magenta); stroke(hot3b, 7*u); untraced(hot3b);
line(hot3c, (left,middle), (right,lower)); color(hot3c, gold); stroke(hot3c, 7*u); untraced(hot3c);
circle(allA, (right,upper), 32*u); outlined(allA); color(allA, cyan); stroke(allA, 4*u); hidden(allA);
circle(allB, (right,middle), 32*u); outlined(allB); color(allB, magenta); stroke(allB, 4*u); hidden(allB);
circle(allC, (right,lower), 32*u); outlined(allC); color(allC, gold); stroke(allC, 4*u); hidden(allC);
circle(b1, (left,middle), 13*u); color(b1, cyan); hidden(b1);
rect(b2, (left,middle), 25*u,25*u); color(b2, magenta); hidden(b2);
text(b3, (left,middle), "◆"); size(b3, 34*u); color(b3, gold); hidden(b3);
tag(cold3a, scene3structure); tag(cold3b, scene3structure); tag(cold3c, scene3structure);
tag(allA, scene3structure); tag(allB, scene3structure); tag(allC, scene3structure);
tag(cold3a, scene3); tag(cold3b, scene3); tag(cold3c, scene3);
tag(hot3a, scene3); tag(hot3b, scene3); tag(hot3c, scene3);
tag(allA, scene3); tag(allB, scene3); tag(allC, scene3);
tag(b1, scene3); tag(b2, scene3); tag(b3, scene3);
// Scene 4: this curve connects nothing. It exists only because the design wants motion.
spline(ribbon, (w*0.11,h*0.57), (w*0.24,h*0.38), (w*0.40,h*0.55), (w*0.59,h*0.34), (w*0.88,h*0.49));
color(ribbon, magenta); stroke(ribbon, 8*u); untraced(ribbon); tag(ribbon, scene4);
hidden(ribbon.knots);
circle(spark1, (w*0.24,h*0.37), 8*u); color(spark1, cyan); hidden(spark1); tag(spark1, scene4);
circle(spark2, (w*0.73,h*0.53), 8*u); color(spark2, gold); hidden(spark2); tag(spark2, scene4);
step("select-one") {
par {
draw(hot1b, 1.65, smooth);
flow(hot1b, 1.65);
travel(parcel, hot1b, 1.65, smooth);
}
}
wait(0.55);
step("creator-orders-the-motion") {
seq {
fade(scene1, 0.35);
par {
show(scene2structure, 0.40);
say(mode, "THREE OBJECTS · CREATOR-AUTHORED ORDER", 0.35);
say(caption, "Nothing inferred round robin. The creator simply places three motions in seq.", 0.40);
}
par { show(r1, 0.15); draw(hot2a, 0.75, smooth); flow(hot2a, 0.75); travel(r1, hot2a, 0.75, smooth); }
par { show(r2, 0.15); draw(hot2b, 0.75, smooth); flow(hot2b, 0.75); travel(r2, hot2b, 0.75, smooth); }
par { show(r3, 0.15); draw(hot2c, 0.75, smooth); flow(hot2c, 0.75); travel(r3, hot2c, 0.75, smooth); }
}
}
wait(0.55);
step("creator-sends-together") {
seq {
fade(scene2, 0.35);
par {
show(scene3structure, 0.40);
show(b1, 0.20); show(b2, 0.20); show(b3, 0.20);
say(mode, "THREE OBJECTS · THREE PATHS · TOGETHER", 0.35);
say(caption, "Nothing inferred broadcast. par makes every authored journey happen together.", 0.40);
}
par {
draw(hot3a, 1.40, smooth); flow(hot3a, 1.40); travel(b1, hot3a, 1.40, smooth);
draw(hot3b, 1.40, smooth); flow(hot3b, 1.40); travel(b2, hot3b, 1.40, smooth);
draw(hot3c, 1.40, smooth); flow(hot3c, 1.40); travel(b3, hot3c, 1.40, smooth);
}
}
}
wait(0.55);
step("motion-can-just-be-design") {
seq {
fade(scene3, 0.35);
par {
say(mode, "NO ENDPOINTS · NO DOMAIN · JUST MOTION", 0.35);
say(caption, "A free curve can carry light in both directions because the composition asks for it.", 0.40);
show(spark1, 0.25); show(spark2, 0.25);
draw(ribbon, 0.80, smooth);
}
flow(ribbon, 4.20, both, continuous);
}
}
wait(1.25);
step("foundation") {
par {
pulse(ribbon, 0.75);
say(mode, "OBJECTS · PATHS · MOTION · TIMING", 0.35);
say(caption, "Manic animates the creator's declaration. It never invents the subject's behavior.", 0.45);
}
}
wait(1.40);
Run it directly or audit every target format:
manic examples/motion-flow-foundation.manic
manic check examples/motion-flow-foundation.manic --canvas all
Complete V2 example
This generic Reel demonstrates all three relationship words, release, path travel, an identity-preserving blueprint change, particle arrangement, a shared pivot turn, and a readable final hold:
// 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);
}
}
Run it directly—no extra runtime option is required:
manic examples/motion-graphics-v2.manic
Advanced story — compose the whole motion language
The compact example above answers “what do the three new words do?” This advanced Reel answers the more important creator question: “how do they form a story with the motion vocabulary I already know?”
It keeps one visual world alive across three acts:
- A question travels while its label stays attached; a path pulse guides the eye and surrounding facts wander.
- The notation rewrites, the same question becomes a model, and the same facts arrange into a system. The model then spins locally.
- Labels and particles turn around one shared pivot while the equation reaches the story’s final meaning.
seq creates cause and effect, par groups changes that express one idea, and
stagger prevents a dense scene from arriving as a visual shock.
// 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);
}
}
Run the advanced story through the same file-only production path:
manic examples/motion-graphics-v2-story.manic
The original essentials example remains useful when learning wander,
travel, explicit morph, and particle arrangement:
// motion-graphics.manic — persistent objects, paths, particles, and a clean settle
title("Motion Graphics Essentials");
canvas("9:16");
template("mono");
watermark(manicMark, (150, 145), "Made With Manic");
let accent = 194;
let warm = 38;
text(kicker, (540, 170), "MOTION GRAPHICS");
size(kicker, 24); color(kicker, dim);
text(headline, (540, 245), "Keep the object. Change its state.");
size(headline, 42);
rect(chamber, (540, 535), 700, 300);
outline(chamber, dim); outlined(chamber); stroke(chamber, 3);
rect(startCell, (350, 535), 290, 270); hidden(startCell);
particles(dots, startCell, 36, 6, 23);
hue(dots, accent, 0.58, 0.72); glow(dots, 0.7);
text(note, (540, 835), "random layout · organic deterministic routes");
size(note, 25); color(note, dim);
plot(path, (245, 1120), 145, 175, "1-exp(-1.4*x)", (0, 3.8));
hue(path, warm, 0.60, 0.70); stroke(path, 5); untraced(path);
dot(marker, (245, 1120), 9); color(marker, fg); glow(marker, 1.0);
text(pathNote, (540, 1215), "travel moves the real marker — then it stays");
size(pathNote, 25); color(pathNote, dim);
line(connector, (245, 1120), (835, 850)); hidden(connector);
plot(pathMorph, (245, 1120), 145, 175, "1-exp(-1.4*x)", (0, 3.8));
hue(pathMorph, warm, 0.60, 0.70); stroke(pathMorph, 5);
morph(pathMorph, connector); hidden(pathMorph);
circle(orbit, (540, 535), 245); hidden(orbit);
step("scatter") {
seq {
par {
seq { arrange(dots, chamber, "random", 1.25, out); wander(dots, 1.75); }
seq { wait(0.25); say(note, "same dots · natural motion · repeatable render", 0.45); }
}
}
}
step("follow-a-path") {
seq {
par {
draw(path, 2.4, smooth);
travel(marker, path, 2.4, smooth);
}
wait(0.45);
}
}
step("preserve-the-path") {
seq {
par {
fade(path, 0.20);
show(pathMorph, 0.10);
to(pathMorph, morph, 1, 0.90, smooth);
say(pathNote, "morph keeps an open path open", 0.45);
}
wait(0.45);
}
}
step("arrive-and-settle") {
seq {
par {
arrange(dots, orbit, "ring", 1.15, smooth);
fade(chamber, 0.35);
seq { wait(0.45); say(note, "ring arrival", 0.35); }
}
par {
transform(dots, (540,535), 0.9659, -0.2588, 0.2588, 0.9659, 0.50, out);
say(note, "brief motion → a deliberate stop", 0.35);
}
wait(1.20);
}
}
Professional motion checklist
- Author relationships and final states; avoid hand-keyframing intermediate coordinates.
- Use
parfor changes that belong to one idea andseqfor cause-and-effect. - Prefer
smoothfor explanatory transformations andoutfor a short settle. - Keep an important object id alive instead of fading it out and rebuilding it.
- Leave a final
waitso the audience can read the state motion created. - Preview by named
stepand scrub backwards: the same time must always produce the same frame.
Timing — flow, named phases & clocks
By default, verbs play one after another. Three wrappers change that — they turn “then, then, then” into “together” or “cascading”.
| wrapper | plays its steps… | use for |
|---|---|---|
| (nothing) | one after another | the normal flow |
par { … } | all at the same instant | reveal a group at once |
seq { … } | one after another (explicit) | grouping inside a par |
stagger(d) { … } | each one d seconds after the last | cascades / waves |
step("name") { … } | all at once, with a named exported start | change one reactive world across representations |
show(a); show(b); // a, THEN b
par { show(a); show(b); } // a and b together
stagger(0.1) { show(a); show(b); show(c); } // a, then b 0.1s later, then c…
Put a for loop inside one and it just works — the loop expands first, so all
its statements land in the wrapper:
par { for i in 0..6 { show(a{i}); } } // whole row at once
stagger(0.1) { for i in 0..6 { show(b{i}); } } // whole row cascading
// the same reveal, three ways: sequence, together, cascade.
title("Timing"); canvas("16:9");
text(t, (cx, 110), "seq · par · stagger"); color(t, cyan); size(t, 30); hidden(t);
for i in 0..6 { dot(a{i}, (220 + i*160, 300), 16); color(a{i}, cyan); hidden(a{i}); }
for i in 0..6 { dot(b{i}, (220 + i*160, 470), 16); color(b{i}, magenta); hidden(b{i}); }
show(t, 0.5);
// top row, all at the same instant
par { for i in 0..6 { show(a{i}); } }
wait(0.5);
// bottom row, cascading 0.1s apart
stagger(0.1) { for i in 0..6 { show(b{i}); } }
wait(1.0);
▶ See it play:
Beats & sections
Two more timing words structure a longer video:
wait(1.2); // hold — nothing moves for 1.2s
section("Part Two"); // a titled marker (jump to it in preview with keys 1–9;
// also exported for lining up narration)
mark("beat-3"); // a named timestamp for your editor
wait is your friend for pacing — a beat of stillness after something lands
reads far better than rushing to the next move.
Named reactive steps
Use step when a beat represents the world’s next conceptual state, not
just anonymous timing. Its children start together like par; its duration is
the longest child; anything not mentioned remains exactly as it was. The unique
name is written to markers.json and becomes a first-class editing boundary:
manic stages FILE.manic lists durations, --stage NAME previews or records
one stage, and --from-stage / --to-stage export an inclusive arc.
step("explain") {
rewrite(work, `f'(x)=2x`, 0.9, smooth);
to(tangent, x, 2.5, 2.0, smooth);
to(slopeValue, x, 2.5, 2.0, smooth);
say(caption, "Every representation changes together.");
}
Steps are top-level and names must be non-empty and unique. Put seq { … }
inside a step when a small part needs ordered choreography.
An authored wait after a step remains part of that stage until the next step
begins. This lets a stage-only export keep the reading hold after its motion.
See Story stages for the live navigator and publishing
workflow.
Generic Timing v2 — one clock for any scene
Use a generic timing controller when several parts of a scene must share one exact schedule. It is format-neutral: the same controller can coordinate a physics simulation, geometry proof, chart, caption sequence, or ordinary shapes.
Think of it as four small pieces:
| piece | responsibility |
|---|---|
timing | declares the phase names, durations, exact total, and optional clock position |
timerstyle | changes only the visible clock; it never changes scene timing |
timed | runs the clock and schedules the complete phase contract |
during | contains the ordinary animation actions for one named phase |
The phase declaration is the source of truth. The clock is only one visual view of that choreography.
Quick reference
| form | use |
|---|---|
timing(clock,"intro=1 demo=6 result=2") | declare named phases |
timing(clock,(1160,80),"...") | declare phases and set the initial clock position |
timing(clock,"duration=6") | shorthand for one phase named main |
timerstyle(clock,"...") | change appearance without changing timing |
timerstyle(clock,(1160,80),"...") | restyle and reposition the clock |
timed(clock) { ... } | play the complete phase schedule and clock |
during("phase") { ... } | author one phase inside timed |
run(clock) | play the clock alone |
countdown(id,[at],[seconds],["style"]) | independent countdown without named phases |
Core pattern, after defining the referenced entities and simulation:
timing(clock, (1160,80), "intro=1 demo=6 result=2");
timerstyle(clock, "look=ring number=inside color=cyan");
timed(clock) {
during("intro") { show(title, 0.6); }
during("demo") { par { run(sim, 6); draw(sim.path, 6); } }
during("result") { show(answer, 0.6); }
}
Use countdown when you only need an independent visual countdown. Use
timing when named phases must govern other animation.
Compose inside phases
Each during block accepts the usual timeline language. In particular, use
par for actions that must occupy the same phase together:
timing(clock, "setup=1 experiment=6 result=2");
timed(clock) {
during("setup") { show(title, 0.6); }
during("experiment") {
par {
run(pendulum, 6);
draw(pendulum.path, 6);
karaoke(caption, 0.35);
}
}
during("result") {
par {
show(formula, 0.6);
show(explanation, 0.6);
}
wait(1.4);
}
}
Rules that prevent drift
- Give a generic controller a fresh id. Do not reuse an entity, quiz, simulation, or group id.
- Put constructors and style modifiers outside
timed/during; put timeline actions such asshow,draw,run,wait,par, andseqinside. - A short phase block is padded automatically. A block that exceeds its phase is an error.
- A phase may have at most one
duringblock. Combine related work inside it withparorseq. - An omitted phase is valid and becomes a blank hold.
- Phase blocks may appear in any source order: they are placed at the absolute
offsets declared by
timing. timed(clock)already runs the clock. Do not also callrun(clock)inside it.run(clock)is timer-only playback.run(clock, dur)is rejected because the phase declaration already owns the duration.
Choose the clock’s look
The clock uses native manic shapes, so it stays sharp at any output size and can be targeted like the rest of the scene. No SVG workflow is required.
| look | best fit |
|---|---|
ring | neutral default; compact and familiar |
bar | long processes or wide layouts |
segments | energetic stages and presentations |
ticks | precise, technical, or measurement-led scenes |
number | minimal layouts where the value is enough |
pulse | short, urgent moments; use sparingly |
none | keep exact phase choreography without showing a clock |
The main controls are:
number=inside|outside|noneanddirection=fill|drainsize=small|medium|large|0.5..2.0andthickness=0.4..3.0color,track,label, andfont=mono|displayfinish=fade|hold|flash|pulsefor the completion cue
Start with the default ring, then change the look only when it supports the
scene’s meaning. For legibility, keep strong contrast between color and
track, and avoid combining a busy clock with dense content in the same corner.
Advanced styling can target the stable tags clock.timer,
clock.timer.track, clock.timer.progress, clock.timer.value,
clock.timer.label, and clock.timer.effects (replace clock with the
controller id).
Generic controller or Creator quiz?
The same visual clock system serves two different timing contracts:
timing(fresh_id, "phase=seconds ...")creates a generic controller used bytimedandduring.timing(quiz_id, "calm ask=... think=... reveal=...")configures a Creator quiz and is played withrun(quiz_id).
Do not wrap a quiz in timed; the quiz runner already owns its ask, options,
think, reveal, hold, and stagger phases.
Common fixes
| message or symptom | fix |
|---|---|
| controller id is already in use | choose a fresh id for generic timing |
| a phase overruns | shorten its sequence, compose simultaneous work with par, or increase the declared phase |
| unknown phase | match the name in during to the timing declaration |
| duplicate phase block | combine the work into one during block |
| competing duration | remove the duration argument from run(clock, dur) |
| timer appears twice | remove run(clock) from inside timed(clock) |
Complete non-quiz example using a pendulum, caption, formula, and segmented clock:
// Generic Timing v2 — one named clock coordinates a non-quiz physics scene.
// `timed` runs the visual timer and places every `during` block at the exact
// offset declared by `timing`; short blocks are padded, overruns are errors.
title("Generic Timing v2 — Pendulum");
canvas("16:9");
template("mono");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
text(head, (315, 72), "ONE CLOCK. EVERY BEAT.");
size(head, 30); bold(head); color(head, fg); hidden(head);
text(sub, (315, 112), "Generic Timing v2 — not a quiz");
size(sub, 18); color(sub, dim); hidden(sub);
pendulum(p, (640, 245), 1.5, 42, 140, 0.03);
untraced(p.path);
equation(law, (640, 602), `T\approx2\pi\sqrt{L/g}`, 54);
color(law, gold); hidden(law);
text(note, (640, 655), "The scene and clock share the same named phases.");
size(note, 21); color(note, dim); hidden(note);
text(done, (640, 680), "INTRO → EXPERIMENT → INSIGHT → OUTRO");
size(done, 18); color(done, cyan); hidden(done);
// A fresh id creates a format-neutral timing controller. Its phases total
// 10.4 seconds; no duplicate duration is passed to `timed` or `run`.
timing(showclock, (970, 86), "intro=1.2 experiment=6 insight=2 outro=1.2");
timerstyle(showclock,
"look=segments number=outside direction=drain size=small thickness=1.15 color=cyan track=dim label=MASTER_CLOCK font=display finish=pulse");
timed(showclock) {
during("intro") {
par { show(head, 0.7); show(sub, 0.7); }
}
during("experiment") {
par { run(p, 6); draw(p.path, 6); }
}
during("insight") {
par { show(law, 0.6); show(note, 0.6); }
pulse(p.bob, 0.7);
}
during("outro") {
show(done, 0.6);
}
}
Next: the palette, glow, and easings → Colour & style.
Voice — narrate your story
Add spoken narration to a Manic video with three ideas:
- Pick a voice once —
voice("…") - Speak full sentences —
speak("…") - Keep short on-screen text separate —
say(…)
You do not time the audio by hand. Each speak line lasts as long as it
takes to say — motion on that beat waits with it.
Voice is optional. Skip it when the video should stay silent; use say alone
for captions.
say vs speak
say | speak | |
|---|---|---|
| What it does | Changes on-screen text | Speaks a voice-over line |
| Heard? | No | Yes |
Needs voice(...)? | No | Yes — once per file |
| Best for | Short punch lines, labels | Full sentences the viewer hears |
| Example | say(cap, "GOAL"); | speak("The ball curls into the net."); |
Rule of thumb: write the short caption with say, the full line with
speak. They can run together on the same beat.
par {
say(cap, "same grid → 3D"); // what you read
speak("Now lift the same grid into height."); // what you hear
}
Or update a caption and speak in one call:
speak(cap, "Pull the camera back.");
How to add narration (step by step)
1. Put a caption box on the cast (optional but common)
text(cap, (cx, h*0.82), "");
size(cap, 28); color(cap, dim); wrap(cap, w*0.8);
2. Choose a voice once (near the top)
voice("cartesia");
You only need this if you use speak. One voice(...) per file is enough.
3. Narrate on the timeline
speak("Every world starts flat.");
Together:
title("A spoken beat");
canvas("16:9");
voice("cartesia");
text(cap, (cx, h*0.82), "");
size(cap, 28); color(cap, dim); wrap(cap, w*0.8);
par {
say(cap, "cell by cell…");
speak("Watch a map generate itself until the islands lock.");
}
wait(0.3); // a short breath before the next idea
speak and voice always go together. Captions alone need only say — no
voice line.
Choosing a speaker
voice takes up to four pieces:
voice(service, speaker?, pace?, language?)
Easy names (recommended)
voice("gtts"); // simple built-in
voice("cartesia"); // Skylar
voice("cartesia", "katie");
voice("cartesia", "jameson");
voice("elevenlabs"); // Roger
voice("elevenlabs", "alice");
voice("elevenlabs", "jessica");
| service | easy speaker names |
|---|---|
| Cartesia | katie, skylar, jameson, gemma, archie |
| ElevenLabs | roger, alice, sarah, jessica, adam, george, liam, … |
Your own speaker (provider voice id)
Paste the voice id from Cartesia or ElevenLabs when you have a favourite speaker that is not in the easy list:
// Cartesia — voice UUID from your Cartesia voice library
voice("cartesia", "a0e99841-438c-4a64-b679-ae501e7d6091");
// ElevenLabs — voice id from your ElevenLabs voice library
voice("elevenlabs", "21m00Tcm4TlvDq8ikWAM");
Easy names and raw ids both go in the same speaker slot.
Pace
voice("elevenlabs", "alice", "slow"); // normal | slow | fast
voice("cartesia", "katie", "fast");
Language
Defaults to English (en). Pass a language code when the narration is not English:
// language last (with pace)
voice("cartesia", "katie", "normal", "hi");
voice("elevenlabs", "alice", "normal", "es");
// or skip pace and put the language third
voice("cartesia", "katie", "hi");
voice("elevenlabs", "roger", "fr");
Common codes: en, hi, es, fr, de, pt, ja, zh, ko, it, ar, …
For the built-in gtts service, the second argument is the language:
voice("gtts", "en");
voice("gtts", "hi");
Narrate with motion (same beat)
Put caption, voice, and animation in one par so they land together:
par {
say(cap, "fly the ridges");
speak("Fly in low over the ridges.");
orbit3(255, 15, 12, 3.8, smooth);
}
If the spoken line is longer than the motion, Manic holds the picture until
the line finishes. A trailing wait(0.3) is only a pause between ideas —
not a substitute for narration length.
Pacing — budget your Short by word count
You don’t set timings by hand, but you can predict how long a narrated video will run: a spoken line lasts about 2.5 words per second (≈150 words per minute — a natural narration pace).
| Target length | Budget for speak |
|---|---|
| 15s Short | ~35–40 words |
| 30s Short | ~75 words |
| 60s Short | ~150 words |
So a Short that feels right on paper can quietly balloon: 150 words of narration is a full minute, even if the script looks short. If a video runs long, trim the words first — the motion follows the voice, not the other way round.
Preview pacing without an API key. Manic estimates each speak line’s
length from its word count even when text-to-speech is offline or no key is
set (the line is silent, but the timeline still reserves the time). That means
--still <t> lands on the same beat it will at full voice — so you can
check timing and framing before spending a single TTS call:
manic monty-hall.manic --still 15.6 # the frame the viewer sees at 15.6s
A few practicalities:
- Each line is clamped to 0.8s–30s — one very long
speakwon’t stall forever, but split big paragraphs into separate lines anyway; they read better. wait(...)adds on top of the spoken time. Sincespeakalready paces the beat, keep waits small (wait(0.3)–wait(0.6)) — they’re breaths between ideas, not the pacing itself.- Real TTS duration replaces the estimate when a voice key is present; it lands
very close, so your
--stillpreview stays accurate. - Tip: to count your narration, add up the words inside every
speak("…")and divide by 2.5 for a rough total in seconds.
Math in spoken lines
Write symbols as you would on screen; Manic reads them naturally:
speak("The slope is √(tan θ).");
Examples in the gallery
- Roberto Carlos’ Banana Kick — story VO + captions
- Your Map Is a 3D World — map → 3D with narration
- The Shortest Path — vector maze + A* with problem→solve narration (ElevenLabs)
- Made of Emoji — a vector spectacle with promo VO (ElevenLabs)
- The Monty Hall Problem — probability puzzle told in emoji, problem→solution→CTA narration (ElevenLabs)
Quick checklist
- One
voice("…")near the top when you want narration - Full sentences in
speak("…") - Short on-screen lines in
say(cap, "…") - Use
par { … }when caption, voice, and motion should start together - Budget length by word count (~2.5 words/sec) — ~75 words ≈ 30s
- Preview any beat with
--still <t>(timing holds even with no TTS key) - No voice? Omit
voice/speak—sayalone is fine
Templates — choose the whole visual system
A template controls the background, semantic palette, glow character and any page chrome for the entire movie. Choose it once near the top of the file:
canvas("9:16");
template("black");
black is the default. If a DSL file does not call template(...), it renders
exactly as if template("black") had been written. Keeping the call explicit is
useful in shared examples; omitting it is convenient for ordinary authoring.
Available templates
| template | best for | character |
|---|---|---|
black | creator stories, vivid explainers and general authoring | exact #000000 canvas, full semantic colour, no chrome |
mono | professional explainers, proofs and restrained Reels | black-and-white editorial surface, clear luminance hierarchy, subtle glow |
plain (alias blank) | dark indigo neon scenes | original neon semantic colours on a subtly tinted near-black surface |
terminal | code, algorithms and technical demos | neon terminal frame and stronger chrome |
paper | textbook figures, worksheets and print | white page, dark ink, crisp low-glow rendering |
blueprint | geometry, engineering and construction | cyan/white drafting marks on deep navy |
shorts | energetic social content where hue matters | restrained dark creator palette |
Useful aliases are monochrome, blackwhite, black-white, and bw for
mono; blank and clean for plain; light and print for paper; and
blue for blueprint.
black and plain are intentionally different. black is a truly black,
chrome-free creator canvas; plain retains Manic’s original indigo-black neon
surface.
Mono in practice
// Mono is the default; keep this line explicit when teaching or sharing.
title("Mono template");
canvas("16:9");
template("mono");
rect(card, (640, 360), 930, 430);
color(card, panel);
text(head, (640, 205), "ONE PALETTE · CLEAR HIERARCHY");
size(head, 42);
color(head, fg);
hidden(head);
circle(a, (410, 370), 58); color(a, cyan); hidden(a);
circle(b, (640, 370), 58); color(b, magenta); hidden(b);
circle(c, (870, 370), 58); color(c, lime); hidden(c);
text(al, (410, 470), "PRIMARY"); color(al, fg); hidden(al);
text(bl, (640, 470), "SECONDARY"); color(bl, dim); hidden(bl);
text(cl, (870, 470), "SUCCESS"); color(cl, lime); hidden(cl);
show(head, 0.4);
par {
show(a, 0.5);
show(b, 0.5);
show(c, 0.5);
}
par {
show(al, 0.35);
show(bl, 0.35);
show(cl, 0.35);
}
pulse(c);
wait(1);
Named colours remain meaningful under mono. fg, dim, panel, cyan,
magenta, lime, gold, red, orange, blue, teal, violet, coral,
indigo, and mint are mapped to deliberately different greys instead of
collapsing to identical white. This preserves hierarchy and correct-answer
contrast while keeping the export monochrome.
Use named colours for template-aware work:
color(answer, lime); // success role; becomes a bright mono tone
color(note, dim); // secondary role; remains visually quiet
hue(...) is intentionally an explicit colour choice and bypasses semantic
palette remapping. Avoid it when the output must remain strictly black and
white.
DSL selection versus export override
The DSL call travels with the scene. The command-line option is useful for a one-off alternate render:
manic scene.manic --still 4.0
manic scene.manic --record out --template paper
An explicit --template NAME export option overrides the DSL template for that
run. This is a quick way to proof the same semantic scene on mono, paper and
blueprint without editing the source.
Creator and Reel guidance
Start with black when colour is part of the explanation. It gives Creator stories a clean, cinema-dark canvas while preserving semantic accents:
canvas("9:16");
// template("black") is optional because black is the default.
creator(me, "@anish2good yt=zarigatongy x=@anish2good web=8gwifi.org/manic footer=social");
quiz(q, "Which statement is true?");
Choose mono for an intentionally black-and-white identity, shorts for a
tinted studio surface, or paper for an exam-sheet or textbook treatment.
Template choice does not change Creator layout, safe areas, timing, option
labels or social-platform selection—it changes only their visual system.
Practical checks
- Review one reading frame and one reveal frame at phone size.
- On black, inspect dim lines and labels as well as bright accents.
- In mono, check luminance contrast rather than relying on colour names.
- Prefer one semantic accent role; do not make every object equally bright.
- Use
plainwhen you specifically want the original indigo-neon surface. - Use
paperorblueprintto test whether a construction remains legible on a very different surface.
Next: Colour, glow and semantic roles →
Colour & style
The palette
manic uses a small set of semantic colour names. The default black template
shows their full hues on an exact black canvas. Choose mono when the scene
should communicate through black-and-white luminance instead. The semantic
roles are:
| name | is | name | is |
|---|---|---|---|
cyan | electric blue | gold | warm amber |
magenta | hot pink | red | warm red |
lime | acid green | orange | orange |
blue | true blue (≠ cyan) | teal | cyan–green |
violet | true purple | coral | soft warm red-orange |
indigo | deep blue–violet | mint | soft cool green |
dim | muted grey-violet | fg | near-white (default text) |
void | the background | panel | dark fill |
Aliases: pink/accent → magenta · green → lime · yellow/amber → gold ·
purple → violet · turquoise → teal · salmon → coral · seafoam → mint ·
azure → blue · crimson → red · gray/grey → dim · white → fg · bg → void.
color(sun, cyan);
recolor(sun, magenta, 0.5); // animate to a new palette colour
Any colour, by hue
For a computed colour — one per item in a loop — use hue, which takes an
angle from 0 to 360:
hue(sun, 200); // a fixed hue
for i in 0..24 {
hue(p{i}, 360*i/24); // a full rainbow around the loop
}
That’s how the rainbow-ring loop gets its colours.
Gradient paint — computed, not painted
For a colour that reads a quantity, use gradient. One word covers multi-stop
ramps on fills and strokes; the mode (optional) picks the truth:
gradient(wave, blue, cyan, gold, 270); // height of a plot
gradient(path, magenta, cyan); // arc length along a stroke
gradient(well, panel, void, radial); // centre → edge on a fill
gradient(p.path, blue, cyan, gold, "speed"); // true local speed (physics traj.)
gradient(swoop, dim, magenta, "curvature"); // how hard a path bends
Stops are palette names (≥2, evenly spaced) and stay template-aware.
"speed" only works on pre-simulated physics trajectories; "curvature" works
on any path. See the Modifiers table in Shapes, and the demos
gradient,
gradient-fastest-descent, and the
shorts gradient-fastest-descent-shorts /
gradient-pendulum-shorts.
Glow
Every entity has a neon glow (0 = crisp, higher = brighter halo):
glow(sun, 8); // strong halo
glow(grid, 0); // crisp, no halo — good for fine detail
Easings
The optional last argument of a motion verb is the easing — the shape of the motion over time:
| easing | feel |
|---|---|
linear | constant speed (mechanical) |
smooth | ease in and out (the default, natural) |
in / out | accelerate / decelerate |
back | overshoot slightly and settle |
bounce | bounce at the end |
elastic / spring | wobble / springy settle |
move(p, (900, 400), 0.8, bounce);
move(p, (900, 400), 0.8, smooth); // usually what you want
Canvas & size
canvas(...) sets the frame. Give it a preset or explicit pixels:
canvas("16:9"); // 1280x720 (also: 4:5, square, portrait/9:16, 4:3, 1080p, 4k)
canvas(1280, 720); // explicit
portrait / 9:16 is 1080×1920 — pair it with the reel render preset for
vertical / social clips.
Use --canvas portrait|4:5|square|16:9|WIDTHxHEIGHT to reframe one responsive
source without changing its canvas(...) line. The override is applied before
w, h, cx, cy, and build-time layout branches are evaluated.
Templates — the whole look
For the complete selection guide, CLI override rules, mono tips and a runnable black-and-white example, see Templates — choose the whole visual system.
template("...") sets the movie’s look in one call: the background, how the
palette renders, the glow, and any page chrome. Put it near the top, after
canvas(...).
canvas("16:9");
template("paper"); // a flat white exam-paper page
| template | look |
|---|---|
black | default — full semantic colour on exact black, no chrome |
mono (aliases monochrome, bw) | black-and-white editorial, near-black page, subtle glow |
plain (aliases blank, clean) | original neon palette on an indigo-black surface, no chrome |
terminal | the neon-terminal frame (border, title, masthead) |
paper (aliases light, print) | white page, dark ink — for print / textbook figures |
blueprint (alias blue) | white & cyan lines on deep navy |
shorts | restrained dark creator palette for Shorts and Reels |
Omitting template(...) is exactly the same as selecting template("black").
Use mono deliberately for grayscale and plain for the original tinted neon
surface.
The clever part is the palette remap: a template doesn’t just change the
background, it re-maps every named palette colour to that template’s role. So
on mono, cyan, magenta, lime, gold, red, orange, and blue become
carefully separated greys; on paper, panel → light box, fg → dark ink,
lime → forest green, and so on —
which means your existing scene renders legibly on the new page without
recolouring anything. That’s why template("paper") alone turns a pulley,
a spring, or a linked list into a clean textbook figure (see
Elevating a scene). paper/blueprint also drop the glow for
crisp print output.
hue(...) and future explicit RGB colours are intentionally bespoke, so they
pass through instead of being forced to greyscale. Use the named palette colours
when a scene must remain strictly monochrome.
Next: loops, variables, and macros → The language layer.
The language layer
Everything so far has been static text. manic also has a small computation layer that runs before the animation — variables, arithmetic, loops, and macros. It lets one rule draw a hundred shapes.
These are resolved at build time. By the time the animation plays, they’ve expanded into plain calls — so they cost nothing at render.
Variables — let
let r = 120;
let gap = r * 2 + 40;
circle(a, (cx - gap, cy), r);
circle(b, (cx + gap, cy), r);
Arithmetic is what you’d expect: + - * / ^, parentheses, and functions like
sin, cos, sqrt. Constants pi, tau, e are built in, as are the canvas
vars w, h, cx, cy.
Put a
*between names. A number can hug a name (2r,3(x+1)), but two names glued together read as one word — writer*x,i*dx,pi*t, neverrx/idx/pit. This is the most common slip in loops and formulas.
Loops — for
for i in 0..5 {
dot(p{i}, (200 + i*180, cy), 8); // p0 … p4
}
p{i} is id interpolation — {expr} glued to a name makes each entity
unique. Use i in the body to compute positions, sizes, hues…
// one for-loop paints a full rainbow ring.
title("One loop"); canvas("16:9");
text(t, (cx, 90), "one loop, 24 dots, every hue"); color(t, cyan); size(t, 28); hidden(t);
let n = 24;
for i in 0..n {
dot(p{i}, (cx + 300*cos(tau*i/n), cy + 300*sin(tau*i/n)), 14);
hue(p{i}, 360*i/n); // colour by angle -> a rainbow
hidden(p{i});
}
show(t, 0.5);
stagger(0.05) { for i in 0..n { show(p{i}); } }
wait(1.4);
▶ See it play:
Conditionals — if
let n = 5;
if n > 3 {
circle(big, (cx, cy), 120);
}
Macros — def
A def is a reusable rule. Its parameters are numbers; build ids inside with
interpolation. It can even call itself (recursion) — that’s how the fractal tree
in the gallery is one page of code:
def branch(k, x, y, ang, len, depth) {
if depth > 0 && len > 2 {
let x2 = x + len*cos(ang);
let y2 = y - len*sin(ang);
line(seg{k}, (x, y), (x2, y2));
branch(2*k, x2, y2, ang + 0.4, len*0.72, depth - 1);
branch(2*k + 1, x2, y2, ang - 0.4, len*0.72, depth - 1);
}
}
branch(1, cx, h - 40, 1.5708, 150, 12);
Curve L-systems — one rule, one path
For deterministic textbook curves, use lsystem instead of manually recursing
into thousands of separate lines:
lsystem(carpet, (cx,cy), 620,
"F+F+F+F",
"F=FF+F+F+F+FF",
"angle=90 heading=0 iterations=4");
gradient(carpet, cyan, magenta, gold);
stroke(carpet, 2);
untraced(carpet);
draw(carpet, 3, smooth);
The six arguments are:
- id, centre, and fitted square size;
- the starting string (axiom);
- semicolon-separated rewrite rules such as
"X=...;Y=..."; - one short options string.
In the generated program, + turns left by angle, - turns right, |
turns around, and every symbol listed by draw=F moves forward while drawing.
heading sets the first direction, iterations controls detail, and padding
keeps the result away from its fitted edge.
Use closed=true fill=true when the grammar describes a closed boundary; Manic
triangulates it into one concave region. Keep fill=false for line curves. The
constructor deliberately does not interpret branch brackets: use a recursive
def or tree3 for branching plants. This separation keeps curve stories
simple and makes invalid input easy to repair.
Practical tips
- Increase
iterationsone step at a time: rewrite systems grow exponentially. - Keep the curve as one entity, then use ordinary
draw,gradient,move,become,step, and camera verbs. - For a lesson, show generations 0 → 1 → 2 before revealing the detailed curve.
- See
lsystem-asymptote-curves.manicfor four canonical grammars andcreator-lsystem-fractal-curve.manicfor a complete creator Short.
Repeated motifs — one design, many placements
Use repeat when the visual idea is “this motif forms a field,” not “write a
loop that manually copies every part.”
polygon(tile, (cx-12,cy+10), (cx+12,cy+10), (cx,cy-14));
repeat(field, tile, "layout=hex rings=4 spacing=42 rotate=30 scale=0.8");
untraced(field);
draw(field, 2, smooth);
The motif can be one entity or a tag containing several entities. Choose:
layout=hex rings=N spacing=S;layout=grid rows=R cols=C spacing=S(or separategapx/gapy);layout=radial count=N radius=R face=same|out.
rotate turns the arrangement and scale sizes each instance. Every output is
tagged with the destination id; stable instances are {id}.i0, {id}.i1, …
The output of one repeat can itself be the motif of another, which is how
nested textbook tilings stay short. Use normal color, gradient, mask,
untraced, draw, and stagger afterward.
See asymptote-tiling-reference.manic for hex/grid/radial/nested coverage and
creator-one-tile-pattern-story.manic for the creator treatment.
Reductions
Fold a range into one number with sum, prod, min, max:
let n = 6;
let total = sum(i in 1..n : i); // 1 + 2 + … + (n-1)
That’s the whole language. The rest is kits — bundles of higher-level figures → Kits.
Manic API reference
This page is generated from manic-lang’s builtin catalog, the same source used by parsing, editor highlighting, autocomplete, and engine/catalog regression tests. It currently lists 393 builtins across 16 kits.
Run python3 scripts/gen-api.py after changing the catalog. Run python3 scripts/gen-api.py --check to verify that this committed page is current.
Reading a signature
new idis the name created by a constructor.id or tagtargets an existing entity, generated part, or group.- Parameters inside brackets are optional.
…means the builtin is catalogued, but its detailed parameter metadata still lives in the linked topic guide orLANGUAGE.md.- This is the exhaustive name index; the topic chapters remain the best place to learn composition, constraints, and examples.
Standard shapes, styling, and motion
| API | Kind | What it does |
|---|---|---|
circle(id: new id, center: (x, y), r: number) | constructor / setup | a circle |
rect(id: new id, center: (x, y), w: number, h: number) | constructor / setup | a rectangle |
image(id: new id, center: (x, y), path: string, [w: number], [h: number]) | constructor / setup | a raster image (PNG/JPG) from a bundled asset: URI or file path, centred at a point (w×h px) |
svg(id: new id, center: (x, y), path: string, [size: number]) | constructor / setup | import VECTOR artwork from an asset: URI or file path as native path entities (each subpath a traceable Polyline/Polygon), fitted to size px wide and centred; pieces are tagged {id} so draw/show/fade/hue/gradient/move broadcast |
equation(id: new id, center: (x, y), latex: string, [size: number]) | constructor / setup | typeset a LaTeX math string centred at a point; standard \textcolor{cyan}{…} gives individual terms semantic Manic colours that follow the template. Put LaTeX in BACKTICKS; animate with show/fade/move/scale (image, not draw-on) |
line(id: new id, from: (x, y), to: (x, y)) | constructor / setup | a line segment |
link(id: new id, from: id or tag, to: id or tag, [bend: number]) | constructor / setup | a straight or bent edge that follows two moving entities |
arrow(id: new id, from: (x, y), to: (x, y)) | constructor / setup | an arrow |
polygon(…) | constructor / setup | a filled polygon: polygon(id, (x1,y1), (x2,y2), (x3,y3), …, [color]) |
dot(id: new id, at: (x, y), [r: number]) | constructor / setup | a small filled dot |
particles(id: new id, container: id or tag, count: number, [radius: number], [seed: number], [layout: string]) | constructor / setup | deterministic small dots inside a circle or rectangle; optional random/grid/ring layout |
text(id: new id, at: (x, y), text: string) | constructor / setup | a text label |
label(…) | constructor / setup | a label pinned to an entity |
caption(id: new id, words: string, at: (x, y), [size: number], [color: colour]) | constructor / setup | a row of caption words (karaoke/pop) |
support(id: new id, center: (x, y), [len: number], [dir: string]) | constructor / setup | a hatched fixed support (wall/ceiling/floor) for mechanics diagrams; dir = open side |
counter(id: new id, at: (x, y), value: number, [decimals: number], [prefix: string], [suffix: string]) | constructor / setup | a live numeric readout |
parameter(id: new id, at: (x, y), initial: number, min: number, max: number, [label: string], [decimals: number]) | constructor / setup | a visible bounded value for a smooth multi-visual journey |
bind(parameter: id or tag, target: id or tag, property: new id, formula_or_from: string, [to: number], [formula_component_3: string]) | constructor / setup | connect a parameter to a property, plot/field formula, or stable generated 3-D family |
cursor(id: id or tag) | constructor / setup | give a text entity a typewriter cursor |
sticky(id: id or tag) | constructor / setup | pin an entity to the screen so it stays fixed through cam/zoom (HUD overlay) |
morph(…) | constructor / setup | sampled-point shape morph a->b |
invertpath(id: new id, source: id or tag, center: id or tag, radius: number, [samples: number]) | constructor / setup | live whole-outline inversion about a named center |
reflectpath(id: new id, source: id or tag, mirror: id or tag, [samples: number]) | constructor / setup | live whole-outline reflection across a named line |
copy(new: new id, src: id or tag) | constructor / setup | duplicate an entity |
color(id: id or tag, color: colour) | constructor / setup | set fill/stroke colour |
outline(id: id or tag, color: colour) | constructor / setup | set outline colour |
size(id: id or tag, size: number) | constructor / setup | set text size |
wrap(id: id or tag, width: number) | constructor / setup | wrap a text/caption/$…$ label to a width (px), breaking at word boundaries; inline math stays atomic |
stroke(id: id or tag, width: number) | constructor / setup | set stroke width |
dashed(id: id or tag, [dash: number], [gap: number]) | constructor / setup | use a repeating dash/gap stroke on a path-like entity |
gradient(…) | constructor / setup | multi-stop gradient on the primary paint: gradient(id, c1, c2, …, [mode]) — along a path stroke by arc length, a linear (angle°) / radial fill, or a computed “speed”/“curvature” quantity on strokes |
glow(id: id or tag, amount: number) | constructor / setup | set neon glow amount |
opacity(id: id or tag, value: number) | constructor / setup | set opacity 0..1 |
hue(id: id or tag, degrees: number, [s: number], [l: number]) | constructor / setup | drive colour by an HSL hue |
rot(id: id or tag, degrees: number) | constructor / setup | set rotation (degrees) |
z(id: id or tag, z: number) | constructor / setup | set draw order |
clip(id: id or tag, region: id or tag) | constructor / setup | clip an entity (or tagged group) to another entity’s rectangular bounds — reveal-through-a-window / crop-to-panel / region-wipe; the scissor follows the region each frame |
mask(id: id or tag, region: id or tag) | constructor / setup | clip an entity (or tagged group) to another entity’s ARBITRARY shape (circle, polygon, star, an imported svg outline) — reveal-through-any-shape / lens / porthole / spotlight; masks any content and follows the region each frame |
tag(id: id or tag, tag: new id) | constructor / setup | add a group tag |
bold(id: id or tag) | constructor / setup | use the bold mono font |
display(id: id or tag) | constructor / setup | mark visible |
hidden(id: id or tag) | constructor / setup | start hidden (opacity 0) |
filled(id: id or tag) | constructor / setup | turn the fill on |
outlined(id: id or tag) | constructor / setup | turn the outline on |
untraced(id: id or tag) | constructor / setup | start undrawn, ready for draw-on |
union(…) | constructor / setup | boolean union of two shapes |
intersect(…) | constructor / setup | boolean intersection |
intersection(…) | constructor / setup | boolean intersection |
difference(…) | constructor / setup | boolean difference a-b |
subtract(…) | constructor / setup | boolean difference a-b |
exclusion(…) | constructor / setup | boolean symmetric difference |
xor(…) | constructor / setup | boolean symmetric difference |
brace(…) | constructor / setup | a curly brace |
bracelabel(…) | constructor / setup | a brace with a label |
bracetext(…) | constructor / setup | a brace’s text label |
draw(id: id or tag, [dur: number], [ease: easing]) | animation verb | trace a stroke on |
flow(path: id or tag, [dur: number], [direction: id or tag], [mode: id or tag]) | animation verb | send a directional luminous pulse or finite continuous stream over a path |
erase(id: id or tag, [dur: number], [ease: easing]) | animation verb | reverse of draw |
show(id: id or tag, [dur: number]) | animation verb | fade in |
fade(id: id or tag, [dur: number]) | animation verb | fade out |
flash(id: id or tag, [color: colour], [dur: number], [ease: easing]) | animation verb | flash a colour, then restore |
recolor(id: id or tag, color: colour, [dur: number]) | animation verb | permanently change colour |
pulse(id: id or tag, [dur: number]) | animation verb | grow-and-settle attention pulse |
oscillate(id: id or tag, prop: new id, period: number, amp: number, [phase: number], [dur: number]) | animation verb | continuously oscillate a property sinusoidally — prop is size|opacity|x|y|hue; period seconds/cycle; amp is a scale delta (size), opacity delta, pixels (x/y) or degrees (hue); phase in cycles (0–1) — give looped siblings a per-index phase for a travelling wave; runs for dur (default 6s). Works on a tag. |
breathe(id: id or tag, period: number, amp: number, [phase: number], [dur: number]) | animation verb | size oscillation (alias for oscillate with prop=size): the entity grows and shrinks; give looped siblings a per-index phase for a breathing wave |
shake(id: id or tag, [dur: number]) | animation verb | horizontal shake (error gesture) |
spin(id: id or tag, [degrees: number], [dur: number]) | animation verb | spin about the centre |
move(id: id or tag, to: (x, y), [dur: number], [ease: easing]) | animation verb | move to an absolute point |
wander(particles: id or tag, [dur: number]) | animation verb | gently move a particle group inside its source container |
burst(particles: id or tag, [dur: number]) | animation verb | explode a particle group outward from its container centre, decelerating and fading — the impulse/impact/confetti/ignition beat |
arrange(particles: id or tag, container: id or tag, [layout: string], [dur: number], [ease: easing]) | state-changing verb | move persistent particles into a deterministic random, grid, or ring layout; random uses stable organic curved routes |
travel(entity: id or tag, path: id or tag, [dur: number], [ease: easing]) | state-changing verb | move one persistent entity once along an existing path and stop at its endpoint |
attach(child: id or tag, target: id or tag, [offset: (x, y)]) | state-changing verb | keep an entity pinned to another entity plus an optional offset; use target none to release |
become(source: id or tag, target: id or tag, [dur: number], [ease: easing]) | state-changing verb | transform one entity into a declared visual blueprint while retaining the source id |
turn(id_or_tag: id or tag, pivot: (x, y), degrees: number, [dur: number], [ease: easing]) | state-changing verb | rotate one entity or tagged arrangement rigidly around a shared pivot |
disintegrate(entity: id or tag, [dur: number]) | state-changing verb | dissolve an entity’s actual silhouette into deterministic seekable particles |
shift(id: id or tag, by: (x, y), [dur: number], [ease: easing]) | animation verb | move by a delta |
grow(id: id or tag, to: (x, y), [dur: number]) | animation verb | animate a line/arrow endpoint |
scale(id: id or tag, factor: number, [dur: number]) | animation verb | animate scale to a factor |
rotate(id: id or tag, degrees: number, [dur: number], [ease: easing]) | animation verb | animate rotation |
say(id: id or tag, text: string, [dur: number], [ease: easing]) | animation verb | crossfade text to new content |
speak(…) | animation verb | narrate with TTS; service from voice(“gtts”|“cartesia”|“elevenlabs”) (default gTTS); speak(“text”) or speak(caption, “text”); duration is the voice length |
cue(sound: id or tag) | animation verb | play a deterministic procedural sound beat: tick, pop, whoosh, or chime |
rewrite(equation: id or tag, latex: string, [dur: number], [ease: easing]) | state-changing verb | smoothly transform one authored LaTeX equation state into the next while matching unchanged visual parts; this animates supplied math and does not solve it |
type(id: id or tag, [dur: number]) | animation verb | typewriter reveal |
to(id: id or tag, prop: new id, value: number, [dur: number], [ease: easing]) | animation verb | animate any property to a value |
set(id: id or tag, prop: new id, value: number, [dur: number], [ease: easing]) | animation verb | alias of to |
transform(id: id or tag, origin: (x, y), a: number, b: number, c: number, d: number, [dur: number], [ease: easing]) | animation verb | apply a 2x2 matrix (ApplyMatrix) |
swap(a: id or tag, b: id or tag, [dur: number]) | state-changing verb | swap two entities, or array slots i,j |
cycle(…) | state-changing verb | move entities cyclically into one another’s positions along an optional arc |
cam(to: (x, y), [dur: number], [ease: easing]) | animation verb | pan the camera to a point |
zoom(factor: number, [dur: number], [ease: easing]) | animation verb | zoom the camera |
karaoke(…) | animation verb | highlight caption words in sequence |
wordpop(…) | animation verb | pop caption words in one at a time |
Generative
| API | Kind | What it does |
|---|---|---|
lsystem(id: new id, center: (x, y), size: number, axiom: string, rules: string, [options: string]) | constructor / setup | a fitted, batched curve L-system with turtle turns, continuous draw-on, and optional closed concave fill |
repeat(id: new id, motif: id or tag, options: string) | constructor / setup | repeat one entity or tagged motif as a stable hex, grid, or radial arrangement |
ifs2(id: new id, center: (x, y), size: (x, y), count: number, seed: number, rules: string, [options: string]) | constructor / setup | a deterministic batched affine iterated-function system |
mandelbrot(id: new id, center: (x, y), size: (x, y), xrange: (x, y), yrange: (x, y), iterations: number, [columns: number]) | constructor / setup | a batched escape-time Mandelbrot field |
polarpath(id: new id, center: (x, y), scale: number, formula: string, range: (x, y), [samples: number], [closed: number]) | constructor / setup | a sampled polar formula path |
hull2(id: new id, cloud: id or tag, [depth: number], [pivot: number]) | constructor / setup | an onion-depth hull derived from a batched point cloud |
Process
| API | Kind | What it does |
|---|---|---|
livehistogram(id: new id, center: (x, y), min: number, max: number, bins: number, [width: number], [height: number], [color: colour]) | constructor / setup | an initially empty histogram driven by real measurements from a deterministic collection process |
stream(particles: id or tag, path: id or tag, dur: number, [spread: number], [ease: easing]) | state-changing verb | progressively send a persistent particle collection along a path and retain truthful arrival/speed measurements |
emit(particles: id or tag, path: id or tag, profile: id or tag, dur: number, [spread: number], [ease: easing]) | state-changing verb | release a persistent particle collection with deterministic individual speeds sampled from a named profile |
advect(particles: id or tag, field: id or tag, duration: number, [rate: number]) | state-changing verb | move a persistent particle collection through a bounded vector field using deterministic seekable integration |
branch(particles: id or tag, paths: id or tag, dur: number, [ease: easing]) | state-changing verb | send every persistent item through one seeded route in a tagged directed acyclic path network |
collect(histogram: id or tag, particles: id or tag, measurement: id or tag, [dur: number], [ease: easing]) | state-changing verb | catch processed particles in the live-histogram bins selected by their real speed, arrival, outcome, or step measurement |
observe(target: id or tag, particles: id or tag, measurement: id or tag) | animation verb | drive a counter or live histogram from measurements produced by the latest stream, emit, or branch process |
Charts
| API | Kind | What it does |
|---|---|---|
racechart(id: new id, layout: string, periods: string, [title: string]) | constructor / setup | declare an animated race chart: layout “bar” (v1), a period-label list that ticks as it plays, and an optional title; feed rows with racedata, then play with race |
racedata(parent: new id, block: string) | constructor / setup | paste the data for a race chart — one row per entity: label [icon] v0 v1 …; rows split on newline or ;, cells on comma/tab/whitespace; the optional icon is an SVG shorthand (us → a flag) or a full asset path |
raceseries(parent: new id, label: string, icon_or_values: string, [values: string]) | constructor / setup | add ONE entity to a race chart (the per-series sibling of racedata, for the computed/loop path): raceseries(parent, "label", ["icon"], "v0 v1 …") |
raceline(parent: new id, [label: string], [values: string]) | constructor / setup | add a companion LINE across the top of a bar race — a running total (auto-summed from the bars) or an explicit tracked metric: raceline(parent, ["label"], ["v0 v1 …"]) |
racepanel(parent: new id) | constructor / setup | add a multi-line HISTORY panel at the bottom of a bar race — every series drawn as a line up to a moving time-cursor, synced to the race (the classic bar+line combo): racepanel(parent) |
race(parent: new id, [dur: number], [ease: new id]) | animation verb | play the whole race: interpolate each series between periods, re-rank, slide bars to their new slots (the reorder IS the race), rescale to the running max, and tick the period + value counters |
Math
| API | Kind | What it does |
|---|---|---|
axes(id: new id, center: (x, y), halfw: number, halfh: number, [unit: number]) | constructor / setup | a coordinate frame (optional tick spacing in px) |
plot(id: new id, center: (x, y), sx: number, sy: number, formula: function or formula, [domain: number]) | constructor / setup | y = f(x) as a curve |
normal(id: new id, curve: new id, x: number, [len: number]) | constructor / setup | the normal (perpendicular) line to a curve at x |
slope(id: new id, curve: new id, x: number, [offset: (x, y)]) | constructor / setup | a live readout of a curve’s slope at x |
area(id: new id, curve: new id, a: number, b: number, [n: number]) | constructor / setup | the filled region under a curve from a to b |
integral(id: new id, curve: new id, a: number, b: number, [at: (x, y)]) | constructor / setup | a live readout of the integral of a curve from a to b |
roots(id: new id, curve: new id, [color: colour]) | constructor / setup | dots where a curve crosses zero |
deriv(id: new id, curve: new id, [color: colour]) | constructor / setup | the derivative f’ of a curve, drawn as its own curve |
accum(id: new id, curve: new id, [a: number], [color: colour]) | constructor / setup | the accumulation function integral(a..x) of a curve |
extrema(id: new id, curve: new id, [color: colour]) | constructor / setup | dots at a curve’s maxima and minima (slope = 0) |
inflections(id: new id, curve: new id, [color: colour]) | constructor / setup | dots where a curve changes concavity (f’’ = 0) |
band(id: new id, top: new id, bottom: new id, [color: colour]) | constructor / setup | the filled region between two curves |
taylor(id: new id, curve: new id, a: number, n: number, [color: colour]) | constructor / setup | the degree-n Taylor polynomial of a curve about a |
limit(id: new id, curve: new id, a: number, [color: colour]) | constructor / setup | visualise lim(x->a) f(x): the value approached, with an approaching dot |
newton(id: new id, curve: new id, x0: number, [steps: number]) | constructor / setup | Newton’s method zig-zag from a starting guess |
spline(…) | constructor / setup | a smooth curve through a list of points |
trajectory(id: new id, dx/dt: string, dy/dt: string, start: (x, y), center: (x, y), scale: number, [steps: number]) | constructor / setup | an ODE path: dx/dt, dy/dt integrated from a start point |
linmap(id: new id, center: (x, y), unit: number, a: number, b: number, c: number, d: number, [span: number]) | constructor / setup | a 2x2 matrix applied to the plane (grid + basis) |
determinant(id: new id, center: (x, y), unit: number, a: number, b: number, c: number, d: number, [color: colour]) | constructor / setup | the unit square mapped to a parallelogram; area = det |
eigen(id: new id, center: (x, y), unit: number, a: number, b: number, c: number, d: number, [color: colour]) | constructor / setup | a matrix’s real eigenvector directions (with eigenvalues) |
diagonalise(id: new id, center: (x, y), unit: number, a: number, b: number, c: number, d: number, [color: colour]) | constructor / setup | A = P D P^-1: in the eigenbasis the matrix is a pure stretch |
diagonalize(id: new id, center: (x, y), unit: number, a: number, b: number, c: number, d: number, [color: colour]) | constructor / setup | A = P D P^-1: in the eigenbasis the matrix is a pure stretch |
rref(id: new id, data: string, center: (x, y), [cellw: number], [rowh: number]) | constructor / setup | animated Gaussian elimination to reduced row-echelon form |
project(id: new id, center: (x, y), unit: number, b: (x, y), a: (x, y), [color: colour]) | constructor / setup | orthogonal projection of a vector onto a line (subspace) |
leastsquares(id: new id, center: (x, y), unit: number, data: string, [color: colour]) | constructor / setup | the best-fit line through a point cloud (linear regression) |
linsolve(id: new id, center: (x, y), unit: number, a: number, b: number, c: number, d: number, e: number, f: number, [span: number]) | constructor / setup | the row picture of Ax=b: two lines meeting at the solution |
span(id: new id, center: (x, y), unit: number, v: (x, y), [w: (x, y)], [color: colour]) | constructor / setup | the span of one or two vectors (a line, or the whole plane) |
vector(id: new id, origin: (x, y), delta: (x, y), [color: colour]) | constructor / setup | an arrow from an origin |
numberline(…) | constructor / setup | a labelled number line |
numberplane(…) | constructor / setup | a gridded coordinate plane |
plane(…) | constructor / setup | a coordinate plane |
complexplane(…) | constructor / setup | the complex plane |
polarplane(…) | constructor / setup | a polar grid |
matrix(id: new id, data: string, center: (x, y), [colw: number], [rowh: number]) | constructor / setup | a bracketed matrix |
table(id: new id, data: string, center: (x, y), [colw: number], [rowh: number], [cols: string], [rows: string]) | constructor / setup | a ruled table |
mathtable(…) | constructor / setup | a table of math expressions |
integertable(…) | constructor / setup | a table of integers |
decimaltable(…) | constructor / setup | a table of decimals |
arc(…) | constructor / setup | a circular arc |
sector(…) | constructor / setup | a pie sector |
annulus(…) | constructor / setup | a ring / annular sector |
pie(…) | constructor / setup | a pie chart |
arrowfield(…) | constructor / setup | a named or formula-authored bounded vector field |
vectorfield(…) | constructor / setup | a named or formula-authored bounded vector field |
tangent(…) | constructor / setup | tangent line to a curve at x (or tangent points to a circle) |
Systems
| API | Kind | What it does |
|---|---|---|
architecture(id: new id, [center: (x, y)], [width: number], [height: number]) | constructor / setup | an automatically laid-out diagram canvas; geometry is optional and auto-fits the canvas when omitted |
flowchart(id: new id, [direction: id or tag], [max_nodes: number]) | constructor / setup | an edge-ranked flowchart canvas (Mermaid graph TD/LR); with no direction it auto-orients TD/LR to fit and re-decides as nodes are added; reuses the architecture layout/ports/scale-to-fit. Optional max_nodes is the readability split limit (default 6 top-down, 12 left-right) — over it, check warns to split into linked sub-flows |
c4(id: new id, [level: id or tag]) | constructor / setup | a C4-model diagram canvas (Context/Container/Component); inside it node kinds read as C4 elements (person/system/container/component/external) with name/[type: tech]/description |
node(id: new id, parent: id or tag, kind: string, label: string, [description: string], [technology: string]) | constructor / setup | a component inside an architecture/cluster/c4/flowchart; kind is a provider icon, native archetype, flowchart shape, or C4 element. In a c4 diagram the extra args are the box’s description and technology |
cluster(id: new id, parent: id or tag, label: string, [legacy_members: string]) | constructor / setup | a labelled responsive ownership group; children name it as their parent |
connect(id: new id, from: id or tag, to: id or tag, [routing: routing], [from_port: port], [to_port: port]) | constructor / setup | a dashed directed possibility with an optional bend or port-aware orthogonal route |
annotate(edge: id or tag, text: string) | constructor / setup | a small caption at a connection’s midpoint (a decision’s yes/no, or any edge annotation) |
message(id: new id, source: id or tag, label: string) | constructor / setup | a persistent generic message starting at one system node |
request(id: new id, source: id or tag, label: string) | constructor / setup | a persistent labelled request starting at one system node |
route(message: id or tag, connection: id or tag, [duration: number], [ease: easing]) | state-changing verb | move one persistent message through a continuous named connection and illuminate its selected lane |
hotpath(message: id or tag, [duration: number], [seed: number]) | state-changing verb | move one persistent message end-to-end over a seeded valid path, choosing one lane at each fan-out |
Geometry
| API | Kind | What it does |
|---|---|---|
point(id: new id, at: (x, y), [label: string]) | constructor / setup | a labelled point |
segment(id: new id, a: id or tag, b: id or tag) | constructor / setup | segment between two points |
midpoint(id: new id, a: id or tag, b: id or tag) | constructor / setup | midpoint of two points |
centroid(…) | constructor / setup | centroid of a triangle |
circumcenter(…) | constructor / setup | circumcentre |
incenter(…) | constructor / setup | incentre |
orthocenter(…) | constructor / setup | orthocentre |
foot(…) | constructor / setup | foot of a perpendicular |
meet(…) | constructor / setup | line-line intersection |
linecircle(…) | constructor / setup | line-circle intersection |
circlecircle(…) | constructor / setup | circle-circle intersection |
commontangent(id: new id, oA: id or tag, aOn: id or tag, oB: id or tag, bOn: id or tag, [type: string]) | constructor / setup | a common tangent to two circles (each = centre + a point on it): segment {id} between the touch points {id}.a/{id}.b; type external/direct (default) or internal/transverse |
reflect(…) | constructor / setup | reflect a point over a line |
bisector(…) | constructor / setup | angle bisector |
rotpoint(…) | constructor / setup | rotate a point about another |
between(…) | constructor / setup | a point between two others |
anglepoint(…) | constructor / setup | a point at an angle |
circumcircle(…) | constructor / setup | circumscribed circle |
incircle(…) | constructor / setup | inscribed circle |
circle2(…) | constructor / setup | circle from centre + a point on it |
ellipse(…) | constructor / setup | an ellipse |
parabola(…) | constructor / setup | a parabola |
hyperbola(…) | constructor / setup | a hyperbola |
fullline(…) | constructor / setup | an infinite line through two points |
anglemark(…) | constructor / setup | an angle arc mark |
rightangle(…) | constructor / setup | a right-angle square mark |
Algorithms and data structures
| API | Kind | What it does |
|---|---|---|
graph(id: new id, verts: string, edges: string, layout: new id, center: (x, y), scale: number, [radius: number]) | constructor / setup | a node/edge graph (weights via a-b:w) |
array(id: new id, vals: string, center: (x, y), [cw: number], [ch: number]) | constructor / setup | a row of value cells in slot boxes |
list(id: new id, vals: string, center: (x, y), [kind: new id], [cw: number], [ch: number]) | constructor / setup | a linked list (singly/doubly/circular) |
stack(id: new id, center: (x, y), [cw: number], [ch: number]) | constructor / setup | a stack (LIFO, grows up) |
queue(id: new id, center: (x, y), [cw: number], [ch: number]) | constructor / setup | a queue (FIFO, grows right) |
hashmap(id: new id, n: number, center: (x, y), [ew: number], [ch: number]) | constructor / setup | n buckets with separate chaining |
pointer(id: new id, arr: id or tag, slot: number, [label: string]) | constructor / setup | an index caret under an array slot |
caret(id: new id, at: (x, y), label: string, [dir: new id]) | constructor / setup | a labelled triangle marker |
compare(arr: id or tag, i: number, j: number, [color: colour]) | animation verb | flash the values in two array slots |
pointat(id: id or tag, arr: id or tag, slot: number, [dur: number]) | animation verb | slide an index pointer to a slot |
push(id: id or tag, value: string, [dur: number]) | state-changing verb | push onto a stack |
pop(id: id or tag, [dur: number]) | state-changing verb | pop the top of a stack |
enqueue(id: id or tag, value: string, [dur: number]) | state-changing verb | enqueue at the back |
dequeue(id: id or tag, [dur: number]) | state-changing verb | dequeue from the front |
insert(id: id or tag, after: number, value: string, [dur: number]) | state-changing verb | splice a node into a list |
remove(id: id or tag, index: number, [dur: number]) | state-changing verb | unlink a list node |
put(id: id or tag, key: string, val: string, [dur: number]) | state-changing verb | hash a key into a bucket + chain |
get(id: id or tag, key: string, [dur: number]) | animation verb | scan a bucket’s chain for a key |
bfs(g: id or tag, start: id or tag) | state-changing verb | breadth-first traversal (queue) |
dfs(g: id or tag, start: id or tag) | state-changing verb | depth-first traversal (stack) |
dijkstra(g: id or tag, start: id or tag) | state-changing verb | single-source shortest paths |
Grid
| API | Kind | What it does |
|---|---|---|
grid(id: new id, [center: (x, y)], [cols: number], [rows: number], [cellsize: number], [seeded_cellsize: number]) | constructor / setup | a 2D cell grid (arg1 a “# . @ *” spec seeds it, else it is the centre) |
neighbors(id: id or tag, mode: string) | constructor / setup | grid connectivity: “4” (default) or “8” (diagonals) |
setcell(id: id or tag, r: number, c: number, kind: id or tag) | constructor / setup | set one cell’s kind (wall/open/start/goal) |
walls(id: id or tag, cells: string) | constructor / setup | batch-set several cells to wall: “r,c r,c …” |
gridbfs(id: id or tag, start: (x, y), goal: (x, y)) | state-changing verb | unweighted BFS over open cells (spatial sibling of bfs) |
gridastar(id: id or tag, start: (x, y), goal: (x, y), [heuristic: id or tag]) | state-changing verb | A* pathfinding with a heuristic (manhattan/euclidean/diagonal) |
evolve(id: id or tag, rule: string) | constructor / setup | pre-simulate one CA generation (“life” or a Golly rulestring) |
collapse(id: id or tag, tileset: string, [seed: number]) | constructor / setup | pre-simulate a seeded Wave-Function-Collapse settling |
3D
| API | Kind | What it does |
|---|---|---|
camera3(eye: (x, y, z), target: (x, y, z), [fov: number], [projection: id or tag]) | constructor / setup | a Z-up orbit camera |
point3(id: new id, at: (x, y, z), [radius: number]) | constructor / setup | a point in 3D space |
line3(id: new id, from: (x, y, z), to: (x, y, z)) | constructor / setup | a 3D line segment |
arrow3(id: new id, from: (x, y, z), to: (x, y, z)) | constructor / setup | a 3D vector arrow |
cube3(id: new id, center: (x, y, z), size: (x, y, z)) | constructor / setup | a rotatable 3D cuboid |
linmap3(id: new id, center: (x, y, z), a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, [color: colour]) | constructor / setup | a 3x3 matrix deforming the unit cube; volume = determinant |
eigen3(id: new id, center: (x, y, z), a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, [color: colour]) | constructor / setup | the real eigenvector directions of a 3x3 matrix (invariant lines) |
sphere3(id: new id, center: (x, y, z), radius: number) | constructor / setup | a 3D sphere |
collection3(id: new id, center: (x, y, z), count: number, spread: (x, y, z), seed: number, [radius: number]) | constructor / setup | renderer-batched repeated 3D points with stable indexed children |
collection3data(id: new id, center: (x, y, z), points: string, [radius: number]) | constructor / setup | renderer-batched explicit 3D coordinates with stable indexed children |
links3(id: new id, collection: id or tag, mode: id or tag, [neighbors: number]) | constructor / setup | live relationship mesh over a collection3: chain, nearest, or all |
links3data(id: new id, collection: id or tag, edges: string) | constructor / setup | an explicit arbitrary edge list that follows a 3D collection |
trail3(id: new id, collection: id or tag, [child: number], [thickness: number]) | constructor / setup | live history path for one stable indexed child of a collection3 |
ring3(id: new id, collection: id or tag, child: number, [segments: number]) | constructor / setup | live orbit ring derived from one segment of a collection3 chain |
historyplot(id: new id, collection: id or tag, child: number, component: id or tag, center: (x, y), size: (x, y)) | constructor / setup | screen-space 2D component history derived from one compiled collection3 child |
historyplot3(id: new id, collection: id or tag, child: number, component: id or tag, origin: (x, y, z), size: (x, y)) | constructor / setup | world-space 3D component history derived from one compiled collection3 child |
child3(id: new id, collection: id or tag, index: number, [radius: number]) | constructor / setup | addressable proxy for one stable indexed child of a collection3 |
tree3(id: new id, root: (x, y, z), length: number, angle: number, shrink: number, depth: number, seed: number) | constructor / setup | seeded fractal tree batched as one edge mesh per generation |
vectorfield3(id: new id, center: (x, y, z), half: (x, y, z), u: string, v: string, w: string, [density: number]) | constructor / setup | bounded batched 3D vector field from x/y/z/p component formulas |
grid3(id: new id, center: (x, y, z), half: number, [spacing: number]) | constructor / setup | an XY ground grid |
axes3(id: new id, origin: (x, y, z), length: number, [step: number]) | constructor / setup | three colored coordinate axes with ticks + numbers |
frame3(id: new id, center: (x, y, z), size: (x, y, z), [options: string]) | constructor / setup | bounded scientific axes and selectable XY/XZ/YZ grid planes |
randomwalk3(id: new id, center: (x, y, z), steps: number, [seed: number], [options: string]) | constructor / setup | a deterministic batched 3D axis or turtle walk with optional direction color and depth shading |
hilbert3(id: new id, center: (x, y, z), size: number, order: number, [options: string]) | constructor / setup | a bounded batched 3D Hilbert curve that visits every lattice cell once |
present3(frame: id or tag, mode: id or tag, [dur: number], [ease: easing]) | state-changing verb | restyle one frame3 as textbook linework or spatial color |
move3(id: id or tag, to: (x, y, z), [dur: number], [ease: easing]) | animation verb | move a 3D entity |
shift3(id: id or tag, by: (x, y, z), [dur: number], [ease: easing]) | animation verb | shift a 3D entity |
rotate3(id: id or tag, degrees: (x, y, z), [dur: number], [ease: easing]) | animation verb | rotate to Euler angles |
grow3(id: id or tag, to: (x, y, z), [dur: number], [ease: easing]) | animation verb | retarget a 3D line or arrow |
orbit3(azimuth: number, elevation: number, radius: number, [dur: number], [ease: easing]) | animation verb | orbit the 3D camera |
roll3(degrees: number, [dur: number], [ease: easing]) | animation verb | roll the 3D camera around its viewing direction |
look3(target: (x, y, z), [dur: number], [ease: easing]) | animation verb | move the 3D camera target |
drift3(collection: id or tag, dur: number, [amount: number]) | state-changing verb | compile deterministic bounded ambient motion for a collection3 |
chain3(collection: id or tag, lengths: string, rates: string, dur: number) | state-changing verb | animate a generated dependent chain from per-link lengths and rotation rates |
advect3(collection: id or tag, field: id or tag, dur: number, [rate: number]) | state-changing verb | advect a collection3 through a time-varying vectorfield3 with deterministic RK4 |
followshot3(target: id or tag, [offset: (x, y, z)]) | state-changing verb | make the 3D camera target follow an entity, or release with none |
view3(target: id or tag, view: string, [dur: number], [ease: easing], [margin: number]) | state-changing verb | smoothly frame a 3D entity or tagged group from a named view |
travel3(entity: id or tag, path: id or tag, [dur: number], [ease: easing]) | state-changing verb | move a persistent 3D entity along a line, arrow, or curve |
attach3(child: id or tag, target: id or tag, [offset: (x, y, z)], [mode: id or tag]) | state-changing verb | attach one 3D entity to another, or release it with none |
become3(source: id or tag, blueprint: id or tag, [dur: number], [ease: easing]) | state-changing verb | transform a persistent 3D entity into a blueprint |
turn3(target: id or tag, pivot: (x, y, z), axis: id or tag, degrees: number, [dur: number], [ease: easing]) | state-changing verb | turn a 3D entity or tagged group around a world-space pivot and axis |
pin3(label: id or tag, at: (x, y, z), [offset: (x, y)]) | constructor / setup | glue a 2D label to a 3D point with an optional screen-space offset |
follow3(id: id or tag, target: id or tag, [offset: (x, y, z)]) | constructor / setup | track another 3D entity + offset each frame |
midpoint3(id: new id, a: id or tag, b: id or tag) | constructor / setup | a point at the midpoint of two 3D entities |
link3(id: new id, a: id or tag, b: id or tag, [trim: number]) | constructor / setup | a live 3D edge between two moving entities |
project3(id: new id, source: id or tag, plane: string) | constructor / setup | live orthogonal projection of a 3D entity onto xy, xz, or yz |
projectpath3(id: new id, source: id or tag, plane: id or tag) | constructor / setup | live orthogonal projection of a complete changing 3D path |
contour3(id: new id, surface: id or tag, level: number) | constructor / setup | a level curve on a surface3 height field |
label3(label: id or tag, target: id or tag, [world_height: number]) | constructor / setup | projected label with optional world-space height |
curve3(id: new id, x: string, y: string, z: string, [domain: (x, y)]) | constructor / setup | parametric 3D curve from x(t)/y(t)/z(t) formulas |
surface3(id: new id, z: string, xdom: (x, y), ydom: (x, y), [res: number]) | constructor / setup | filled, flat-shaded height-field surface z = f(x,y) |
implicit3(id: new id, formula: string, xdom: (x, y), ydom: (x, y), zdom: (x, y), [level: number], [resolution: number]) | constructor / setup | an isosurface extracted from a bounded 3D scalar field |
lsystem3(id: new id, origin: (x, y, z), step: number, angle: number, iterations: number, axiom: string, rules: string, [max_symbols: number]) | constructor / setup | a bounded deterministic branching 3D turtle grammar |
heightmap3(id: new id, grid: id or tag, z: string, [size: number]) | constructor / setup | lift a 2D grid into a 3D terrain mesh: z(x,y,h), h=cell value |
gradient3(id: new id, surface: new id, x: number, y: number, [color: colour]) | constructor / setup | steepest-ascent arrow on a surface3 at (x,y) |
tangentplane3(id: new id, surface: new id, x: number, y: number, [color: colour]) | constructor / setup | the plane tangent to a surface3 at (x,y) |
volume3(id: new id, surface: new id, [res: number], [color: colour]) | constructor / setup | the volume under a surface3 as a grid of columns |
param3(id: new id, x: string, y: string, z: string, udom: (x, y), vdom: (x, y), [res: number]) | constructor / setup | parametric surface x(u,v),y(u,v),z(u,v) — tori, shells, Möbius |
prism3(id: new id, center: (x, y, z), sides: number, radius: number, height: number) | constructor / setup | regular n-gon prism (many sides ≈ cylinder) |
pyramid3(id: new id, center: (x, y, z), sides: number, radius: number, height: number) | constructor / setup | regular n-gon pyramid (many sides ≈ cone) |
revolve3(id: new id, center: (x, y, z), profile: string, tdom: (x, y), [sides: number]) | constructor / setup | solid of revolution from a radius profile r(t) |
extrude3(id: new id, source: id or tag, height: number, [center: (x, y, z)]) | constructor / setup | extrude a 2D shape or boolean region into a solid prism (CSG) |
morph3(a: id or tag, b: id or tag, [spin: number]) | constructor / setup | set 3D entity a up to morph into b’s shape (animate with to(a, morph, 1, dur)) |
thick(id: id or tag, radius: number) | constructor / setup | render a 3D curve3/line3/arrow3 as a tube of the given world radius |
finish3(id: id or tag, spec: string) | constructor / setup | bounded 3D finish: shading (flat|smooth), material (matte|metal|glass), texture (solid|checker|stripes), scale, mesh, wire (0..1 — render any solid as a bright triangulated WIREFRAME with no filled faces; sphere/cube/prism/pyramid/extrude/surface), depth, shadow |
model3(id: new id, path: string, center: (x, y, z), [scale: number]) | constructor / setup | load deterministic geometry from a bundled asset: URI or safety-limited OBJ path |
assembly3(id: new id, path: string, center: (x, y, z), [scale: number]) | constructor / setup | load grouped OBJ geometry as addressable id.group entities sharing one id tag |
tube3(id: new id, path: id or tag, radius: string, [sides: number]) | constructor / setup | variable-radius tube around a line3, arrow3, or curve3 |
Brand
| API | Kind | What it does |
|---|---|---|
banner(id: new id, center: (x, y), [scale: number]) | constructor / setup | the manic logo/banner |
watermark(id: new id, [at: (x, y)], [text: string]) | constructor / setup | a screen-fixed watermark; omit the point for responsive bottom-right placement or pass an exact point |
Stats
| API | Kind | What it does |
|---|---|---|
histogram(id: new id, center: (x, y), data: string, [bins: number], [width: number], [height: number], [color: colour]) | constructor / setup | bin a dataset into bars — the shape of the data |
summary(id: new id, center: (x, y), data: string, [width: number], [color: colour]) | constructor / setup | describe a dataset: mean/median/mode markers, spread, and readouts |
correlation(id: new id, center: (x, y), unit: number, data: string, [color: colour]) | constructor / setup | scatter + best-fit line + the Pearson correlation r |
skew(id: new id, center: (x, y), data: string, [bins: number], [width: number], [height: number], [color: colour]) | constructor / setup | a histogram labelled with its skewness (left / right / symmetric) |
boxplot(id: new id, center: (x, y), data: string, [width: number], [color: colour]) | constructor / setup | five-number summary as a box-and-whisker (box = IQR) |
hypothesis(id: new id, center: (x, y), z: number, [alpha: number], [unit: number]) | constructor / setup | a two-tailed significance test: p-value as shaded normal tails |
covariance(id: new id, center: (x, y), unit: number, data: string, [color: colour]) | constructor / setup | covariance as signed-area rectangles about the mean cross |
bayes(id: new id, center: (x, y), heads: number, tails: number, [width: number], [height: number]) | constructor / setup | Bayesian updating: prior + likelihood -> posterior for a coin’s bias |
distribution(id: new id, center: (x, y), kind: string, a: number, [b: number], [color: colour]) | constructor / setup | a named distribution: uniform / exponential / maxwell / binomial / poisson |
confidence(id: new id, center: (x, y), mean: number, sd: number, n: number, [level: number], [width: number]) | constructor / setup | a confidence interval for a mean: estimate + z*sd/sqrt(n) error bar |
montecarlo(id: new id, center: (x, y), points: number, [seed: number], [size: number]) | constructor / setup | estimate pi by darts: random points in a square vs the circle |
randomwalk(id: new id, center: (x, y), steps: number, [seed: number], [scale: number]) | constructor / setup | a 2D random walk path from the centre |
bellcurve(id: new id, center: (x, y), mu: number, sigma: number, [unit: number], [color: colour]) | constructor / setup | the normal/Gaussian bell curve with the 68-95-99.7 rule shaded |
gaussian(id: new id, center: (x, y), mu: number, sigma: number, [unit: number], [color: colour]) | constructor / setup | the normal/Gaussian bell curve with the 68-95-99.7 rule shaded |
lln(id: new id, center: (x, y), trials: number, [seed: number], [width: number], [height: number]) | constructor / setup | Law of Large Numbers: a running proportion settling onto the truth |
clt(id: new id, center: (x, y), samplesize: number, trials: number, [seed: number], [width: number], [height: number], [color: colour]) | constructor / setup | Central Limit Theorem: sample means of dice pile into a bell |
Physics
| API | Kind | What it does |
|---|---|---|
freekick(id: new id, center: (x, y), speed: number, angle: number, spin: number, [scale: number], [steps: number]) | constructor / setup | an RK4 free-kick trajectory under drag + the Magnus force (sidespin curves it); draw {id}, run(id) flies the ball |
pendulum(id: new id, [center: (x, y)], [length: number], [angle0: number], [unit: number], [damping: number]) | constructor / setup | a swinging pendulum (pre-simulated); animate with swing(id) |
spring(id: new id, [center: (x, y)], [stiffness: number], [x0: number], [unit: number], [damping: number]) | constructor / setup | a mass on a spring (pre-simulated); animate with run(id) |
doublependulum(id: new id, [center: (x, y)], [angle1: number], [angle2: number], [unit: number]) | constructor / setup | the chaotic double pendulum (pre-simulated); animate with run(id) |
springpendulum(id: new id, [center: (x, y)], [angle0: number], [stretch0: number], [unit: number], [damping: number]) | constructor / setup | an elastic pendulum (swings + bounces); animate with run(id) |
kapitza(id: new id, [center: (x, y)], [angle0: number], [vibeamp: number], [unit: number]) | constructor / setup | a Kapitza pendulum — fast vibration stabilises the inverted position |
cartpendulum(id: new id, [center: (x, y)], [angle0: number], [unit: number]) | constructor / setup | a pendulum on a spring-mounted cart (control-theory system) |
comparependulum(id: new id, [center: (x, y)], [angle0: number], [unit: number]) | constructor / setup | two chaotic pendulums a hair apart — sensitive dependence |
verticalspring(id: new id, [center: (x, y)], [stretch0: number], [unit: number], [damping: number]) | constructor / setup | a mass bobbing on a vertical spring under gravity |
springincline(id: new id, [center: (x, y)], [angle: number], [unit: number], [damping: number]) | constructor / setup | a mass on a spring on an inclined plane |
bungee(id: new id, [center: (x, y)], [unit: number], [damping: number]) | constructor / setup | a bungee jump — free-fall then a one-sided elastic cord |
resonance(id: new id, [center: (x, y)], [drivefreq: number], [unit: number], [damping: number]) | constructor / setup | a driven spring — amplitude peaks near the natural frequency |
doublespring(id: new id, [center: (x, y)], [unit: number]) | constructor / setup | two masses coupled by springs — beating / normal modes |
seriesparallel(id: new id, [center: (x, y)], [unit: number]) | constructor / setup | springs in series vs parallel — soft/slow vs stiff/fast |
carsuspension(id: new id, [center: (x, y)], [unit: number]) | constructor / setup | a quarter-car riding a scrolling road (bump/washboard/pothole) |
piston(id: new id, [center: (x, y)], [rpm: number], [unit: number]) | constructor / setup | an engine piston — a spinning crank drives a slider in a cylinder |
molecule(id: new id, [center: (x, y)], [atoms: number], [unit: number]) | constructor / setup | N atoms bonded by springs, vibrating about their shape |
robotarm(id: new id, [center: (x, y)], [mode: number], [unit: number]) | constructor / setup | a two-link robot arm tracking a target by inverse kinematics (mode 1=circle, 2=figure-8, 0=fixed) |
pulley(id: new id, [center: (x, y)], [m1: number], [m2: number], [unit: number]) | constructor / setup | a vertical Atwood machine — two masses over one pulley |
pulleyscale(id: new id, [center: (x, y)], [m1: number], [m2: number], [unit: number]) | constructor / setup | an Atwood machine with an in-line spring scale reading the rope tension |
blocktackle(id: new id, [center: (x, y)], [load: number], [effort: number], [strands: number], [unit: number]) | constructor / setup | a compound pulley (block & tackle): N strands give a mechanical advantage of N |
compoundpulley(id: new id, [center: (x, y)], [mA: number], [mB: number], [mC: number], [unit: number]) | constructor / setup | a compound pulley: a fixed pulley + a movable pulley with three masses A, B, C (static when mA = mB+mC) |
ramp(id: new id, [center: (x, y)], [angle: number], [mass: number], [applied: number], [unit: number]) | constructor / setup | a block sliding on an inclined plane with friction |
dropmass(id: new id, [center: (x, y)], [dropheight: number], [unit: number]) | constructor / setup | a mass dropped onto a spring-block — inelastic collision, energy lost |
inclinepulley(id: new id, [center: (x, y)], [angle: number], [m1: number], [m2: number], [unit: number]) | constructor / setup | a block on an incline tied over a top pulley to a hanging mass (incline-Atwood) |
doubleincline(id: new id, [center: (x, y)], [angle1: number], [angle2: number], [m1: number], [m2: number], [unit: number]) | constructor / setup | two blocks on a wedge’s two slopes, tied over an apex pulley (right slope rough) |
inclinebumper(id: new id, [center: (x, y)], [angle: number], [mass: number], [stiffness: number], [unit: number]) | constructor / setup | a block slides down an incline into a spring bumper at the base, then bounces back |
springchain(id: new id, [center: (x, y)], [angle: number], [unit: number]) | constructor / setup | three blocks joined by two springs on an incline — coupled oscillators / normal modes |
looptrack(id: new id, [center: (x, y)], [radius: number], [height: number], [unit: number]) | constructor / setup | a ball rolls down a ramp and around a vertical loop-the-loop (curved track) |
collideblocks(id: new id, [center: (x, y)], [m1: number], [m2: number], [restitution: number], [unit: number]) | constructor / setup | two blocks collide on a frictionless track with elastic walls (restitution e) |
bulletblock(id: new id, [center: (x, y)], [bulletmass: number], [speed: number], [blockmass: number], [unit: number]) | constructor / setup | a bullet fired into a block embeds (perfectly inelastic — most kinetic energy lost) |
newtonscradle(id: new id, [center: (x, y)], [balls: number], [pulled: number]) | constructor / setup | Newton’s cradle — pull N balls, N swing out (event-driven elastic collisions) |
stringwave(id: new id, [center: (x, y)], [width: number], [amp: number], [pluck: number]) | constructor / setup | a wave on a plucked string (N masses on springs, fixed ends) — the wave equation |
raft(id: new id, [center: (x, y)], [personmass: number], [raftmass: number], [unit: number]) | constructor / setup | a person walking on a floating raft — centre of mass stays fixed |
brachistochrone(id: new id, [center: (x, y)], [unit: number]) | constructor / setup | four beads race down different curves under gravity — the cycloid wins |
run(id: id or tag, [dur_or_gens: number], [dur: number]) | animation verb | replay a sim’s motion over [dur] s (or a grid’s frames: run(grid, [gens], [dur])) |
swing(id: id or tag, [dur: number]) | animation verb | replay a pendulum’s motion over [dur] seconds (alias of run) |
forces(id: id or tag, [dur: number]) | animation verb | reveal a sim’s free-body force diagram (ramp: gravity, normal, friction, a) |
phase(id: id or tag, center: (x, y), [size: number]) | constructor / setup | a sim’s phase portrait (e.g. θ vs ω); animates with swing |
well(id: id or tag, center: (x, y), [size: number]) | constructor / setup | a sim’s potential-energy well with the body as a rolling ball |
timegraph(id: id or tag, center: (x, y), [size: number]) | constructor / setup | a sim’s phase variables as curves over time (with a sweep line) |
energygraph(id: id or tag, center: (x, y), [size: number]) | constructor / setup | KE/PE/total energy as curves over time (with a sweep line) |
Machine learning
| API | Kind | What it does |
|---|---|---|
network(id: new id, center: (x, y), layers: string, activations: string, [width: number], [height: number], [seed: number]) | constructor / setup | a deterministic feed-forward neural network with responsive layered layout and stable node/edge tags |
activation(id: new id, center: (x, y), function: id or tag, [width: number], [height: number]) | constructor / setup | a truthful plot of linear, ReLU, sigmoid, or tanh |
forward(network: id or tag, input: string, [dur: number], [ease: easing]) | state-changing verb | compute and progressively reveal one feed-forward pass with real activations and output values |
loss(network: id or tag, target: string, [kind: id or tag], [dur: number], [ease: easing]) | state-changing verb | compare the latest network output with a target using cross-entropy or mean-squared error |
backward(network: id or tag, [dur: number], [ease: easing]) | state-changing verb | compute exact reverse-mode gradients and reveal them from output to input |
update(network: id or tag, [learning_rate: number], [dur: number], [ease: easing]) | state-changing verb | apply one gradient-descent parameter update and recompute the prediction and loss |
checkpoint(id: new id, network: id or tag) | state-changing verb | save one network’s exact parameters, prediction, target, and loss for a later rollback |
restore(network: id or tag, checkpoint: id or tag, [dur: number], [ease: easing]) | state-changing verb | animate and exactly restore a checkpointed network state; this is rollback, not dataset-level unlearning |
tensor(id: new id, center: (x, y), values: string, [cell: number], [color: colour]) | constructor / setup | a finite numeric grid; semicolons separate rows and vertical bars separate channels |
kernel(id: new id, center: (x, y), values: string, [cell: number], [color: colour]) | constructor / setup | a finite convolution kernel with one grid per input channel |
convolve(id: new id, input: id or tag, kernel: id or tag, center: (x, y), [stride: number], [padding: number], [bias: number], [activation: id or tag], [cell: number]) | constructor / setup | compute a feature map from an input tensor and kernel with validated stride, padding, bias, and activation |
pool(id: new id, input: id or tag, center: (x, y), kind: id or tag, [window: number], [stride: number], [padding: number], [cell: number]) | constructor / setup | compute deterministic max or average pooling independently across tensor channels |
scan(output: id or tag, [dur: number], [ease: easing]) | state-changing verb | coordinate the receptive field, operator, arithmetic, and destination cell of a convolution or pool |
tokenize(id: new id, center: (x, y), text: string, [mode: id or tag], [width: number]) | constructor / setup | split text into a small deterministic token sequence using authored, word, or character boundaries |
embedding(id: new id, tokens: id or tag, center: (x, y), vectors: string, [position: id or tag], [width: number], [height: number]) | constructor / setup | turn a token sequence into explicit or seeded educational vectors and add sinusoidal or no positional encoding |
transformer(id: new id, embedding: id or tag, center: (x, y), config: string, [width: number], [height: number]) | constructor / setup | compute and draw a complete deterministic transformer block from an ML5 embedding and a compact heads/mask/MLP/norm/dropout specification |
encode(transformer: id or tag, [dur: number], [ease: easing]) | state-changing verb | reveal a transformer block through heads, masking, concatenation, residuals, normalization, MLP, dropout, and output |
logits(id: new id, transformer: id or tag, token: number, center: (x, y), labels: string, [temperature: number], [width: number], [height: number], [seed: number]) | constructor / setup | project one transformer hidden row into an authored educational vocabulary and compute the full temperature-scaled softmax |
sample(logits: id or tag, strategy: string, [dur: number], [ease: easing]) | state-changing verb | filter, renormalize, and deterministically select from a logits view using greedy, categorical, top-k, or top-p decoding |
attention(id: new id, center: (x, y), tokens: string, embeddings: string, [width: number], [height: number], [seed: number]) | constructor / setup | compute and draw one deterministic scaled dot-product self-attention head from explicit token embeddings |
attend(attention: id or tag, token: number, [dur: number], [ease: easing]) | state-changing verb | focus one 1-based query token and reveal its Q/K matches, softmax row, weighted V mix, and residual lane |
topk(id: new id, attention: id or tag, token: number, center: (x, y), labels: string, [k: number], [width: number], [height: number], [seed: number]) | constructor / setup | project one attention residual into an authored candidate vocabulary and draw the exact top-k softmax probabilities |
Optics
| API | Kind | What it does |
|---|---|---|
refract(id: new id, [center: (x, y)], [n1: number], [n2: number], [angle: number]) | constructor / setup | a light ray bending across a boundary (Snell’s law); run(id) sweeps the angle, showing total internal reflection |
lens(id: new id, [center: (x, y)], [focal: number], [aperture: number]) | constructor / setup | a converging lens focusing parallel rays to a focal point; run(id) sweeps the focal length so the focus slides |
prism(id: new id, [center: (x, y)], [glass: string]) | constructor / setup | white light splitting into a spectrum through a prism (real Sellmeier dispersion); run(id) sweeps the incidence angle |
achromat(id: new id, [center: (x, y)], [aperture: number]) | constructor / setup | chromatic aberration (red & blue focus apart) and its fix; run(id) sweeps in the achromatic doublet, merging the foci |
lenssystem(id: new id, [center: (x, y)], [preset: string], [object: number]) | constructor / setup | a real multi-element lens ray-traced through its spherical/aspheric surfaces (named preset OR a custom prescription string); draw(id.rays) sketches rays, run(id) sweeps a focus sensor + spot read-out. Optional finite object distance (px). |
rayfan(id: new id, [center: (x, y)], [preset: string]) | constructor / setup | the ray-fan aberration plot of a lens preset (transverse ray error vs pupil height); draw(id.curve) sketches the spherical-aberration S-curve |
spotdiagram(id: new id, [center: (x, y)], [preset: string]) | constructor / setup | the spot diagram of a lens preset at best focus (rays land in a blur disc — spherical aberration), with an RMS read-out and the ideal point focus |
fieldspot(id: new id, [center: (x, y)], [preset: string], [field: number]) | constructor / setup | the off-axis spot diagram (3-D pupil trace at a field angle) — a coma comet / astigmatic blur off-axis, with an Airy-disk diffraction-limit overlay |
Creator
| API | Kind | What it does |
|---|---|---|
creator(id: new id, spec: string) | constructor / setup | a reusable v2 creator profile: handle/platforms plus name, tagline, logo, palette, footer, CTA and safe-area keys |
socials(id: id or tag, [at: (x, y)]) | constructor / setup | draw the creator profile’s responsive footer; social mode uses normalized native YouTube/X/Instagram/TikTok/Facebook/LinkedIn/GitHub/web/email icons |
quiz(id: new id, question: string, [style: string]) | constructor / setup | start a responsive quiz: studio default; optional skin/reveal/layout/density/labels/timer/pace/seconds/motion/safe/accent controls |
option(id: id or tag, text: string, [correct: id or tag]) | constructor / setup | add one of up to six fitted answer cards with stable A–F semantic tags; a trailing correct marks the right one |
timing(…) | constructor / setup | generic named-phase timing controller (optional position) or quiz-specific pace/phase configuration; use generic controllers with timed/during |
timerstyle(…) | constructor / setup | style a quiz or generic timing controller: optional generic position plus native look, number, direction, size, colours, label and finish |
safezone(id: new id, [inset: number]) | constructor / setup | a responsive safe-area guide; optional numeric inset or shorts/reels/tiktok/clean profile |
countdown(id: new id, [at: (x, y)], [secs: number], [style: string]) | constructor / setup | a standalone Timing v2 widget sharing the quiz timer looks and style controls; play with run(id, secs) |
figure(target: id or tag, [center: (x, y)], [size: (x, y)]) | constructor / setup | fit a complete tagged group into the responsive media region, including text/images/equations and live-dependency checks |
explain(quiz: id or tag, text: string, [source: string]) | constructor / setup | attach optional author-supplied answer context and source to a quiz reveal |
endcard(profile: id or tag, [spec: string]) | constructor / setup | build a hidden responsive creator end card; reveal profile.endcard at the final beat |
Fixed language vocabularies
Colours
fg, void, black, ink, cyan, magenta, lime, gold, red, orange, blue, teal, violet, coral, indigo, mint, dim, panel, rainbow
Easings
linear, smooth, inout, in, out, overshoot, back, bounce, elastic, spring
Named plot functions
sin, cos, tan, asin, arcsin, acos, arccos, atan, arctan, parabola, sq, square, cubic, cube, line, id, identity, abs, exp, sqrt, log, ln, recip, inv, gauss, bell, sinc, sigmoid, logistic, relu, step, heaviside
Canvas presets
16:9, 1080p, 4k, square, portrait, 4:5, 4:3, widescreen, landscape, 720p, fullhd, hd, 2160p, 1:1, 9:16, vertical, story, reel, feed
Templates and aliases
black, mono, plain, terminal, paper, blueprint, shorts, monochrome, blackwhite, black-white, bw, blank, clean, neon, shell, print, light, blue, short, punch
Language keywords
let, for, if, else, def, in, sum, prod, min, max
Reserved variables
w, h, cx, cy, pi, e, tau, inf, infinity
Systems routing and ports
Routing: orthogonal
Ports: auto, left, right, top, bottom
Speech services and curated Cartesia voices
Services: gtts, google, cartesia, elevenlabs, 11labs
Cartesia voices: katie, skylar, jameson, gemma, archie
Machine learning — models made visible
Manic ML begins with a simple promise: the learner should see the values the model actually computed, one meaningful flow at a time. A dense network is not useful merely because every node and edge is present. The current layer should be legible; the surrounding architecture should provide context without becoming a mesh of noise.
ML1 covers deterministic feed-forward networks and activation functions. ML2 adds supervised loss, exact reverse-mode gradients, and explicit parameter updates on the same persistent figure. ML3 adds tensors, convolution, pooling, and a shared operator scan. ML4 adds one exact, focused self-attention head and a deterministic top-k output view. ML5 makes the earlier text-to-model-input journey visible through honest token boundaries, embeddings, and position. ML6 carries those vectors through a complete multi-head transformer block. ML7 keeps the final language-model projection separate, then makes temperature and next-token sampling visible without pretending to run a pretrained model.
The visual language is still ordinary Manic
The ML nouns compute the diagram. Core Manic supplies the cinematography. The shipped examples use a small visual grammar consistently:
- cyan carries input or data;
- lime carries positive or retained signal;
- magenta carries negative contribution or reverse gradient;
- gold marks the active operation, residual result, or selected prediction;
- dim structure preserves context without letting every edge compete.
Attention outputs and Transformer stages contain real vector summaries or
signed mini bars, so a labelled box is never the only explanation. forward
leaves a quiet contribution-weighted trace, backward uses external gradient
badges, and encode flows through the main path plus both residual bypasses.
These are defaults of the kit; creators do not need to animate internal tags by
hand.
Camera motion is optional. When it helps, keep it small, focus one calculation, and return to the complete result:
step("compute") {
par {
forward(net, "0.15 0.92 0.38", 4.2, smooth);
seq {
par { cam((w*0.24,h*0.47),0.55,smooth); zoom(1.06,0.55,smooth); }
wait(0.55);
cam((cx,h*0.47),0.8,smooth);
wait(0.55);
cam((w*0.76,h*0.47),0.8,smooth);
par { cam((cx,cy),0.55,smooth); zoom(1,0.55,smooth); }
}
}
}
Avoid decorative particles around a network. Use a bounded packet or flow
only when it represents a token, activation, attention contribution, gradient,
or sampled choice. The settled frame must remain understandable with motion
paused and with the default black template.
Start with the story you need
You do not need to learn the whole ML kit. Choose one row, create the named
object, and use normal Manic step, show, say, and pulse calls around it.
| Story | Start with | Animate with |
|---|---|---|
| Scalar → vector → matrix → tensor | tensor | core Manic verbs |
| One activation function | activation | core Manic verbs |
| Prediction through a dense model | network | forward |
| Why a prediction is wrong | network + forward | loss |
| How gradients assign credit | prediction + loss | backward |
| One visible learning correction | completed backward pass | update |
| Image → feature map | tensor + kernel + convolve | scan |
| Smaller feature map | tensor + pool | scan |
| Text → tokens → positioned vectors | tokenize + embedding | core Manic verbs |
| How one token finds context | attention | attend |
| Context → candidate probabilities | attention + topk | core Manic verbs |
| Complete transformer block | transformer | encode |
| Hidden state → logits → next token | transformer + logits | sample |
The smallest useful pattern is:
activation(view, (cx,cy), relu, 510, 260);
untraced(view.axes);
untraced(view.curve);
step("meet-the-rule") {
draw(view.axes, 0.7);
draw(view.curve, 1.2);
}
ML words compute or construct the truthful figure. Core Manic remains the story language. This division keeps a tensor-only or activation-only lesson small instead of forcing every creator into a complete neural network.
The complete ML1–ML7 vocabulary
| Kind | Words | What the creator supplies |
|---|---|---|
| Figures | tensor, kernel, activation, network, tokenize, embedding, attention, transformer | values, shape, text, boundaries, activation names, embeddings, compact block options, or a deterministic seed |
| Derived views | convolve, pool, topk, logits | a source figure plus operator, candidate, token, temperature, or projection choices |
| Computation | forward, loss, backward, update, scan, attend, encode, sample | input, target, learning rate, selected token/block, decoding strategy, duration, and easing |
backward is intentionally not standalone: it requires a forward prediction
and loss. scan similarly requires a convolve or pool result. Errors name
the missing prerequisite instead of drawing a plausible but false animation.
Learn tensors without a network
Use a 1×1 grid for one displayed scalar, one row for a vector, rows for a
matrix, and | to stack channels. The accompanying title can name the familiar
rank while the same tensor noun supplies stable cells and values:
tensor(scalar, (180,360), "7");
tensor(vector, (460,360), "7 2 -1 4");
tensor(matrix, (760,360), "7 2 -1; 4 0 3; 1 5 6");
tensor(volume, (1060,360),
"7 2 -1; 4 0 3; 1 5 6 | 2 4 8; 1 3 9; 0 5 6");
Every part is selectable: volume.channel0, volume.row1, volume.col2,
volume.cells, volume.values, or one cell such as volume.c1.r0c2.
Explain one activation without a network
activation(view, (cx,cy), relu, 620, 320);
Standalone plots support linear, relu, sigmoid, and tanh. Use
view.axes, view.curve, and the ordinary line, point, equation, draw, and
pulse tools to test inputs or explain a region. softmax belongs inside a
network because it transforms a complete vector rather than one scalar.
A complete forward pass
network(net, (cx, cy), "3 6 4 3", "relu tanh softmax", 820, 350, 21);
forward(net, "0.15 0.92 0.38", 4.2, smooth);
The two quoted lists serve different purposes:
"3 6 4 3"defines input, hidden, hidden, and output layer sizes."relu tanh softmax"defines what happens after each of the three affine transitions.21is the deterministic seed. The same file produces the same weights, activations, prediction, and frames every time.- The input supplied to
forwardmust contain exactly three finite values.
Manic uses Xavier-uniform weights for the seeded educational model. It computes each affine layer, applies the named activation, and uses a numerically stable softmax. The output bars and percentages are derived from that result.
One complete learning step
forward(net, "0.15 0.92 0.38", 3.2, smooth);
loss(net, "1 0 0", crossentropy, 1.5, smooth);
backward(net, 3.2, smooth);
checkpoint(beforeUpdate, net);
update(net, 0.18, 2.3, smooth);
These four beats deliberately remain visible:
forwardstores the real activations for the authored input.losscompares the output with the target.crossentropyrequires a softmax output and a non-negative target distribution that sums to one.msesupports other output activations and arbitrary finite targets.backwardcalculates exact reverse-mode gradients for every visible and hidden parameter. The pulse travels output → input along the existing edges.updateappliesparameter -= learning_rate * gradient, recomputes the same input, and replaces the output bars and loss with their new computed values.
The optional loss kind defaults to cross-entropy for a softmax output and MSE
otherwise. The default learning rate is 0.15. A learning rate is not a visual
speed: changing it changes the mathematics, and a large value can truthfully
increase the loss.
Undo one authored update exactly
Place a checkpoint after the prediction has been compared with its target and before the parameter update. It takes no timeline time:
backward(net, 3.2, smooth);
checkpoint(beforeUpdate, net);
update(net, 0.18, 2.3, smooth);
restore(net, beforeUpdate, 2.3, smooth);
checkpoint saves every weight and bias plus the current layer values,
prediction, target, and loss. restore reverses the visible flow and returns
all of them to that exact saved state. It also clears active gradients; call
backward again before attempting another update.
This is precise checkpoint rollback. It is useful for explaining what one gradient step changed, comparing before and after, or showing an undo action. It is not general machine unlearning: restoring a saved state does not prove that a data point’s influence was removed from an otherwise trained model.
From pixels to feature maps
Rows use ;, values use spaces or commas, and channels use | inside one
quoted grid. This keeps small textbook tensors readable:
tensor(image, (250, 340), "0 0 1; 0 1 1; 0 0 1", 44, cyan);
kernel(edge, (540, 340), "-1 0 1; -2 0 2; -1 0 1", 44, magenta);
convolve(feature, image, edge, (820, 340), 1, 1, 0, relu, 44);
scan(feature, 4.0, smooth);
pool(compact, feature, (1080, 340), max, 2, 2, 0, 44);
scan(compact, 2.8, smooth);
convolve computes one output feature map. Its optional values are stride,
zero padding, bias, cellwise activation, and cell size. A multi-channel input
uses one kernel grid per input channel and sums every channel into each output
cell:
tensor(rgb, (300, 340), "1 2; 3 4 | 10 20; 30 40 | 2 0; 1 3");
kernel(k, (600, 340), "1 | 0.5 | -1");
convolve(feature, rgb, k, (900, 340));
For multiple feature detectors, author multiple kernels and outputs. That keeps each receptive field explainable instead of hiding a filter bank behind one visually dense call.
pool supports max and average, operating independently on each channel.
The default window is 2 and the default stride equals the window. Padding does
not fabricate candidate values: padded positions are excluded. Max-pool ties
select the first valid cell in row-major order, which makes selection stable
across renders and direct seeking.
One scanner for convolution and pooling
scan(output, duration, easing) coordinates four identities that should never
drift apart:
- the receptive-field frame on the source;
- the kernel/operator focus;
- the truthful arithmetic summary;
- the destination frame and exact revealed value.
When a pooled tensor consumes a convolution result, starting the pooling scan
automatically quiets the completed convolution arithmetic strip. The figures
remain in place, so the learner sees continuity without two competing status
lines. Use normal step, caption, show, and pulse calls for the narrative;
let scan own the synchronized numerical choreography.
Show how words gain position
Start with the sentence, choose honest boundaries, then turn those identities into vectors:
tokenize(words, (cx, 150), "the cat chased the cat", word, w*0.70);
embedding(context, words, (cx, 470), "seeded 6 37", sinusoidal,
w*0.90, h*0.46);
The optional token mode is:
word— Unicode letters/numbers form words; punctuation stays separate;character— each character is a token and whitespace remains visible;authored—|marks every exact boundary for a hand-authored subword explanation.
Authored boundaries are not called BPE because Manic has not applied a merge
table. embedding accepts either one explicit numeric row per token (rows
separated with ;) or "seeded DIM [SEED]". Seeded values are reproducible
educational lookup vectors, not pretrained weights. Repeated copies of the
same token reuse the same base vector; adding exact sinusoidal position makes
their final model inputs different. Use none instead of sinusoidal when the
lesson should stop at the lookup table.
Reveal context.vectors, then context.positions, then context.combined.
For comparison, pulse stable rows such as context.row1; use .dimN to focus
one feature across the table. Manic caps the story at 12 tokens and eight
dimensions so the values remain teachable on a phone.
Explain one transformer attention head
Give attention a short token list and one embedding row per token. Then focus
one token with its 1-based position:
attention(head, (cx, 360),
"Art | ificial | intelligence | transforms | business",
"1 0.2 -0.4 0.7; 0.8 0.1 -0.3 0.6; -0.2 1 0.5 0.3; 0.1 0.6 0.9 -0.2; 0.7 -0.1 0.4 1",
980, 420, 23);
attend(head, 3, 5.2, smooth);
This computes one seeded Q/K/V projection, the scaled score matrix
QK^T / sqrt(d), a stable row-wise softmax, and each exact weighted value mix.
attend(head, 3, ...) highlights the query for intelligence; it does not
rebuild or replace the surrounding tokens.
Add a small output ranking only when the story needs it:
topk(next, head, 3, (1540, 400),
"business | work | world | industry | future | people",
4, 420, 260, 29);
topk adds the selected embedding to its attention output, applies a seeded
educational output projection, and shows probabilities from the full softmax.
Those percentages are mathematically exact for the authored figure, but they
are not predictions from a pretrained language model. Keep the candidate list
small enough to read; the visual shows at most eight selected results.
Walk through a complete transformer block
Pass the ML5 embedding directly into transformer; do not copy its rows:
transformer(block, context, (cx, 500),
"heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41",
w*0.92, h*0.62);
encode(block, 6.2, smooth);
The one configuration sentence controls the choices that change the actual calculation:
heads=1..4;d_modelmust divide exactly across them;mask=none|causal; causal future cells receive zero probability;mlp=WIDTHup to 32 andactivation=gelu|relu|silu|tanh;norm=pre|post, which changes where both layer normalizations happen;dropout=0..less-than-1,mode=inference|training, and a reproducibleseed.
Each head computes scaled Q/K scores, applies its mask, normalizes with stable
softmax, and mixes V. Manic concatenates the heads, applies the output
projection, follows the first residual/norm stage, expands and contracts the
MLP, then follows the second residual/norm stage. Training dropout is a real
seeded boolean mask with inverted scaling; inference disables it completely.
encode reveals this existing computation and remains safe under direct seek.
Turn a hidden state into one next token
The transformer’s MLP produces another hidden representation. It does not
directly produce vocabulary probabilities. logits makes the separate
language-model head explicit:
logits(next, block, 5, (cx, 520),
"reason | predict | learn | adapt | explain | .",
0.8, 760, 440, 73);
sample(next, "top-p 0.90 seed=17", 3.8, smooth);
The third argument is a 1-based transformer token. Candidate labels are
|-separated and intentionally authored; Manic supports 2–12 in one readable
view. The optional temperature defaults to 1, followed by width, height, and
projection seed.
logits computes W_lm h + b from that final hidden row. It then divides every
logit by the positive temperature and applies one numerically stable softmax to
the complete candidate list. Reuse the same projection seed for a fair
temperature comparison: logits remain identical while every probability is
recomputed. Lower temperature sharpens the distribution; higher temperature
spreads it.
sample keeps four common choices behind one word:
| Strategy string | Exact behavior |
|---|---|
"greedy" | selects the highest probability; the decoding distribution is one-hot |
"categorical seed=17" | samples from the complete temperature-scaled distribution |
"top-k 3 seed=17" | keeps exactly the three highest candidates, zeros the rest, then renormalizes |
"top-p 0.90 seed=17" | keeps the smallest descending prefix reaching 90% mass, zeros the rest, then renormalizes |
An excluded candidate has exact probability zero and cannot be sampled. The same seed and same distribution choose the same result. All displayed values come from the deterministic educational projection declared in the scene; they are not predictions from a pretrained language model.
Introduce an activation first
activation(reluView, (cx, cy), relu, 510, 260);
untraced(reluView.axes);
untraced(reluView.curve);
par {
draw(reluView.axes, 0.7);
draw(reluView.curve, 1.2);
}
activation supports linear, relu, sigmoid, and tanh. A standalone
softmax curve would be misleading because softmax depends on all entries in a
vector; show it as the output activation of a network instead.
Design details that make the result readable
- Inactive edges remain quiet. During
forward, contribution magnitude drives emphasis and one pulse travels in the direction of computation. - Weight sign and magnitude affect edge styling, but labels, brightness, and width keep the structure understandable under the monochrome template.
- Large numerical layers show their first and last units around an ellipsis. Computation still uses every unit; only the drawing uses level of detail.
- Input, hidden, and output nodes retain stable ids throughout the story. Manic updates values instead of clearing and rebuilding the network.
- Output bars grow from zero to the computed value. A softmax output is labelled as a percentage and the status strip names the selected class.
lossplaces the target beside each output without replacing the prediction. Error magnitude focuses attention on the outputs that disagree.backwardtemporarily recolours connections by gradient sign and weights their emphasis by gradient magnitude. The settled network remains the same object, ready for the update.updateshows the gradient direction first, then restores edge styling from the new weights and recomputes every node. Its final status preserves the old and new loss so the claimed learning outcome is inspectable.restoresends one readable reverse pulse through the same graph, settles edge styling from the saved weights, and restores the saved output bars and loss without rebuilding the network.
Compose it like any other Manic scene
Every part is an ordinary entity carrying useful tags:
| Tag | Selects |
|---|---|
net | the complete network figure |
net.nodes, net.edges, net.values | one visual role |
net.layer0, net.layer1, … | one layer |
net.input, net.hidden, net.output | semantic layer groups |
net.probabilities | output bars and readouts |
net.loss | supervised target readouts |
image.cells, image.values, image.labels | tensor visual roles |
image.channel0, image.row0, image.col0 | tensor slices |
image.c0.r0c0 and .value | one cell and its numeric text |
feature.scan | receptive-field/operator/destination overlays |
words.source, .tokens, .indices, .tokenN | tokenization stages |
context.vectors, .positions, .combined | embedding addition stages |
context.rowN, .dimN, .operators | one token, feature, or operator |
block.heads, .headN, .mask, .matrix | multi-head attention and masks |
block.concat, .projection, .residual1, .norm1 | first half of the block |
block.mlp, .activation, .dropout, .residual2, .norm2 | second half |
block.input, .output, .tokenN, .rowN | persistent token lanes |
head.tokens, .q, .k, .v, .matrix | attention stages |
head.connections, .outputs, .residual | attention flow and residual lanes |
next.labels, .bars, .probabilities | top-k output roles |
That means normal verbs still apply:
hidden(net);
step("meet-the-model") {
show(net, 0.6);
}
step("compute") {
forward(net, "0.15 0.92 0.38", 4.2, smooth);
}
step("decision") {
pulse(net.output, 0.7);
}
Use named steps for question, intuition, computation, and takeaway. Let
forward own the dense numerical choreography; use captions and equations to
explain why the active operation matters.
For a learning story, keep the causal order clear:
step("predict") { forward(net, "0.15 0.92 0.38", 3.2); }
step("compare") { loss(net, "1 0 0", crossentropy, 1.5); }
step("credit") { backward(net, 3.2); }
checkpoint(beforeUpdate, net);
step("learn") { update(net, 0.18, 2.3); }
step("unlearn") { restore(net, beforeUpdate, 2.3); }
Calling forward with a new input starts a fresh learning beat and clears the
old target/gradient state. After update, another backward may compute fresh
gradients for the updated parameters and the same retained target. Every
update still requires a preceding backward; there is no invisible optimizer
loop. Name a restore step “rollback” or explain its exact boundary if you use
the playful label “unlearn”.
Current boundary
The ML kit is intentionally for small educational models. It does not load arbitrary PyTorch or TensorFlow programs, run hidden training loops, expose an optimizer catalogue, train large models, or require a GPU. Explicit authored weights, automatic filter banks, convolutional back-propagation, stacks of multiple transformer blocks, model imports, and packaged pretrained tokenizers remain planned work. ML4 deliberately computes one educational attention head, not a hidden pretrained language model. ML5 offers deterministic word/character splitting and exact authored boundaries; it does not claim a heuristic is BPE. Token sequences accept at most 12 tokens and eight embedding values each. Attention accepts 2–8 tokens with at most eight embedding values each; its candidate vocabulary is capped at 16. ML6 accepts 1–4 heads, requires exact division of the model dimension, caps the MLP at 32 values, and computes one educational block rather than an imported language model. ML7 accepts 2–12 authored candidates and a positive finite temperature; its LM projection and sampling seed are educational and explicit. Tensor axes are capped at 16 cells and a tensor at 2,048 values so unreadable stories fail early instead of silently becoming visual noise.
Review the shipped stories
The foundation story proves that tensor plus ordinary Manic is enough to
explain rank and dimensional growth:
// manic-ml-scalar-to-tensor.manic — ML foundation story
// One value gains an axis, then rows, then stacked channels. The ML-specific
// surface is only tensor(...); ordinary Manic verbs own the explanation.
title("Manic ML — From Scalar to Tensor");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Build_the_dimensions safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
let cell = 38*u;
if h > 1.45*w {
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · DATA FOUNDATIONS");
text(headline, (cx, h*0.105), "One value gains dimensions");
text(caption, (cx, h*0.84), "Begin with one measured value.");
tensor(scalar, (cx, h*0.20), "7", cell, gold);
tensor(vector, (cx, h*0.35), "7 2 -1 4", cell, cyan);
tensor(matrix, (cx, h*0.53), "7 2 -1; 4 0 3; 1 5 6", cell, magenta);
tensor(volume, (cx, h*0.72), "7 2 -1; 4 0 3; 1 5 6 | 2 4 8; 1 3 9; 0 5 6", cell, lime);
text(scalarTitle, (cx, h*0.155), "SCALAR · RANK 0");
text(vectorTitle, (cx, h*0.295), "VECTOR · RANK 1");
text(matrixTitle, (cx, h*0.445), "MATRIX · RANK 2");
text(volumeTitle, (cx, h*0.625), "TENSOR · RANK 3");
arrow(grow1, (cx, h*0.245), (cx, h*0.285));
arrow(grow2, (cx, h*0.395), (cx, h*0.435));
arrow(grow3, (cx, h*0.585), (cx, h*0.615));
}
else if w > 1.25*h {
watermark(manicMark, (w*0.17, h*0.09), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · DATA FOUNDATIONS");
text(headline, (cx, h*0.14), "One value gains dimensions");
text(caption, (cx, h*0.82), "Begin with one measured value.");
tensor(scalar, (w*0.11, h*0.46), "7", cell, gold);
tensor(vector, (w*0.34, h*0.46), "7 2 -1 4", cell, cyan);
tensor(matrix, (w*0.61, h*0.46), "7 2 -1; 4 0 3; 1 5 6", cell, magenta);
tensor(volume, (w*0.86, h*0.46), "7 2 -1; 4 0 3; 1 5 6 | 2 4 8; 1 3 9; 0 5 6", cell, lime);
text(scalarTitle, (w*0.11, h*0.34), "SCALAR · RANK 0");
text(vectorTitle, (w*0.34, h*0.34), "VECTOR · RANK 1");
text(matrixTitle, (w*0.61, h*0.34), "MATRIX · RANK 2");
text(volumeTitle, (w*0.86, h*0.34), "TENSOR · RANK 3");
arrow(grow1, (w*0.17, h*0.46), (w*0.23, h*0.46));
arrow(grow2, (w*0.44, h*0.46), (w*0.51, h*0.46));
arrow(grow3, (w*0.70, h*0.46), (w*0.76, h*0.46));
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC ML · DATA FOUNDATIONS");
text(headline, (cx, h*0.17), "One value gains dimensions");
text(caption, (cx, h*0.84), "Begin with one measured value.");
tensor(scalar, (w*0.24, h*0.34), "7", cell, gold);
tensor(vector, (w*0.73, h*0.34), "7 2 -1 4", cell, cyan);
tensor(matrix, (w*0.24, h*0.65), "7 2 -1; 4 0 3; 1 5 6", cell, magenta);
tensor(volume, (w*0.73, h*0.65), "7 2 -1; 4 0 3; 1 5 6 | 2 4 8; 1 3 9; 0 5 6", cell, lime);
text(scalarTitle, (w*0.24, h*0.235), "SCALAR · RANK 0");
text(vectorTitle, (w*0.73, h*0.235), "VECTOR · RANK 1");
text(matrixTitle, (w*0.24, h*0.505), "MATRIX · RANK 2");
text(volumeTitle, (w*0.73, h*0.505), "TENSOR · RANK 3");
arrow(grow1, (w*0.34, h*0.34), (w*0.50, h*0.34));
arrow(grow2, (w*0.73, h*0.40), (w*0.36, h*0.56));
arrow(grow3, (w*0.35, h*0.65), (w*0.50, h*0.65));
}
size(kicker, 19*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 34*u); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 21*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
size(scalarTitle, 17*u); bold(scalarTitle); color(scalarTitle, dim); hidden(scalarTitle);
size(vectorTitle, 17*u); bold(vectorTitle); color(vectorTitle, dim); hidden(vectorTitle);
size(matrixTitle, 17*u); bold(matrixTitle); color(matrixTitle, dim); hidden(matrixTitle);
size(volumeTitle, 17*u); bold(volumeTitle); color(volumeTitle, dim); hidden(volumeTitle);
color(grow1, dim); stroke(grow1, 2.5*u); hidden(grow1);
color(grow2, dim); stroke(grow2, 2.5*u); hidden(grow2);
color(grow3, dim); stroke(grow3, 2.5*u); hidden(grow3);
hidden(scalar.cells); hidden(scalar.values); hidden(scalar.labels);
hidden(vector.cells); hidden(vector.values); hidden(vector.labels);
hidden(matrix.cells); hidden(matrix.values); hidden(matrix.labels);
hidden(volume.cells); hidden(volume.values); hidden(volume.labels);
step("scalar") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
show(scalarTitle, 0.30);
par {
show(scalar.cells, 0.55);
show(scalar.values, 0.55);
}
}
wait(0.55);
step("vector") {
par {
show(grow1, 0.35);
show(vectorTitle, 0.30);
show(vector.cells, 0.60);
show(vector.values, 0.60);
say(caption, "Repeat the value along one ordered axis: position now matters.", 0.40);
}
}
wait(0.60);
step("matrix") {
par {
show(grow2, 0.35);
show(matrixTitle, 0.30);
show(matrix.cells, 0.65);
show(matrix.values, 0.65);
say(caption, "Add rows to the columns: one axis becomes a two-dimensional matrix.", 0.40);
}
}
wait(0.65);
step("tensor") {
par {
show(grow3, 0.35);
show(volumeTitle, 0.30);
show(volume.cells, 0.70);
show(volume.values, 0.70);
say(caption, "Stack matrices as channels: the same values now carry depth and context.", 0.40);
}
}
wait(0.75);
step("one-family") {
pulse(volume.channel1, 0.75);
say(caption, "Scalar, vector, and matrix are all tensors—distinguished by their axes.", 0.45);
}
wait(1.50);
The activation story uses one real ReLU plot and core Manic probes to explain negative and positive inputs:
// manic-ml-activation-focus.manic — one ML noun, one complete lesson
// `activation` supplies the truthful ReLU curve. Core Manic supplies the
// question, equation, input probes, local emphasis, timing, and takeaway.
title("Manic ML — Why ReLU Changes a Neuron");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=See_the_rule safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · ACTIVATION");
text(headline, (cx, h*0.11), "Why does a neuron need ReLU?");
text(caption, (cx, h*0.79), "A neuron first receives an unrestricted number.");
equation(rule, (cx, h*0.60), `\operatorname{ReLU}(x)=\max(0,x)`, 35*u);
activation(reluView, (cx, h*0.38), relu, w*0.74, h*0.22);
line(negativePath, (w*0.13, h*0.466), (cx, h*0.466));
line(positivePath, (cx, h*0.466), (w*0.87, h*0.27));
line(positiveGuide, (w*0.685, h*0.466), (w*0.685, h*0.368));
circle(negativeProbe, (w*0.315, h*0.466), 8*u);
circle(positiveProbe, (w*0.685, h*0.368), 8*u);
text(negativeNote, (w*0.27, h*0.525), "x = −2 → 0");
text(positiveNote, (w*0.73, h*0.525), "x = 2 → 2");
}
else if w > 1.25*h {
watermark(manicMark, (w*0.17, h*0.09), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · ACTIVATION");
text(headline, (cx, h*0.14), "Why does a neuron need ReLU?");
text(caption, (cx, h*0.82), "A neuron first receives an unrestricted number.");
equation(rule, (w*0.79, h*0.36), `\operatorname{ReLU}(x)=\max(0,x)`, 34*u);
activation(reluView, (w*0.38, h*0.49), relu, w*0.52, h*0.48);
line(negativePath, (w*0.12, h*0.677), (w*0.38, h*0.677));
line(positivePath, (w*0.38, h*0.677), (w*0.64, h*0.25));
line(positiveGuide, (w*0.51, h*0.677), (w*0.51, h*0.463));
circle(negativeProbe, (w*0.25, h*0.677), 8*u);
circle(positiveProbe, (w*0.51, h*0.463), 8*u);
text(negativeNote, (w*0.79, h*0.50), "x = −2 → 0");
text(positiveNote, (w*0.79, h*0.59), "x = 2 → 2");
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC ML · ACTIVATION");
text(headline, (cx, h*0.145), "Why does a neuron need ReLU?");
text(caption, (cx, h*0.82), "A neuron first receives an unrestricted number.");
equation(rule, (cx, h*0.67), `\operatorname{ReLU}(x)=\max(0,x)`, 34*u);
activation(reluView, (cx, h*0.40), relu, w*0.72, h*0.28);
line(negativePath, (w*0.14, h*0.509), (cx, h*0.509));
line(positivePath, (cx, h*0.509), (w*0.86, h*0.26));
line(positiveGuide, (w*0.68, h*0.509), (w*0.68, h*0.384));
circle(negativeProbe, (w*0.32, h*0.509), 8*u);
circle(positiveProbe, (w*0.68, h*0.384), 8*u);
text(negativeNote, (w*0.27, h*0.58), "x = −2 → 0");
text(positiveNote, (w*0.73, h*0.58), "x = 2 → 2");
}
size(kicker, 19*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 34*u); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 21*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
hidden(rule);
color(negativePath, magenta); stroke(negativePath, 5*u); glow(negativePath, 0.35); untraced(negativePath);
color(positivePath, cyan); stroke(positivePath, 5*u); glow(positivePath, 0.35); untraced(positivePath);
color(positiveGuide, dim); stroke(positiveGuide, 2*u); dashed(positiveGuide, 9*u, 7*u); untraced(positiveGuide);
color(negativeProbe, magenta); filled(negativeProbe); glow(negativeProbe, 0.55); hidden(negativeProbe);
color(positiveProbe, cyan); filled(positiveProbe); glow(positiveProbe, 0.55); hidden(positiveProbe);
size(negativeNote, 18*u); color(negativeNote, magenta); bold(negativeNote); hidden(negativeNote);
size(positiveNote, 18*u); color(positiveNote, cyan); bold(positiveNote); hidden(positiveNote);
untraced(reluView.axes);
untraced(reluView.curve);
hidden(reluView.label);
step("question") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
draw(reluView.axes, 0.75);
}
wait(0.55);
step("rule") {
show(rule, 0.55);
say(caption, "ReLU makes one transparent promise: return the larger of zero and x.", 0.40);
}
wait(0.55);
step("negative-input") {
par {
draw(negativePath, 0.75);
show(negativeProbe, 0.35);
show(negativeNote, 0.35);
say(caption, "Negative input is muted at zero; it cannot send negative evidence onward.", 0.40);
}
}
wait(0.65);
step("positive-input") {
par {
draw(positivePath, 0.85);
draw(positiveGuide, 0.55);
show(positiveProbe, 0.35);
show(positiveNote, 0.35);
say(caption, "Positive input passes through unchanged, preserving its strength.", 0.40);
}
}
wait(0.70);
step("activation") {
par {
draw(reluView.curve, 1.10);
show(reluView.label, 0.35);
say(caption, "That small bend gives a network a nonlinear decision boundary.", 0.40);
par { cam((cx, h*0.47), 0.70, smooth); zoom(1.10, 0.70, smooth); }
}
}
wait(0.75);
step("takeaway") {
par {
pulse(reluView.curve, 0.75);
say(caption, "ReLU does not invent a signal: it gates what the neuron already computed.", 0.45);
par { cam((cx, cy), 0.70, smooth); zoom(1.0, 0.70, smooth); }
}
}
wait(1.50);
The gallery example combines activation, network, forward computation, named story stages, Creator branding, and a professional restrained layout:
// manic-ml-forward-pass.manic — ML1 creator proof
// A real deterministic network computes one prediction. The story keeps the
// model persistent and reveals only the active computation instead of flashing
// every connection at once.
title("Manic ML — A Forward Pass You Can Follow");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=See_the_computation safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
// One story reflows to portrait, feed, square, and landscape. The portrait
// network narrows to reserve a clean probability lane at the right.
if h > 1.45*w {
watermark(manicMark, (w*0.18, h*0.075), "Made With Manic");
text(kicker, (cx, h*0.08), "MANIC ML · FORWARD PASS");
text(headline, (cx, h*0.13), "How does a network choose?");
text(caption, (cx, h*0.72), "First, a neuron keeps positive evidence and removes negative evidence.");
activation(reluView, (cx, h*0.40), relu, w*0.72, h*0.24);
network(model, (cx, h*0.42), "3 6 4 3", "relu tanh softmax", w*0.55, h*0.32, 21);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.18, h*0.10), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · FORWARD PASS");
text(headline, (cx, h*0.14), "How does a network choose?");
text(caption, (cx, h*0.83), "First, a neuron keeps positive evidence and removes negative evidence.");
activation(reluView, (cx, h*0.49), relu, w*0.42, h*0.36);
network(model, (cx, h*0.47), "3 6 4 3", "relu tanh softmax", w*0.64, h*0.39, 21);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.07), "MANIC ML · FORWARD PASS");
text(headline, (cx, h*0.13), "How does a network choose?");
text(caption, (cx, h*0.82), "First, a neuron keeps positive evidence and removes negative evidence.");
activation(reluView, (cx, h*0.43), relu, w*0.68, h*0.30);
network(model, (cx, h*0.45), "3 6 4 3", "relu tanh softmax", w*0.64, h*0.40, 21);
}
size(kicker, 20*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 36*u); bold(headline); hidden(headline);
size(caption, 22*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
// A truthful ReLU curve introduces the operation used by the hidden layer.
hidden(reluView);
untraced(reluView.axes);
untraced(reluView.curve);
// 3 inputs → 6 ReLU units → 4 tanh units → 3 softmax probabilities.
// Seed 21 makes the educational model reproducible across every render.
hidden(model);
step("activation") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
show(reluView, 0.55);
draw(reluView.axes, 0.70);
draw(reluView.curve, 1.20);
}
wait(0.75);
step("meet-the-network") {
fade(reluView, 0.45);
show(model, 0.65);
say(caption, "The same operation now lives inside a small, deterministic network.", 0.45);
}
wait(0.65);
step("forward-pass") {
par {
forward(model, "0.15 0.92 0.38", 4.20, smooth);
say(caption, "Follow the bright path: inputs become evidence, then probabilities.", 0.45);
seq {
par { cam((w*0.24, h*0.47), 0.55, smooth); zoom(1.06, 0.55, smooth); }
wait(0.55);
cam((cx, h*0.47), 0.80, smooth);
wait(0.55);
cam((w*0.76, h*0.47), 0.80, smooth);
wait(0.40);
par { cam((cx, cy), 0.55, smooth); zoom(1.0, 0.55, smooth); }
}
}
}
wait(0.90);
step("takeaway") {
pulse(model.output, 0.75);
say(caption, "The picture is driven by the computed values—not a decorative animation.", 0.45);
}
wait(1.60);
The ML2 story keeps one network on screen through prediction, target, loss, backward credit assignment, and a visibly recomputed gradient update:
// manic-ml-learning-step.manic — ML2 creator proof
// One persistent network predicts, measures its mistake, sends exact gradients
// backward, changes its parameters, then rolls that one saved change back.
// No layer is cleared and redrawn; rollback is not claimed as full unlearning.
title("Manic ML — How One Mistake Becomes Learning");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=See_the_learning safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.13, h*0.075), "Made With Manic");
text(kicker, (cx, h*0.08), "MANIC ML · ONE LEARNING STEP");
text(headline, (cx, h*0.13), "How does a model learn?");
text(caption, (cx, h*0.72), "First the network predicts from the current weights.");
network(model, (cx, h*0.42), "3 6 4 3", "relu tanh softmax", w*0.55, h*0.32, 21);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.18, h*0.10), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · ONE LEARNING STEP");
text(headline, (cx, h*0.14), "How does a model learn?");
text(caption, (cx, h*0.83), "First the network predicts from the current weights.");
network(model, (cx, h*0.47), "3 6 4 3", "relu tanh softmax", w*0.64, h*0.39, 21);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.07), "MANIC ML · ONE LEARNING STEP");
text(headline, (cx, h*0.13), "How does a model learn?");
text(caption, (cx, h*0.82), "First the network predicts from the current weights.");
network(model, (cx, h*0.45), "3 6 4 3", "relu tanh softmax", w*0.64, h*0.40, 21);
}
size(kicker, 20*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 35*u); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 22*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
hidden(model);
step("question") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
show(model, 0.65);
}
wait(0.55);
step("predict") {
par {
forward(model, "0.15 0.92 0.38", 3.20, smooth);
say(caption, "A forward pass turns the input into three probabilities.", 0.40);
}
}
wait(0.55);
step("measure-the-mistake") {
par {
loss(model, "1 0 0", crossentropy, 1.50, smooth);
say(caption, "The correct answer is class 1. Cross-entropy measures the mismatch.", 0.40);
}
}
wait(0.55);
step("send-credit-backward") {
par {
backward(model, 3.20, smooth);
say(caption, "The gradient carries responsibility backward through the same connections.", 0.40);
seq {
par { cam((w*0.76, h*0.47), 0.40, smooth); zoom(1.08, 0.40, smooth); }
wait(0.30);
cam((cx, h*0.47), 0.55, smooth);
wait(0.25);
cam((w*0.24, h*0.47), 0.55, smooth);
wait(0.25);
par { cam((cx, cy), 0.45, smooth); zoom(1.0, 0.45, smooth); }
}
}
}
wait(0.55);
// A zero-duration authored checkpoint captures the exact pre-update weights,
// prediction, target, and loss. It does not add a hidden runtime state.
checkpoint(beforeUpdate, model);
step("learn") {
par {
update(model, 0.18, 2.30, smooth);
say(caption, "Each parameter moves opposite its gradient; the same input is computed again.", 0.40);
}
}
wait(0.70);
step("unlearn") {
par {
restore(model, beforeUpdate, 2.30, smooth);
say(caption, "Exact rollback restores the saved parameters and their earlier prediction.", 0.40);
seq {
par { cam((w*0.24, h*0.47), 0.40, smooth); zoom(1.08, 0.40, smooth); }
wait(0.30);
cam((cx, h*0.47), 0.55, smooth);
wait(0.25);
cam((w*0.76, h*0.47), 0.55, smooth);
wait(0.25);
par { cam((cx, cy), 0.45, smooth); zoom(1.0, 0.45, smooth); }
}
}
}
wait(0.70);
step("takeaway") {
pulse(model.output, 0.75);
say(caption, "This undoes one saved update. Dataset-level unlearning is a different process.", 0.45);
}
wait(1.50);
The ML3 story turns a small image into an edge-response feature map and then a pooled summary, with the same scanner serving both operators:
// manic-ml-cnn-edge-story.manic — ML3 creator proof
// A tiny image becomes a feature map and then a pooled summary. Every number is
// computed by Manic; scan coordinates the receptive field and destination.
title("Manic ML — How a CNN Finds an Edge");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=See_the_feature safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
let cell = 48*u;
if h > 1.45*w {
watermark(manicMark, (w*0.13, h*0.075), "Made With Manic");
text(kicker, (cx, h*0.08), "MANIC ML · CONVOLUTION");
text(headline, (cx, h*0.13), "How does a CNN see edges?");
text(caption, (cx, h*0.72), "Start with a tiny image: bright cells form a simple shape.");
tensor(image, (w*0.28, h*0.35), "0 0 0 0 0; 0 0 1 1 0; 0 1 1 1 0; 0 0 1 1 0; 0 0 0 0 0", cell, cyan);
kernel(edge, (w*0.72, h*0.35), "-1 0 1; -2 0 2; -1 0 1", cell, magenta);
convolve(feature, image, edge, (w*0.34, h*0.54), 1, 0, 0, relu, cell);
pool(pooled, feature, (w*0.72, h*0.54), max, 2, 1, 0, cell);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.18, h*0.10), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · CONVOLUTION");
text(headline, (cx, h*0.14), "How does a CNN see edges?");
text(caption, (cx, h*0.82), "Start with a tiny image: bright cells form a simple shape.");
tensor(image, (w*0.17, h*0.47), "0 0 0 0 0; 0 0 1 1 0; 0 1 1 1 0; 0 0 1 1 0; 0 0 0 0 0", cell, cyan);
kernel(edge, (w*0.42, h*0.47), "-1 0 1; -2 0 2; -1 0 1", cell, magenta);
convolve(feature, image, edge, (w*0.68, h*0.47), 1, 0, 0, relu, cell);
pool(pooled, feature, (w*0.89, h*0.47), max, 2, 1, 0, cell);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.07), "MANIC ML · CONVOLUTION");
text(headline, (cx, h*0.13), "How does a CNN see edges?");
text(caption, (cx, h*0.82), "Start with a tiny image: bright cells form a simple shape.");
tensor(image, (w*0.18, h*0.44), "0 0 0 0 0; 0 0 1 1 0; 0 1 1 1 0; 0 0 1 1 0; 0 0 0 0 0", cell, cyan);
kernel(edge, (w*0.45, h*0.44), "-1 0 1; -2 0 2; -1 0 1", cell, magenta);
convolve(feature, image, edge, (w*0.70, h*0.44), 1, 0, 0, relu, cell);
pool(pooled, feature, (w*0.89, h*0.44), max, 2, 1, 0, cell);
}
size(kicker, 20*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 35*u); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 22*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
hidden(image); hidden(edge); hidden(feature); hidden(pooled);
step("pixels") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
show(image, 0.65);
}
wait(0.55);
step("edge-detector") {
show(edge, 0.55);
say(caption, "This 3×3 kernel compares the left side of each patch with the right.", 0.40);
}
wait(0.55);
step("feature-map") {
show(feature, 0.50);
say(caption, "The feature map begins quiet; each destination waits for one receptive field.", 0.40);
}
wait(0.35);
step("convolution-scan") {
par {
scan(feature, 4.80, smooth);
say(caption, "The same kernel slides, multiplies, sums, applies ReLU, and writes one cell.", 0.40);
seq {
par { cam((w*0.38, h*0.45), 0.75, smooth); zoom(1.10, 0.75, smooth); }
wait(2.95);
par { cam((cx, cy), 0.85, smooth); zoom(1.0, 0.85, smooth); }
}
}
}
wait(0.60);
step("pooling-map") {
show(pooled, 0.50);
say(caption, "Max pooling asks a simpler question: where is the strongest local evidence?", 0.40);
}
wait(0.40);
step("pooling-scan") {
par {
scan(pooled, 3.40, smooth);
say(caption, "Each 2×2 window keeps its first maximum; ties are deterministic.", 0.40);
}
}
wait(0.70);
step("takeaway") {
pulse(pooled.cells, 0.75);
say(caption, "A CNN builds meaning locally: pixels → feature responses → compact evidence.", 0.45);
}
wait(1.50);
The ML5 story begins with a repeated word, proves that its base lookup vector is reused, and then shows how sinusoidal position makes each occurrence unique:
// manic-ml-token-embedding.manic — ML5 acceptance story
// A repeated word keeps one seeded educational token embedding, while exact
// sinusoidal position makes each occurrence a different model input.
title("Manic ML — From Words to Positioned Embeddings");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Follow_the_representation safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.16, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.055), "MANIC ML · EMBEDDINGS");
text(headline, (cx, h*0.095), "A word needs meaning—and a place");
text(caption, (cx, h*0.84), "Start with text. Keep every boundary and every number inspectable.");
tokenize(words, (cx, h*0.19), "the cat chased the cat", word, w*0.80);
embedding(context, words, (cx, h*0.50), "seeded 6 37", sinusoidal, w*0.90, h*0.48);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.16, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.07), "MANIC ML · EMBEDDINGS");
text(headline, (cx, h*0.125), "A word needs meaning—and a place");
text(caption, (cx, h*0.84), "Start with text. Keep every boundary and every number inspectable.");
tokenize(words, (cx, h*0.23), "the cat chased the cat", word, w*0.68);
embedding(context, words, (cx, h*0.54), "seeded 6 37", sinusoidal, w*0.90, h*0.48);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC ML · EMBEDDINGS");
text(headline, (cx, h*0.17), "A word needs meaning—and a place");
text(caption, (cx, h*0.83), "Start with text. Keep every boundary and every number inspectable.");
tokenize(words, (cx, h*0.30), "the cat chased the cat", word, w*0.76);
embedding(context, words, (cx, h*0.60), "seeded 6 37", sinusoidal, w*0.90, h*0.38);
}
size(kicker, 18*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 32*u); bold(headline); wrap(headline, w*0.84); hidden(headline);
size(caption, 20*u); color(caption, dim); wrap(caption, w*0.82); hidden(caption);
hidden(words);
hidden(context);
step("sentence") {
show(kicker, 0.30);
show(headline, 0.42);
show(words.labels, 0.35);
show(words.source, 0.40);
show(caption, 0.35);
}
wait(0.55);
step("tokens") {
par {
show(words.tokens, 0.75);
show(words.indices, 0.75);
say(caption, "Word tokenization turns the sentence into five ordered identities.", 0.38);
}
}
wait(0.60);
step("lookup") {
fade(words, 0.35);
par {
show(context.labels, 0.40);
show(context.tokens, 0.55);
show(context.vectors, 0.95);
say(caption, "A seeded educational lookup gives each token identity one six-number vector.", 0.40);
}
}
wait(0.60);
step("same-word") {
par {
pulse(context.row1, 0.70);
pulse(context.row4, 0.70);
say(caption, "Both copies of cat reuse the same base embedding. The lookup depends on the token—not its location.", 0.42);
}
}
wait(0.65);
step("position") {
par {
show(context.positions, 1.00);
show(context.operators, 0.45);
say(caption, "Sinusoidal position adds a deterministic coordinate for places 0 through 4.", 0.42);
}
}
wait(0.65);
step("model-input") {
par {
show(context.combined, 1.00);
say(caption, "Token vector plus position becomes the model input. The repeated word now carries two different locations.", 0.42);
}
}
wait(0.70);
step("takeaway") {
par {
pulse(context.row1, 0.72);
pulse(context.row4, 0.72);
say(caption, "Meaning says what the token is. Position says where this occurrence belongs.", 0.42);
}
}
wait(1.40);
The ML4 story keeps the token lanes visible while one query reveals Q/K/V, one truthful softmax row, its weighted value mix, the residual, and a small candidate ranking:
// manic-ml-transformer-attention.manic — ML4 acceptance story
// Explicit token embeddings become Q/K/V, one query row becomes normalized
// attention weights, values mix, a residual is added, and a real output
// projection produces top-k probabilities.
title("Manic ML — One Transformer Attention Head");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Follow_the_attention safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · SELF-ATTENTION");
text(headline, (cx, h*0.105), "One token finds context");
text(caption, (cx, h*0.84), "Start with explicit token embeddings—not decorative wires.");
attention(head, (cx, h*0.38), "Art | ificial | intelligence | transforms | business",
"1 0.2 -0.4 0.7; 0.8 0.1 -0.3 0.6; -0.2 1 0.5 0.3; 0.1 0.6 0.9 -0.2; 0.7 -0.1 0.4 1",
w*0.82, h*0.40, 23);
topk(next, head, 3, (cx, h*0.71), "business | work | world | industry | future | people", 4, w*0.72, h*0.18, 29);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.17, h*0.09), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · SELF-ATTENTION");
text(headline, (cx, h*0.14), "One token finds context");
text(caption, (cx, h*0.83), "Start with explicit token embeddings—not decorative wires.");
attention(head, (w*0.40, h*0.48), "Art | ificial | intelligence | transforms | business",
"1 0.2 -0.4 0.7; 0.8 0.1 -0.3 0.6; -0.2 1 0.5 0.3; 0.1 0.6 0.9 -0.2; 0.7 -0.1 0.4 1",
w*0.68, h*0.58, 23);
topk(next, head, 3, (w*0.84, h*0.52), "business | work | world | industry | future | people", 4, w*0.26, h*0.34, 29);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC ML · SELF-ATTENTION");
text(headline, (cx, h*0.13), "One token finds context");
text(caption, (cx, h*0.84), "Start with explicit token embeddings—not decorative wires.");
attention(head, (cx, h*0.39), "Art | ificial | intelligence | transforms | business",
"1 0.2 -0.4 0.7; 0.8 0.1 -0.3 0.6; -0.2 1 0.5 0.3; 0.1 0.6 0.9 -0.2; 0.7 -0.1 0.4 1",
w*0.84, h*0.42, 23);
topk(next, head, 3, (cx, h*0.72), "business | work | world | industry | future | people", 4, w*0.72, h*0.18, 29);
}
size(kicker, 19*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 34*u); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 21*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
hidden(head); hidden(next);
step("tokens") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
show(head.labels, 0.40);
show(head.tokens, 0.60);
}
wait(0.55);
step("projections") {
par {
show(head.q, 0.55);
show(head.k, 0.55);
show(head.v, 0.55);
say(caption, "Each embedding is projected into a Query, Key, and Value vector.", 0.40);
}
}
wait(0.60);
step("scores") {
show(head.matrix, 0.70);
say(caption, "Scaled dot products become one softmax row: every weight is positive and the row sums to 100%.", 0.45);
}
wait(0.65);
step("attend") {
par {
attend(head, 3, 5.20, smooth);
say(caption, "Focus intelligence: Q asks, K measures relevance, and weighted V carries the context.", 0.45);
seq {
par { cam((cx, h*0.46), 0.75, smooth); zoom(1.07, 0.75, smooth); }
wait(3.45);
par { cam((cx, cy), 0.85, smooth); zoom(1.0, 0.85, smooth); }
}
}
}
wait(0.75);
step("prediction") {
show(next, 0.70);
say(caption, "The residual plus attention mix enters an output projection and a truthful softmax ranking.", 0.45);
}
wait(0.80);
step("takeaway") {
pulse(next.rank0, 0.75);
say(caption, "Attention is selective information flow—not every connection shouting at once.", 0.45);
}
wait(1.50);
The ML6 story preserves one token lane through two causal heads, concatenation, both residual/norm stages, a GELU MLP, and the exact settled block output:
// manic-ml-transformer-block.manic — ML6 acceptance story
// One persistent token lane passes through multi-head causal attention,
// concatenation, two residual paths, pre-normalization, and a GELU MLP.
title("Manic ML — Inside One Transformer Block");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Follow_the_computation safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · TRANSFORMER BLOCK");
text(headline, (cx, h*0.105), "Inside one transformer block");
text(caption, (cx, h*0.84), "Follow one token lane through every computed stage.");
tokenize(words, (cx, h*0.15), "the cat slept because it dreamed", word, w*0.82);
embedding(context, words, (cx, h*0.41), "seeded 6 37", sinusoidal, w*0.90, h*0.38);
transformer(block, context, (cx, h*0.49), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.90, h*0.56);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.17, h*0.09), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · TRANSFORMER BLOCK");
text(headline, (cx, h*0.14), "Inside one transformer block");
text(caption, (cx, h*0.82), "Follow one token lane through every computed stage.");
tokenize(words, (cx, h*0.22), "the cat slept because it dreamed", word, w*0.72);
embedding(context, words, (cx, h*0.54), "seeded 6 37", sinusoidal, w*0.90, h*0.48);
transformer(block, context, (cx, h*0.52), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.92, h*0.62);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC ML · TRANSFORMER BLOCK");
text(headline, (cx, h*0.13), "Inside one transformer block");
text(caption, (cx, h*0.83), "Follow one token lane through every computed stage.");
tokenize(words, (cx, h*0.23), "the cat slept because it dreamed", word, w*0.78);
embedding(context, words, (cx, h*0.52), "seeded 6 37", sinusoidal, w*0.90, h*0.44);
transformer(block, context, (cx, h*0.52), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.90, h*0.57);
}
size(kicker, 18*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 30*u); bold(headline); wrap(headline, w*0.86); hidden(headline);
size(caption, 19*u); color(caption, dim); wrap(caption, w*0.84); hidden(caption);
hidden(words);
hidden(context);
hidden(block);
step("tokens") {
par {
show(kicker, 0.28);
show(headline, 0.42);
show(words.labels, 0.30);
show(words.source, 0.36);
show(words.tokens, 0.70);
show(words.indices, 0.70);
show(caption, 0.32);
say(caption, "Six ordered tokens begin as one persistent lane.", 0.36);
}
}
wait(0.55);
step("model-input") {
seq {
fade(words, 0.32);
par {
show(context.labels, 0.32);
show(context.tokens, 0.42);
show(context.vectors, 0.82);
say(caption, "Each identity receives a stable lookup vector.", 0.36);
}
par {
show(context.positions, 0.72);
show(context.operators, 0.34);
show(context.combined, 0.78);
say(caption, "Position is added exactly. These six vectors are the block input.", 0.38);
}
}
}
wait(0.60);
step("encode") {
seq {
par {
fade(context, 0.34);
fade(caption, 0.24);
}
par {
encode(block, 6.2, smooth);
seq {
par { cam((cx - w*0.25, h*0.52), 0.75, smooth); zoom(1.08, 0.75, smooth); }
wait(1.35);
cam((cx + w*0.17, h*0.52), 1.15, smooth);
wait(1.50);
par { cam((cx, cy), 0.85, smooth); zoom(1.0, 0.85, smooth); }
}
}
}
}
wait(0.70);
step("takeaway") {
seq {
show(caption, 0.20);
par {
pulse(block.residual1, 0.72);
pulse(block.residual2, 0.72);
pulse(block.output, 0.72);
say(caption, "Attention shares context. The MLP reshapes each token. Residuals keep its identity continuous.", 0.42);
}
}
}
wait(1.35);
The ML7 story compares the same projection at cool and warm temperatures, then shows greedy and top-p decoding as exact filtered distributions and one seeded next-token choice:
// manic-ml-logits-sampling.manic — ML7 acceptance story
// The same LM projection is viewed at two temperatures, then top-p sampling
// filters, renormalizes, and makes one reproducible next-token choice.
title("Manic ML — How a Transformer Chooses the Next Token");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Follow_the_computation safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.07), "MANIC ML · LOGITS → PROBABILITIES → TOKEN");
text(headline, (cx, h*0.115), "How does a transformer choose its next word?");
text(caption, (cx, h*0.83), "One hidden vector. One full distribution. One reproducible choice.");
tokenize(words, (cx, h*0.17), "the model learned to", word, w*0.78);
embedding(context, words, (cx, h*0.42), "seeded 6 37", sinusoidal, w*0.88, h*0.34);
transformer(block, context, (cx, h*0.47), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.90, h*0.50);
logits(cool, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 0.55, w*0.86, h*0.54, 73);
logits(warm, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 1.45, w*0.86, h*0.54, 73);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.17, h*0.09), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · LOGITS → PROBABILITIES → TOKEN");
text(headline, (cx, h*0.13), "How does a transformer choose its next word?");
text(caption, (cx, h*0.82), "One hidden vector. One full distribution. One reproducible choice.");
tokenize(words, (cx, h*0.21), "the model learned to", word, w*0.64);
embedding(context, words, (cx, h*0.50), "seeded 6 37", sinusoidal, w*0.88, h*0.42);
transformer(block, context, (cx, h*0.50), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.90, h*0.58);
logits(cool, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 0.55, w*0.78, h*0.54, 73);
logits(warm, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 1.45, w*0.78, h*0.54, 73);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · LOGITS → PROBABILITIES → TOKEN");
text(headline, (cx, h*0.155), "How does a transformer choose its next word?");
text(caption, (cx, h*0.82), "One hidden vector. One full distribution. One reproducible choice.");
tokenize(words, (cx, h*0.21), "the model learned to", word, w*0.70);
embedding(context, words, (cx, h*0.50), "seeded 6 37", sinusoidal, w*0.88, h*0.40);
transformer(block, context, (cx, h*0.50), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.90, h*0.55);
logits(cool, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 0.55, w*0.82, h*0.54, 73);
logits(warm, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 1.45, w*0.82, h*0.54, 73);
}
size(kicker, 17*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88); hidden(headline);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86); hidden(caption);
hidden(words);
hidden(context);
hidden(block);
hidden(cool);
hidden(warm);
step("prompt") {
par {
show(kicker, 0.28);
show(headline, 0.42);
show(words.labels, 0.28);
show(words.source, 0.34);
show(words.tokens, 0.62);
show(words.indices, 0.62);
show(caption, 0.28);
say(caption, "The prompt becomes an ordered token lane.", 0.34);
}
}
wait(0.45);
step("hidden-state") {
seq {
fade(words, 0.28);
par {
show(block.output, 0.70);
show(block.labels, 0.26);
say(caption, "The transformer MLP ends at a hidden representation—not probabilities.", 0.40);
}
pulse(block.output, 0.72);
}
}
wait(0.45);
step("cool-temperature") {
seq {
fade(block, 0.30);
sample(cool, "greedy", 3.4, smooth);
say(caption, "Low temperature sharpens every candidate. Greedy keeps only the maximum.", 0.42);
}
}
wait(0.65);
step("warm-temperature") {
seq {
fade(cool, 0.30);
sample(warm, "top-p 0.90 seed=17", 3.8, smooth);
say(caption, "Higher temperature spreads the full softmax. Top-p keeps the smallest 90% nucleus, renormalizes it, then samples.", 0.46);
}
}
wait(0.75);
step("takeaway") {
par {
pulse(warm.temperature, 0.70);
pulse(warm.probabilities, 0.70);
say(caption, "Temperature reshapes probability. Sampling turns that distribution into one reproducible next token.", 0.44);
}
}
wait(1.30);
Kits — domain knowledge, ordinary Manic composition
The words so far (circle, move, flash, for…) are the core. On top of
that, manic ships kits — bundles of higher-level figures for a domain. You
use them exactly like any other call.
math
Coordinate frames, function plots, vectors, tables:
axes(ax, (cx, cy), 520, 240); // a coordinate frame
plot(wave, (cx, cy), 78, 120, "sin(x)"); // y = f(x) from a formula
tangent(t, wave, 0.5); // the tangent line + dot at x = 0.5
vector(v, (cx, cy), (120, -90)); // an arrow from an origin
matrix(m, "1 0; 0 1", (cx, cy)); // a bracketed matrix
geo
Olympiad-style constructions — you write the geometry, not coordinates, and everything is live (drag a point and the circumcircle, centroid, angles all recompute):
point(A, (300, 500)); point(B, (900, 500)); point(C, (620, 180));
circumcircle(cc, A, B, C); // recomputes if A/B/C move
midpoint(m, A, B);
algo
Data structures and algorithms — arrays + sorting, linked lists, stacks/queues, graphs, hash maps, BFS/DFS, Dijkstra:
array(a, "5 2 8 1", (cx, cy)); compare(a, 0, 1); swap(a, 0, 1);
graph(g, "a b c d", "a-b:2 b-c:1 c-d:3", circular, (cx, cy), 200);
dijkstra(g, a); // animates shortest paths
Groups make these one-liners: a graph tags its nodes and edges, so
draw(g.edges) or flash(g.nodes, cyan) animates the whole set.
ml
Small neural-network explanations whose displayed activations and probabilities come from the declared model rather than a staged effect:
network(net, (cx, cy), "3 6 4 3", "relu tanh softmax", 820, 350, 21);
forward(net, "0.15 0.92 0.38", 4.2, smooth);
loss(net, "1 0 0", crossentropy, 1.5, smooth);
backward(net, 3.2, smooth);
checkpoint(beforeUpdate, net);
update(net, 0.18, 2.3, smooth);
restore(net, beforeUpdate, 2.3, smooth); // exact rollback, not general unlearning
tensor(image, (260, 340), "0 0 1; 0 1 1; 0 0 1", 44);
kernel(edge, (560, 340), "-1 0 1; -2 0 2; -1 0 1", 44);
convolve(feature, image, edge, (850, 340), 1, 1, 0, relu, 44);
scan(feature, 4.0, smooth);
tokenize(words, (650, 150), "the cat chased the cat", word, 900);
embedding(context, words, (650, 470), "seeded 6 37", sinusoidal, 1080, 430);
transformer(block, context, (650, 500),
"heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41",
1120, 520);
encode(block, 6.2, smooth);
logits(next, block, 5, (650, 500),
"reason | predict | learn | adapt | explain | .", 0.8, 760, 440, 73);
sample(next, "top-p 0.90 seed=17", 3.8, smooth);
attention(head, (650, 360), "Art | ificial | intelligence | transforms | business",
"1 0.2 -0.4 0.7; 0.8 0.1 -0.3 0.6; -0.2 1 0.5 0.3; 0.1 0.6 0.9 -0.2; 0.7 -0.1 0.4 1",
980, 420, 23);
attend(head, 3, 5.2, smooth);
Use activation to introduce a scalar activation curve, then network and
forward to follow one computation. Add loss, backward, and update for
one explicit, numerically truthful learning step. Use tensor, kernel,
convolve, pool, and the shared scan for CNN/operator stories. Large layers
automatically reduce visual detail without reducing the numerical model. Use
tokenize and embedding to reveal honest token boundaries, stable lookup
vectors, exact positional values, and their elementwise sum. Use
transformer and encode for one complete multi-head block with masking,
normalization, residuals, MLP, and truthful training/inference dropout. Use
logits and sample to keep the separate LM projection, temperature-scaled
full softmax, filtering, renormalization, and seeded next-token choice truthful. Use
attention and attend for one focused, exact self-attention head; add topk
only when a deterministic educational candidate ranking helps the story. See
Machine learning — models made visible.
three (3D)
A whole second world — a camera, solids, surfaces, and curves in real 3D space,
which you spin and morph. Every 3D word ends in 3:
camera3((8, -10, 6), (0, 0, 1), 45); // an eye to look through
cube3(box, (0, 0, 1), (2, 2, 2)); // a shaded box
revolve3(vase, (3, 0, 1.5), "0.7+0.4*sin(t*2)", (0, 3)); // spin a profile
orbit3(70, 25, 12, 4, smooth); // swing the camera around
It has its own chapter — see Going 3D.
stats
Turn data — or a random process — into a picture that reveals its shape, centre, and spread. Each builtin animates a process: a histogram builds up bar by bar, sample means pile into a bell, a running proportion settles onto the truth.
histogram(h, (cx, cy), "72 85 90 68 95 88 76 91 83", 8, 640, 300, rainbow);
bellcurve(b, (cx, cy), 100, 15); // the 68-95-99.7 rule
clt(c, (cx, cy), 5, 1200); // the Central Limit Theorem
histogram · summary · boxplot · skew · bellcurve · correlation ·
lln · clt · hypothesis · covariance · bayes · distribution ·
confidence · montecarlo · randomwalk. Seeded, so renders are reproducible.
physics
Simulations built from their physics and pre-simulated with RK4 at build time,
so every render is deterministic. Each sim’s parts are ordinary manic entities, and
the optional views (phase · well · timegraph · energygraph) show the same
motion as math panels. run(id) (alias swing) plays it.
pendulum(p, (cx, 200), 2, 50); phase(p, (980, 200), 120);
well(p, (980, 470), 120); run(p, 8); // one swing, three views
doublependulum(dp, (400, 240)); par { run(dp, 12); draw(dp.path, 12); } // chaos
Pendulum family: pendulum · doublependulum · springpendulum · kapitza ·
cartpendulum · comparependulum. Spring family: spring · verticalspring ·
springincline · bungee · resonance · doublespring · seriesparallel ·
carsuspension. Mechanics: robotarm · piston · molecule · ramp (with a forces(id)
free-body diagram) · inclinepulley · doubleincline · inclinebumper ·
springchain · looptrack (a curved-track loop-the-loop) · stringwave (a wave on a string) · newtonscradle · collideblocks · bulletblock (event-driven collisions) · dropmass · raft ·
brachistochrone. Pulleys: pulley (Atwood) ·
pulleyscale (reads the tension) · blocktackle (N-strand block & tackle) ·
compoundpulley (fixed + movable, masses A/B/C).
Because a sim’s parts are ordinary entities, any base look composes over them —
e.g. template("paper") + a hatched support turns a pulley or spring into a
textbook figure (see Elevating a scene and the *-paper examples).
optics
Light as geometry, with the real physics underneath — Snell’s law, Sellmeier
dispersion, and full spherical/aspheric ray tracing — so the bending, the colours
and the focus are earned, not painted. Each builtin is static geometry that
animates by a parameter sweep (run(id)) or by sketching its rays on
(draw(id.rays)).
refract(r, (640, 380), 1.0, 1.52); run(r, 7); // Snell's law; run sweeps the angle (→ TIR)
lens(l, (620, 360)); run(l, 7); // parallel rays → a focal point
prism(p, (560, 400), "sf11"); run(p, 7); // white light → a real rainbow (dispersion)
Foundations: refract (Snell + total internal reflection) · lens (a converging
thin lens). Dispersion: prism (white → spectrum) · achromat (chromatic
aberration → the doublet fix). Real lenses: lenssystem(id, [center], [preset], [object]) traces a prescription through its actual spherical/aspheric
surfaces — pick a design by name ("singlet", "plano-convex", "aspheric",
"doublet", "triplet") or write your own surface table
"radius thickness glass [conic] [aperture] | …"; an optional finite object
distance images a nearby point. Analysis: rayfan (the ray-fan aberration plot) ·
spotdiagram (the on-axis spot at focus) · fieldspot(id, [center], [preset], [field]) (the off-axis spot — a coma comet / astigmatic blur, with an
Airy-disk diffraction-limit overlay). A rainbow glows on the dark bench; the
geometric ray diagrams also take template("paper") for a textbook look.
creator
A format layer (not a subject): responsive, pre-timed social-video templates a content creator fills in—question, answers, media and a reusable profile. V2 adapts the same source to 9:16, 4:5, 1:1 and 16:9, with named platform safe areas, a polished studio default, configurable motion/timers, responsive footers and end cards.
canvas("9:16"); template("shorts");
creator(me, "@anish2good name=Math_With_Me yt=zarigatongy x=@anish2good web=8gwifi.org/manic footer=social accent=magenta");
quiz(q, "What is 7 x 8?", "studio labels=letters pace=calm motion=calm");
option(q, "54"); option(q, "56", correct); option(q, "48"); option(q, "63");
timerstyle(q, "look=segments position=media finish=pulse");
run(q, 8); // scales the calm ask → think → reveal beat
socials(me);
endcard(me); // reveal later with show(me.endcard)
quiz(id, "question", ["style"]) starts the format; style mixes a card skin —
studio (rounded editorial default) · badge (framed panel + coloured letter badges) · minimal (kicker + accent
rule, outline rows) · glass (glowing borders) · plain (flat) — and a question
reveal — type (typewriter, default) · fade · rise · pop · cut.
option(id, "text", [correct]) adds an answer; run auto-lays-out one to six cards,
fits their type, slides them in, plays the selected native timer, and lights up the correct
card (green badge + check). timing(id,"preset ask=... options=... think=... reveal=... hold=... stagger=...")
separates exact choreography from timerstyle(id,"look=... position=... direction=... finish=...").
The zero-config default remains a balanced draining ring; run(id,dur) scales a preset,
while an explicitly timed quiz uses run(id) so authored seconds remain exact. Also standalone:
countdown(id, [at], [secs], ["style"]), safezone(id, [inset|"profile"]), figure(target, [center], [size]), optional explain, and endcard. Social icons are
vector-drawn with normalized native marks for YouTube, X, Instagram, TikTok,
Facebook, LinkedIn, GitHub, web, and email. Profile values appear beside up to
three icons; no image or SVG assets are required.
Each kit has a full reference at https://8gwifi.org/manic, and you can see them all in motion in the Examples gallery.
Race charts — paste a table, get a race
The charts kit turns a table into an animated race chart — ranked bars, columns, or lines that reorder over time as the numbers change. It’s the viral data-viz format (GDP, population, brands, sports…), but true to the data and self-contained in one script: no timeline scrubbing, no keyframes.
Three ideas:
- Declare a chart —
racechart(id, layout, periods) - Paste the data —
racedata(id, "…") - Play it —
race(id, seconds)
canvas("16:9"); template("blank");
racechart(gdp, "bar", "2000 2010 2020 2024", "GDP by Country ($T)");
racedata(gdp, "
USA, us, 10.3, 15.0, 21.4, 27.4
China, cn, 1.2, 6.1, 14.7, 20.5
Japan, jp, 4.9, 5.8, 5.0, 4.2
India, in, 0.5, 1.7, 2.7, 3.9
");
race(gdp, 8);
The bars grow, reorder as ranks change (the reorder is the race), the axis rescales with the running max, and the year + values tick — all from that block.
The data block
racedata(parent, "block") is the heart of it. One row per entity:
label [icon] v0 v1 v2 … ← one value per period
The parser is forgiving so real data pastes straight in:
- Rows split on a newline or a
;. - Cells split on comma, tab, or whitespace — so a CSV, a TSV, or a spreadsheet copy all work.
- Missing cells count as 0 (e.g. a company/party that didn’t exist yet).
- Multi-word labels are fine when the data is comma- or tab-delimited.
Prefer to build rows in code? raceseries adds one entity at a time — the
computed/loop path:
for i in 0..n {
raceseries(g, name{i}, "rocket", vals{i}); // label, icon, values
}
Icons — any SVG on the bar
The optional icon column puts a flag, logo, or emoji on each bar. It takes any SVG, not just flags:
| You write | Resolves to |
|---|---|
us | a flag shorthand → asset:svg/flags/us.svg |
rocket, heart, trophy | an emoji / icon alias |
asset:svg/lucide/lightbulb.svg | an explicit set path |
logos/acme.svg | your own file (any path ending .svg) |
| (omit it) | the row is just label + values |
Icons are imported as real geometry via svg(), so they recolour
and scale cleanly. Browse every bundled asset in the SVG catalogue.
A handful of flags whose stars use SVG
<use>/<marker>(e.g. China) render imperfectly for now — most flags and all icons/emoji are clean.
Layouts
racechart(id, layout, …) takes one of three:
"bar"— horizontal bars, ranked top-to-bottom. The classic; gets value gridlines + a live Total readout."column"— vertical bars, ranked left-to-right."line"— each series a line that draws on over time; the race is which line climbs highest (fixed axis, tip labels auto-declutter).
Bar + line, together
On a bar race you can overlay a companion line:
raceline(parent)— a line across the top showing a running total (auto-summed from the bars), orraceline(parent, "label", "v0 v1 …")for an explicit metric.racepanel(parent)— a multi-line history panel below the bars: every series as a line revealed up to a moving time-cursor (the Flourish bar+line combo).
racechart(de, "bar", "1949 … 2021", "German Elections %");
racedata(de, " CDU/CSU, , 31, … ; SPD, , 29, … ; … ");
racepanel(de); // history panel below the bars
race(de, 16);
Composing with the rest of manic
A race chart is just tagged entities, so it drops into any scene — a voiced
Creator Short, a quiz reveal, a caption sequence. In
creator-race-quiz a quiz poses a question and
the race chart proves the answer, narrated with speak, closed with a CTA
card — the chart running inside a par while the voice-over plays over it.
Examples
- GDP bar race — flags racing, gridlines, total
- Column race — tennis Grand Slams
- Line race — GDP trajectories
- Bar + total line
- Bar + history panel — from a real CSV
- Race-chart quiz Short — voiced, problem→proof→CTA
Diagrams — draw a system, then watch it work
Declare a technical diagram as text and manic animates it — architecture
diagrams today, with flows, sequences, and more to come. Declare what belongs
where, connect the topology, then follow one message through the design. Manic
handles nested responsive layout, provider artwork, parallel lanes, and
deterministic motion; ordinary step, show, draw, flow, camera, and text
verbs remain the presentation language.
Unlike static diagram-as-code (Mermaid, Mingrammer), a manic diagram moves: the same source that draws the boxes also carries a request through them.
The vocabulary
The whole kit is nine words — a behaviour layer over diagram structure, not a catalogue of cloud verbs:
architecture cluster node connect link
message route hotpath flow
With them you get provider-neutral nodes (no assets) or real icons from 17 providers; declaration-first nested clusters with responsive layout; direct, curved, and orthogonal connections with node-boundary ports; cold dashed topology kept separate from solid runtime motion; and one persistent message whose identity survives every hop. The kit shines for one clear story — and density is no longer your job: geometry is optional, so even a dense platform overview auto-fits and scales itself to stay in-frame (see It just fits, below).
Possible vs. actual
The one rule to internalise: a connection states what is possible; a moving message states what happened.
connect(toWorker1, queue, worker1, orthogonal, right, left);
connect(toWorker2, queue, worker2);
connect(toWorker3, queue, worker3, orthogonal, right, left);
message(job, queue, "101");
route(job, toWorker2, 0.9, linear);
route(job, worker2ToDatabase, 0.9, linear);
The three connections state what is possible. Only the two explicit route
calls state what happened. Manic does not claim RabbitMQ selected Worker 2; the
creator authored that selection.
The mental model
| Layer | Words | Meaning |
|---|---|---|
| Ownership | architecture, cluster, node | what belongs where |
| Topology | connect, link | possible directed paths and neutral visual relationships |
| Runtime story | message, route, hotpath | what one persistent message actually does |
| Aggregate activity | flow | untracked traffic over one path or a path group |
request is an HTTP-friendly alias of message. Provider services are data,
not vocabulary: use "aws:lambda", not a separate Lambda constructor.
Provider names are visual metadata only. Manic does not infer balancing,
queueing, broadcasting, retries, or any other behavior from an icon or label.
Use explicit route/travel for one authored journey, seq for authored order,
and par plus multiple objects when several journeys should happen together.
hotpath is only an optional seeded walk over declared graph geometry; it is
not a simulation of the services shown.
It just fits — no coordinates
Geometry is optional. Write architecture(id) with no center or size and the
diagram fills the canvas (leaving the title and caption bands clear), centres
itself, and lays out every cluster and node for you:
architecture(platform); // that's the whole canvas declaration
Then add as much as the story needs. Leaf clusters with many children wrap into a grid, top-level tiers wrap into rows, and when the packed content would still overflow the frame, the whole diagram scales down as one — cards, icons, labels, and every connection lane together — to a legible minimum. Nothing clips off-canvas, and you never touch a coordinate, split a cluster into columns, or shrink a font to make it fit.
This example declares Route 53, an edge load balancer, a gateway, three availability zones of services, a replicated database cluster, and monitoring — all with zero geometry. Add a tier or a zone and it simply re-fits:
title("A Microservices Platform — Laid Out Automatically");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.055), "DIAGRAMS · AUTO-FIT · NO COORDINATES");
text(headline, (cx, h*0.12), "Add a tier. It re-fits itself.");
text(caption, (cx, h*0.95), "DNS, load balancing, three AZs of services, a database cluster, and monitoring — all auto-laid.");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 26*u); bold(headline); wrap(headline, w*0.86);
size(caption, 17*u); color(caption, dim); wrap(caption, w*0.86);
// No geometry: the diagram auto-fits. Route 53, an edge load balancer, three
// availability zones, a replicated database cluster and monitoring all reflow
// to fit — and when the content would overflow, the whole diagram scales down
// as one. The author types no coordinate.
architecture(platform);
node(dns, platform, "aws:route53", "route 53");
node(lb, platform, "aws:elb", "load balancer");
node(gateway, platform, "aws:api-gateway", "gateway");
cluster(region, platform, "REGION us-east-1");
cluster(azA, region, "AZ-A");
node(a1, azA, "aws:ecs", "svc"); node(a2, azA, "aws:ecs", "svc"); node(a3, azA, "aws:ecs", "svc");
cluster(azB, region, "AZ-B");
node(b1, azB, "aws:ecs", "svc"); node(b2, azB, "aws:ecs", "svc"); node(b3, azB, "aws:ecs", "svc");
cluster(azC, region, "AZ-C");
node(c1, azC, "aws:ecs", "svc"); node(c2, azC, "aws:ecs", "svc"); node(c3, azC, "aws:ecs", "svc");
cluster(database, platform, "DATABASE CLUSTER");
node(primary, database, "aws:rds", "primary");
node(replica1, database, "aws:rds", "replica");
node(replica2, database, "aws:rds", "replica");
node(cache, database, "aws:elasticache", "cache");
cluster(observability, platform, "MONITORING");
node(metrics, observability, "aws:cloudwatch", "metrics");
node(logs, observability, "aws:cloudwatch", "logs");
node(alarms, observability, "aws:cloudwatch", "alarms");
connect(dnsToLb, dns, lb); // resolve, then hit the edge load balancer
connect(lbToGw, lb, gateway); // balanced across the API gateway
connect(toA, gateway, azA); // gateway fans out to each zone's services
connect(toB, gateway, azB);
connect(toC, gateway, azC);
connect(toWrite, a1, primary); // a representative service writes the primary
connect(toRead, a1, replica1); // reads served from a replica
connect(toCache, a1, cache);
connect(repl1, primary, replica1); // primary replicates to both replicas
connect(repl2, primary, replica2);
connect(toMetrics, b1, metrics); // services emit telemetry to monitoring
color(dnsToLb, cyan); color(dnsToLb.hot, cyan); // request path
color(lbToGw, cyan); color(lbToGw.hot, cyan);
color(toA, cyan); color(toA.hot, cyan);
color(toB, cyan); color(toB.hot, cyan);
color(toC, cyan); color(toC.hot, cyan);
color(toRead, cyan); color(toRead.hot, cyan);
color(toCache, cyan); color(toCache.hot, cyan);
color(toWrite, magenta); color(toWrite.hot, magenta); // write path
color(repl1, lime); color(repl1.hot, lime); // replication
color(repl2, lime); color(repl2.hot, lime);
color(toMetrics, gold); color(toMetrics.hot, gold); // telemetry
request(call, dns, "GET /order");
hidden(platform.nodes);
hidden(region.parts); hidden(azA.parts); hidden(azB.parts); hidden(azC.parts);
hidden(database.parts); hidden(observability.parts);
hidden(call.parts);
untraced(dnsToLb); untraced(lbToGw);
untraced(toA); untraced(toB); untraced(toC);
untraced(toWrite); untraced(toRead); untraced(toCache);
untraced(repl1); untraced(repl2); untraced(toMetrics);
step("edge") {
par {
stagger(0.08) { show(dns, 0.4); show(lb, 0.4); show(gateway, 0.4); }
say(caption, "Route 53 resolves the name, an edge load balancer spreads the traffic, the gateway takes it from there.", 0.4);
}
}
wait(0.2);
step("reveal-zones") {
par {
show(region.parts, 0.4);
show(azA.parts, 0.4); stagger(0.05) { show(a1,0.3); show(a2,0.3); show(a3,0.3); }
show(azB.parts, 0.4); stagger(0.05) { show(b1,0.3); show(b2,0.3); show(b3,0.3); }
show(azC.parts, 0.4); stagger(0.05) { show(c1,0.3); show(c2,0.3); show(c3,0.3); }
say(caption, "Three availability zones, each with its own services — nested clusters, auto-laid.", 0.4);
}
}
wait(0.2);
step("state-and-monitoring") {
par {
show(database.parts, 0.4); show(primary, 0.4); show(replica1, 0.4); show(replica2, 0.4); show(cache, 0.4);
show(observability.parts, 0.4); show(metrics, 0.4); show(logs, 0.4); show(alarms, 0.4);
say(caption, "A replicated database cluster and monitoring join in — the whole diagram re-fits.", 0.4);
}
}
wait(0.25);
step("topology") {
par {
stagger(0.06) {
draw(dnsToLb,0.4); draw(lbToGw,0.4);
draw(toA,0.4); draw(toB,0.4); draw(toC,0.4);
draw(toWrite,0.4); draw(toRead,0.4); draw(toCache,0.4);
draw(repl1,0.4); draw(repl2,0.4); draw(toMetrics,0.4);
}
say(caption, "Paths coloured by relationship: request cyan, writes magenta, replication lime, telemetry gold.", 0.4);
}
}
wait(0.3);
step("one-request") {
par {
seq {
show(call.parts, 0.2);
route(call, dnsToLb, 0.7, smooth);
route(call, lbToGw, 0.7, smooth);
route(call, toA, 0.7, smooth);
route(call, toWrite, 0.7, smooth);
}
say(caption, "One order: Route 53 → load balancer → gateway → an AZ-A service → the primary database.", 0.4);
}
}
wait(0.3);
step("takeaway") {
par {
pulse(region.parts, 0.7);
pulse(database.parts, 0.7);
say(caption, "Add a tier, a zone, a replica — you never touch a coordinate. That is manic.", 0.45);
}
}
wait(1.4);
Pass explicit (center, width, height) only when you deliberately want a
hand-placed diagram; it is never required to make one fit.
Flowcharts — a diagram that runs
An architecture lays out regions and clusters; a flowchart ranks its nodes by
the edges between them (like Mermaid’s graph TD/LR) and — this is manic’s edge
— it runs: a token walks the process and takes a branch. Just declare
flowchart(id): like an architecture it auto-fits and needs no coordinates. It
lays the flow top-down and, when the flow is long, wraps it into side-by-side
columns — the count chosen to fill the frame — so every node stays full-size and
readable: you read down one column, then across to the top of the next, like a
multi-column diagram on paper. Forward edges are clean elbows; a long feedback loop
routes neatly around the bottom-left perimeter instead of cutting across the middle.
Pass flowchart(id, LR) to force a single left-to-right row instead.
A flowchart is only useful if you can read its nodes, so there is a readability
limit — 26 nodes top-down, 14 left-right — and past it (more than even column
wrapping keeps legible) the editor warns you to split the process into linked
sub-flows: end one chart with a connector node that hands off to the next.
Raise the limit with a third argument, flowchart(id, dir, N), when you
deliberately want a denser chart.
Every node is the same node(id, parent, "kind", "label") you already know — only
now the kind is a shape: terminator (a start/end pill), process (a step
rectangle), decision (a diamond), io (a parallelogram), subprocess, or
connector (a small circle). The shape is the node’s body, with the label
centred inside — nothing new to learn beyond the shape names.
Connect the nodes; inside a flowchart the edges default to clean orthogonal elbow
connectors along the rank direction. Give a decision’s branches captions with
annotate(edge, "yes"), colour them by meaning, and route a token from the
start terminator to watch the algorithm execute. A loop is just an edge back to an
earlier node (give it explicit ports so it routes up the side):
title("A Flowchart That Runs — the Factorial Loop");
canvas("16:9");
template("paper");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.06), "DIAGRAMS · FLOWCHART · AUTO-LAID");
text(headline, (cx, h*0.12), "A flowchart that runs");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 26*u); bold(headline); wrap(headline, w*0.86);
// `flowchart(fc)` ranks nodes by their connections, auto-fits, and auto-orients:
// a shallow flow like this stays top-down on its own. No coordinates, no direction
// to choose. Node shapes are string kinds.
flowchart(fc);
node(start, fc, "terminator", "start");
node(rd, fc, "io", "read n");
node(init, fc, "process", "f=1 i=1");
node(dec, fc, "decision", "i <= n?");
node(body, fc, "process", "f=f*i i=i+1");
node(out, fc, "io", "print f");
node(fin, fc, "terminator", "end");
connect(e1, start, rd);
connect(e2, rd, init);
connect(e3, init, dec);
connect(e4, dec, body); // keep looping
connect(e5, dec, out); // exit
connect(e6, body, dec, orthogonal, right, right); // loop back up the side
connect(e7, out, fin);
color(e4, cyan); color(e4.hot, cyan);
color(e5, lime); color(e5.hot, lime);
color(e6, gold); color(e6.hot, gold);
annotate(e4, "yes"); annotate(e5, "no"); annotate(e6, "loop");
// A colour key stands in for narration — the diagram explains itself.
text(k1, (w*0.34, h*0.93), "— continue"); size(k1, 14*u); color(k1, cyan); bold(k1);
text(k2, (w*0.50, h*0.93), "— exit"); size(k2, 14*u); color(k2, lime); bold(k2);
text(k3, (w*0.66, h*0.93), "— loop back"); size(k3, 14*u); color(k3, gold); bold(k3);
request(tok, start, "n = 3");
hidden(fc.nodes);
hidden(tok.parts);
step("shape") {
stagger(0.07) {
show(start, 0.3); show(rd, 0.3); show(init, 0.3); show(dec, 0.3);
show(body, 0.3); show(out, 0.3); show(fin, 0.3);
}
}
wait(0.2);
step("run") {
seq {
show(tok.parts, 0.2);
route(tok, e1, 0.4, smooth); route(tok, e2, 0.4, smooth); route(tok, e3, 0.4, smooth);
route(tok, e4, 0.4, smooth); route(tok, e6, 0.5, smooth);
route(tok, e4, 0.4, smooth); route(tok, e6, 0.5, smooth);
route(tok, e4, 0.4, smooth); route(tok, e6, 0.5, smooth);
route(tok, e5, 0.4, smooth); route(tok, e7, 0.4, smooth);
}
}
wait(0.3);
step("takeaway") {
seq { pulse(fin, 0.7); pulse(fin, 0.7); }
}
wait(1.0);
Start without a cloud provider
The foundation includes native visual archetypes for client, service,
gateway, database, cache, queue, storage, and external. These names
select a compact visual only; they still imply no runtime behavior:
architecture(platform, (cx,cy), w*0.88, h*0.58);
node(user, platform, "client", "User");
node(edge, platform, "gateway", "Gateway");
node(api, platform, "service", "API Service");
node(db, platform, "database", "Database");
Use provider names only when recognizable artwork helps the lesson. A generic diagram remains fully portable and needs no image assets.
The complete foundation story sends one request forward and returns the same identity over separately directed paths:
title("Systems Foundation — One Request, One Return");
canvas("16:9");
template("mono");
watermark(mark, (160, 54), "Made With Manic");
text(kicker, (cx, 54), "PROVIDER-NEUTRAL SYSTEMS");
text(headline, (cx, 104), "Structure is not behaviour");
text(caption, (cx, h-58), "Cold paths show what is possible. One persistent dot shows what actually happened.");
size(kicker, 18); color(kicker, dim); bold(kicker);
size(headline, 34); bold(headline);
size(caption, 20); color(caption, dim); wrap(caption, w*0.86);
architecture(platform, (cx, cy+15), w*0.88, h*0.58);
node(user, platform, "client", "User");
node(edge, platform, "gateway", "Gateway");
node(api, platform, "service", "API Service");
node(cache, platform, "cache", "Cache");
node(db, platform, "database", "Database");
connect(toEdge, user, edge);
connect(toApi, edge, api);
connect(toCache, api, cache);
// One semantic connection: Manic chooses node-boundary ports and keeps the
// packet continuous across every internal orthogonal segment.
connect(toDb, cache, db, orthogonal, right, top);
connect(dbReturn, db, api, 95);
connect(apiReturn, api, edge, 75);
connect(edgeReturn, edge, user, 55);
message(packet, user, "GET");
hidden(kicker); hidden(headline); hidden(caption);
untraced(platform.connections);
hidden(packet);
step("structure") {
par {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.45);
draw(platform.connections, 1.20, smooth);
}
}
wait(0.45);
step("continuous-round-trip") {
par {
show(packet, 0.20);
say(caption, "One dot moves continuously end to end. Every route hand-off preserves its identity.", 0.35);
seq {
route(packet, toEdge, 0.70, linear);
route(packet, toApi, 0.70, linear);
route(packet, toCache, 0.70, linear);
route(packet, toDb, 0.70, linear);
par {
recolor(packet, magenta, 0.30);
route(packet, dbReturn, 0.80, linear);
}
route(packet, apiReturn, 0.75, linear);
route(packet, edgeReturn, 0.75, linear);
}
}
}
wait(0.45);
step("proof") {
par {
pulse(packet, 0.70);
say(caption, "The same dot is back at the User: one identity, seven connected journeys, zero animation gaps.", 0.40);
}
}
wait(1.10);
Declare the parent first
A node’s second argument is its parent architecture or cluster. Clusters may themselves belong to an earlier cluster, giving hierarchy without member lists or special block syntax:
architecture(events, (cx,cy), w*0.9, h*0.7);
node(source, events, "aws:eks", "K8s Source");
cluster(flows, events, "EVENT FLOWS");
cluster(workers, flows, "EVENT WORKERS");
node(worker1, workers, "aws:ecs", "Worker 1");
node(worker2, workers, "aws:ecs", "Worker 2");
node(worker3, workers, "aws:ecs", "Worker 3");
node(queue, flows, "aws:sqs", "Event Queue");
cluster(processing, flows, "PROCESSING");
node(proc1, processing, "aws:lambda", "Processor 1");
node(proc2, processing, "aws:lambda", "Processor 2");
node(proc3, processing, "aws:lambda", "Processor 3");
node(store, events, "aws:s3", "Events Store");
node(analytics, events, "aws:redshift", "Analytics");
Landscape uses a horizontal system flow and stacks replicated members inside their clusters. Portrait rotates the main flow and places replicated members across the available width. Cluster frames resize from their descendants.
Cold topology, then hot behaviour
connect(toWorkers, source, workers);
connect(toQueue, workers, queue);
connect(toProcessors, queue, processing);
connect(toStore, processing, store);
message(event, source, "EVENT");
step("process") {
seq {
route(event, toWorkers, 0.8, smooth);
route(event, toQueue, 0.8, smooth);
route(event, toProcessors, 0.8, smooth);
}
}
Connecting a node to a cluster creates the possible lane to every member. Connections are dashed by default: they explain a relationship without falsely claiming that data is flowing. The hot overlay stays solid when activity begins.
The declared connection name addresses the complete fan-out or fan-in group, so
draw(toWorkers) and flow(toWorkers, ...) affect every physical lane. route
still selects only the lane beginning at the message’s current node.
route is the explicit form. It chooses the physical lane that begins at the
message’s current node, illuminates that lane, moves the same message identity,
and records its semantic destination. A later route that does not start there
fails during manic check instead of teleporting.
Use draw(toWorkers) to reveal every possible lane. Use route for one real
message. Use flow(toWorkers, ...) only when the idea is aggregate activity
across the worker pool.
Route around a visual obstacle
Connections remain direct unless the creator supplies a signed bend in canvas units:
connect(toDatabase, services, database);
connect(toCache, services, cache, 145*u);
The optional fourth argument changes only the drawn curve and the route that travels over it. Positive and negative values curve on opposite sides. It does not infer obstacles, architecture semantics, or provider behavior; use the smallest bend that keeps the diagram readable on every target canvas.
Use one orthogonal connector
For architecture diagrams that need right-angle routing, keep the connection as one semantic identity:
connect(toDatabase, cache, database, orthogonal);
connect(returnPath, database, api, orthogonal, bottom, right);
message(packet, cache, "GET");
route(packet, toDatabase, 0.9, linear);
orthogonal uses automatic node-boundary ports by default. Add explicit source
and destination ports only when the composition needs them: left, right,
top, or bottom. The engine builds the internal elbows, but draw, flow,
route, and hotpath still address the declared connection id. One message
moves across every segment without replacement objects or animation gaps.
This is deterministic Manhattan geometry, not obstacle avoidance. Manic does not inspect service kinds or guess what the connection means.
The connection & arrow grammar
systems-arrow-patterns.manic is the visual reference for every way one
connection can be drawn. All six share one rule: the connection describes a
possible relationship; direction and meaning come from the moving object
(route/flow) and from creator-chosen colour, never from the arrowhead
alone.
| # | Pattern | How to author it |
|---|---|---|
| 1 | One-way — one source, one destination | connect(a2b, a, b) |
| 2 | Round-trip — request and response as separate, honest lanes | connect(fwd, a, b) + connect(ret, b, a) (style/colour them differently) |
| 3 | Orthogonal — right-angle Manhattan routing, one identity | connect(a2b, a, b, orthogonal) |
| 4 | Vertical ports — the lane enters/leaves a chosen face | connect(a2b, a, b, orthogonal, top, bottom) |
| 5 | Fan-out — N explicitly authored deliveries (not inferred broadcast) | connect(toW1, q, w1) … connect(toW3, q, w3), or one connect(toWorkers, q, workers) to a cluster |
| 6 | Diagonal duplex — two directions with distinct styling | two connects + per-lane color/hue/dashed |
Colour the lanes by relationship so a dense diagram reads at a glance — each
connection exposes the cold line id and the hot overlay id.hot:
color(fwd, cyan); color(fwd.hot, cyan); // request
hue(ret, 328); hue(ret.hot, 328); // response (pink)
Nothing here implies balancing, buffering, or broadcast. A fan-out is possible lanes; the message that moves — and the colour you give each lane — is what tells the runtime story.
Translate a Mingrammer diagram
The structural mapping is deliberately small:
| Mingrammer | Manic |
|---|---|
Diagram(...) | architecture(...) |
Cluster(...) | cluster(...) |
ECS(...), RDS(...), and other provider objects | node(..., "aws:kind", ...) |
a >> b | connect(path, a, b) |
a - b | link(path, a.card, b.card) |
| runtime behavior | explicit message, route, travel, or flow |
This keeps the imported design faithful without turning labels such as “lb,”
“queue,” or “topic” into hidden behavior. The creator decides which object
moves, which path it takes, and whether several motions use seq or par.
The clustered web-services example recreates Route 53, ELB, three ECS services, an RDS primary/read-only pair, and ElastiCache. It then distinguishes all cold relationships from one database round trip, one cache round trip, and a separately authored database-link pulse. Return journeys use separately directed connections and recolour the same persistent identity rather than playing the incoming path backward. The example gives responses a second visual grammar—curved pink dashed arrows, a heavier glow, and a matching response identity—while requests remain clean solid strokes:
title("Clustered Web Services on AWS");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.025), "Made With Manic");
text(kicker, (cx, h*0.055), "AWS ARCHITECTURE · CLUSTERED WEB SERVICES");
text(headline, (cx, h*0.115), "Requests in. Responses back.");
text(caption, (cx, h*0.935), "Dashed paths describe what is connected—not what is moving now.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);
if h > 1.25*w {
architecture(clusteredWeb, (cx, h*0.51), w*0.84, h*0.70);
text(requestKey, (w*0.36, h*0.225), "REQUEST");
arrow(requestSample, (w*0.48, h*0.225), (w*0.64, h*0.225));
text(responseKey, (w*0.36, h*0.265), "RESPONSE");
arrow(responseSample, (w*0.48, h*0.265), (w*0.64, h*0.265));
}
else {
architecture(clusteredWeb, (cx, h*0.51), w*0.94, h*0.69);
text(requestKey, (w*0.105, h*0.205), "REQUEST");
arrow(requestSample, (w*0.15, h*0.205), (w*0.205, h*0.205));
text(responseKey, (w*0.105, h*0.245), "RESPONSE");
arrow(responseSample, (w*0.15, h*0.245), (w*0.205, h*0.245));
}
size(requestKey, 15*u); color(requestKey, fg); bold(requestKey);
size(responseKey, 15*u); hue(responseKey, 328, 0.90, 0.62); bold(responseKey);
color(requestSample, fg); stroke(requestSample, 3*u);
hue(responseSample, 328, 0.90, 0.62); dashed(responseSample, 10*u, 7*u);
stroke(responseSample, 4*u); glow(responseSample, 0.85);
hidden(requestKey); hidden(requestSample);
hidden(responseKey); hidden(responseSample);
node(dns, clusteredWeb, "aws:route53", "dns");
node(lb, clusteredWeb, "aws:elb", "lb");
cluster(services, clusteredWeb, "SERVICES");
node(web1, services, "aws:ecs", "web1");
node(web2, services, "aws:ecs", "web2");
node(web3, services, "aws:ecs", "web3");
cluster(database, clusteredWeb, "DB CLUSTER");
node(dbPrimary, database, "aws:rds", "userdb");
node(dbReplica, database, "aws:rds", "userdb ro");
node(memcached, clusteredWeb, "aws:elasticache", "memcached");
connect(dnsToLb, dns, lb);
connect(lbToServices, lb, services);
connect(servicesToDb, services, dbPrimary);
connect(servicesToCache, services, memcached, 145*u);
connect(dbToServices, dbPrimary, services, 42*u);
connect(servicesToLb, services, lb, 42*u);
connect(cacheToWeb2, memcached, web2, 145*u);
hue(dbToServices.hot, 328, 0.90, 0.62);
hue(servicesToLb.hot, 328, 0.90, 0.62);
hue(cacheToWeb2.hot, 328, 0.90, 0.62);
dashed(dbToServices.hot, 10*u, 7*u);
dashed(servicesToLb.hot, 10*u, 7*u);
dashed(cacheToWeb2.hot, 10*u, 7*u);
stroke(dbToServices.hot, 4.5*u); glow(dbToServices.hot, 0.95);
stroke(servicesToLb.hot, 4.5*u); glow(servicesToLb.hot, 0.95);
stroke(cacheToWeb2.hot, 4.5*u); glow(cacheToWeb2.hot, 0.95);
link(replication, dbPrimary.card, dbReplica.card);
dashed(replication); color(replication, dim); stroke(replication, 2.5*u);
message(pageRequest, dns, "GET");
message(cacheRead, web2, "READ");
hidden(clusteredWeb.nodes);
hidden(services.parts); hidden(database.parts);
hidden(pageRequest.parts); hidden(cacheRead.parts);
untraced(dnsToLb); untraced(lbToServices);
untraced(servicesToDb); untraced(servicesToCache);
untraced(dbToServices); untraced(servicesToLb); untraced(cacheToWeb2);
untraced(replication);
step("entry") {
par {
show(dns, 0.40);
show(lb, 0.40);
say(caption, "Route 53 and the load balancer form the public entry into the design.", 0.40);
}
}
wait(0.20);
step("service-pool") {
par {
show(services.parts, 0.45);
stagger(0.10) {
show(web1, 0.35);
show(web2, 0.35);
show(web3, 0.35);
}
say(caption, "Three ECS services share one ownership boundary; no runtime choice is implied yet.", 0.40);
}
}
wait(0.20);
step("state") {
par {
show(database.parts, 0.45);
show(dbPrimary, 0.40);
show(dbReplica, 0.40);
show(memcached, 0.40);
say(caption, "The database cluster and cache complete the static architecture.", 0.40);
}
}
wait(0.20);
step("possible-topology") {
par {
draw(dnsToLb, 0.40);
draw(lbToServices, 0.60);
draw(servicesToDb, 0.60);
draw(servicesToCache, 0.60);
draw(replication, 0.45);
show(requestKey, 0.35); show(requestSample, 0.35);
show(responseKey, 0.35); show(responseSample, 0.35);
say(caption, "Every dashed lane is possible. The next moving object will identify the path actually used.", 0.40);
}
}
wait(0.30);
step("request-to-database") {
par {
seq {
show(pageRequest.parts, 0.20);
route(pageRequest, dnsToLb, 0.65, smooth);
route(pageRequest, lbToServices, 0.90, smooth);
route(pageRequest, servicesToDb, 0.90, smooth);
}
say(caption, "This request follows one authored lane: dns → lb → web1 → userdb.", 0.40);
}
}
wait(0.35);
step("database-response") {
par {
seq {
par {
say(pageRequest.label, "200 OK", 0.25);
to(pageRequest.parts, hue, 328, 0.25, smooth);
}
route(pageRequest, dbToServices, 0.90, smooth);
route(pageRequest, servicesToLb, 0.90, smooth);
}
say(caption, "The same identity returns userdb → web1 → lb. DNS resolved the name; it is not the HTTP response path.", 0.40);
}
}
wait(0.35);
step("cache-round-trip") {
par {
seq {
show(cacheRead.parts, 0.20);
route(cacheRead, servicesToCache, 1.05, smooth);
par {
say(cacheRead.label, "HIT", 0.25);
to(cacheRead.parts, hue, 328, 0.25, smooth);
}
route(cacheRead, cacheToWeb2, 1.05, smooth);
}
say(caption, "A separate cache read travels web2 → memcached, becomes HIT, and returns to that same service.", 0.40);
}
}
wait(0.35);
step("replica-relationship") {
par {
flow(replication, 3.20, both, continuous);
say(caption, "The neutral database link can carry a two-way visual pulse only because the creator asked for it.", 0.40);
}
}
wait(0.35);
step("takeaway") {
par {
pulse(services.parts, 0.70);
pulse(database.parts, 0.70);
say(caption, "Architecture stays readable; objects, paths, timing, and composition tell the runtime story.", 0.45);
}
}
wait(1.40);
Infer one complete hot path
For the common architecture-explainer shot, Manic can follow the graph itself:
message(event, source, "EVENT");
step("runtime") {
hotpath(event, 6.0, 27);
}
hotpath(message, [duration], [seed]) begins at the message’s current node,
finds valid outgoing physical lanes, chooses one at every fan-out, and keeps the
same dot moving until it reaches a sink. Only the selected lanes illuminate;
all other relationships stay dashed and quiet.
The optional seed controls the branch choices. The result feels random to a
viewer but is deterministic across previews, backend renders, and WASM. Change
the seed to show another valid execution without rewriting the story. Use
route when the exact service sequence is part of the lesson; use hotpath
when the lesson is “one possible runtime through this topology.”
Directional and continuous flow
flow(path, 0.8); // forward, one clean pulse
flow(path, 4.0, forward, continuous); // finite repeating stream
flow(returnPath, 1.0, reverse, once); // reverse pulse on generic geometry
flow(syncPath, 4.0, both, continuous); // independent duplex streams
The full signature is:
flow(path, [duration], [forward|reverse|both], [once|continuous])
Continuous flow chooses an integer number of length-aware cycles. It begins empty and ends on a cycle boundary, so seeking remains deterministic and the stream drains instead of freezing halfway along the connection. In a Systems story, prefer a separately directed return connection when the topology truly contains a response path.
Complete event-processing story
title("Event Processing on AWS — From Topology to Hot Path");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.055), "AWS ARCHITECTURE · EVENT PROCESSING");
text(headline, (cx, h*0.115), "One topology. One event. One visible hot path.");
text(caption, (cx, h*0.935), "Dashed relationships show every route the architecture allows.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);
if h > 1.25*w {
architecture(eventProcessing, (cx, h*0.51), w*0.82, h*0.70);
}
else {
architecture(eventProcessing, (cx, h*0.51), w*0.92, h*0.69);
}
node(source, eventProcessing, "aws:eks", "K8s Source");
cluster(flows, eventProcessing, "EVENT FLOWS");
cluster(workers, flows, "EVENT WORKERS");
node(worker1, workers, "aws:ecs", "Worker 1");
node(worker2, workers, "aws:ecs", "Worker 2");
node(worker3, workers, "aws:ecs", "Worker 3");
node(queue, flows, "aws:sqs", "Event Queue");
cluster(processing, flows, "PROCESSING");
node(proc1, processing, "aws:lambda", "Processor 1");
node(proc2, processing, "aws:lambda", "Processor 2");
node(proc3, processing, "aws:lambda", "Processor 3");
node(store, eventProcessing, "aws:s3", "Events Store");
node(warehouse, eventProcessing, "aws:redshift", "Analytics");
connect(toWorkers, source, workers);
connect(toQueue, workers, queue);
connect(toHandlers, queue, processing);
connect(toStore, processing, store);
connect(toAnalytics, processing, warehouse);
message(event, source, "EVENT");
hidden(eventProcessing.nodes);
hidden(flows.parts); hidden(workers.parts); hidden(processing.parts);
hidden(event.parts);
untraced(toWorkers); untraced(toQueue); untraced(toHandlers);
untraced(toStore); untraced(toAnalytics);
step("source") {
par {
show(source, 0.45);
say(caption, "EKS is the source; the rest of the system is still only structure.", 0.40);
}
}
wait(0.25);
step("ownership") {
par {
show(flows.parts, 0.50);
show(workers.parts, 0.45);
stagger(0.10) { show(worker1, 0.35); show(worker2, 0.35); show(worker3, 0.35); }
show(queue, 0.40);
show(processing.parts, 0.45);
stagger(0.10) { show(proc1, 0.35); show(proc2, 0.35); show(proc3, 0.35); }
show(store, 0.40); show(warehouse, 0.40);
say(caption, "Nested boundaries reveal ownership without hiding the individual services.", 0.40);
}
}
wait(0.20);
step("possible-topology") {
par {
draw(toWorkers, 0.55);
draw(toQueue, 0.55);
draw(toHandlers, 0.55);
draw(toStore, 0.55); draw(toAnalytics, 0.55);
say(caption, "Every dashed edge is a possible relationship—not a claim that data is flowing.", 0.40);
}
}
wait(0.35);
step("runtime-hot-path") {
par {
show(event.parts, 0.25);
hotpath(event, 6.20, 27);
say(caption, "The event now chooses valid branches and travels end-to-end as one continuous hot path.", 0.40);
}
}
wait(0.35);
step("takeaway") {
par {
pulse(event.parts, 0.70);
say(caption, "Cold topology explains relationships. The moving dot proves what happened now.", 0.45);
}
}
wait(1.40);
Run and audit it directly:
manic examples/aws-event-processing-clusters-poc.manic
manic check examples/aws-event-processing-clusters-poc.manic --canvas all
What the kit will not do for you
By design, the kit never infers behaviour from an icon or a label. It will not
decide that a node is a load balancer, a queue, or a database, and it never
simulates balancing, buffering, broadcast, retries, or failure. You author what
happens with explicit route, flow, seq, par, colour, and ordinary verbs
— that is what keeps the vocabulary small and the diagram honest.
Two practical tips:
- Draw the return path, don’t reverse the arrow. A response is its own connection with its own colour, not the request lane played backward.
- For very dense diagrams, shape the layout yourself. Group nodes into clusters and split wide rows into columns so nothing overflows the frame.
Diagrams — icon reference & aliases
Every diagram node takes a provider:name string that selects artwork only — it never implies routing, balancing, queueing, or persistence. The catalogue covers 17 providers / 2461 components, generated from assets/diagrams/ by scripts/gen-diagram-manifest.py (Mingrammer/Diagrams icon set, disk-loaded at render — no binary bloat).
Reference notation
provider:name— e.g.node(x, arch, "aws:lambda", "fn"),"gcp:bigquery","onprem:redis","k8s:pod". First matching category wins for the bare name.provider:category/name— disambiguates when the same name lives in two categories of one provider, e.g."aws:network/internet-gateway"vs"aws:general/internet-gateway".- Native archetypes need no provider:
client,service,gateway,database,cache,queue,storage,external(no cloud assets).
Friendly aliases
Short, human names resolve to the exact catalogue key — same artwork either way:
| Alias | Resolves to |
|---|---|
aws:apigateway | aws:api-gateway |
aws:route53 | aws:route-53 |
aws:elb | aws:elastic-load-balancing |
aws:load-balancer | aws:elastic-load-balancing |
aws:sqs | aws:simple-queue-service-sqs |
aws:s3 | aws:simple-storage-service-s3-bucket |
aws:ecs | aws:elastic-container-service |
aws:eks | aws:elastic-kubernetes-service |
Everything else passes through unchanged: aws:lambda, gcp:*, azure:*, onprem:*, k8s:*, ibm:*, oci:*, … all resolve directly.
Colouring paths by relationship
A connect(id, …) exposes two colourable parts — the cold dashed line id and the hot overlay id.hot. Group connections by relationship and colour each group (color/hue/glow/dashed/stroke) so a dense diagram reads at a glance:
color(reqPath, cyan); color(reqPath.hot, cyan); // REQUEST
color(logPath, gold); color(logPath.hot, gold); // ANALYTICS
hue(scrape, 328); hue(scrape.hot, 328); // TELEMETRY (pink)
Providers & components
Any listed name works as provider:name. Full machine-readable catalogue: assets/diagrams-manifest.json.
alibabacloud — 92 components
alibabacloud:alibabacloud, alibabacloud:analytic-db, alibabacloud:anti-bot-service, alibabacloud:anti-ddos-basic, alibabacloud:anti-ddos-pro, alibabacloud:antifraud-service, alibabacloud:api-gateway, alibabacloud:apsaradb-cassandra, alibabacloud:apsaradb-hbase, alibabacloud:apsaradb-memcache, alibabacloud:apsaradb-mongodb, alibabacloud:apsaradb-oceanbase, alibabacloud:apsaradb-polardb, alibabacloud:apsaradb-postgresql … (+78 more)
aws — 513 components
aws:ad-connector, aws:alexa-for-business, aws:amazon-devops-guru, aws:amazon-managed-grafana, aws:amazon-managed-prometheus, aws:amazon-managed-workflows-apache-airflow, aws:amazon-opensearch-service, aws:amplify, aws:analytics, aws:apache-mxnet-on-aws, aws:api-gateway, aws:api-gateway-endpoint, aws:app-mesh, aws:app-runner … (+499 more)
azure — 690 components
azure:aad-licenses, azure:abs-member, azure:access-review, azure:active-directory, azure:active-directory-connect-health, azure:activity-log, azure:ad-b2c, azure:ad-domain-services, azure:ad-identity-protection, azure:ad-privileged-identity-management, azure:administrative-units, azure:advisor, azure:ai-studio, azure:aks-istio … (+676 more)
digitalocean — 26 components
digitalocean:certificate, digitalocean:containers, digitalocean:dbaas-primary, digitalocean:dbaas-primary-standby-more, digitalocean:dbaas-read-only, digitalocean:dbaas-standby, digitalocean:digitalocean, digitalocean:docker, digitalocean:domain, digitalocean:domain-registration, digitalocean:droplet, digitalocean:droplet-connect, digitalocean:droplet-snapshot, digitalocean:firewall … (+12 more)
elastic — 42 components
elastic:agent, elastic:alerting, elastic:apm, elastic:app-search, elastic:auditbeat, elastic:beats, elastic:cloud, elastic:crawler, elastic:ece, elastic:eck, elastic:elastic, elastic:elasticsearch, elastic:endpoint, elastic:enterprise-search … (+28 more)
firebase — 22 components
firebase:ab-testing, firebase:app-distribution, firebase:app-indexing, firebase:authentication, firebase:crash-reporting, firebase:crashlytics, firebase:dynamic-links, firebase:extensions, firebase:firebase, firebase:firestore, firebase:functions, firebase:hosting, firebase:in-app-messaging, firebase:invites … (+8 more)
gcp — 123 components
gcp:access-context-manager, gcp:advanced-solutions-lab, gcp:ai-hub, gcp:ai-platform, gcp:ai-platform-data-labeling-service, gcp:api-gateway, gcp:apigee, gcp:app-engine, gcp:armor, gcp:assured-workloads, gcp:automl, gcp:automl-natural-language, gcp:automl-tables, gcp:automl-translation … (+109 more)
generic — 27 components
generic:android, generic:blank, generic:centos, generic:datacenter, generic:debian, generic:firewall, generic:generic, generic:ios, generic:linux-general, generic:mobile, generic:qemu, generic:rack, generic:raspbian, generic:red-hat … (+13 more)
gis — 65 components
gis:actinia, gis:addok, gis:ban, gis:baremaps, gis:cesium, gis:deegree, gis:g3w-suite, gis:gdal, gis:geohealthcheck, gis:geomapfish, gis:geomesa, gis:geonetwork, gis:geonode, gis:geopackage … (+51 more)
ibm — 164 components
ibm:actionable-insight, ibm:alert-notification, ibm:analytics, ibm:annotate, ibm:api-developer-portal, ibm:api-management, ibm:api-polyglot-runtimes, ibm:api-security, ibm:app-server, ibm:application-logic, ibm:artifact-management, ibm:bare-metal-server, ibm:block-storage, ibm:blockchain … (+150 more)
k8s — 46 components
k8s:api, k8s:c-c-m, k8s:c-m, k8s:c-role, k8s:chaos-mesh, k8s:cm, k8s:crb, k8s:crd, k8s:cronjob, k8s:deploy, k8s:ds, k8s:ep, k8s:etcd, k8s:external-dns … (+32 more)
oci — 141 components
oci:alarm, oci:alarm-white, oci:api-gateway, oci:api-gateway-white, oci:api-service, oci:api-service-white, oci:audit, oci:audit-white, oci:autonomous, oci:autonomous-white, oci:autoscale, oci:autoscale-white, oci:backbone, oci:backbone-white … (+127 more)
onprem — 172 components
onprem:activemq, onprem:aerospike, onprem:airflow, onprem:ambassador, onprem:ansible, onprem:apache, onprem:argocd, onprem:atlantis, onprem:awx, onprem:beam, onprem:bind-9, onprem:bitwarden, onprem:boundary, onprem:buzzfeed-sso … (+158 more)
openstack — 51 components
openstack:ansible, openstack:barbican, openstack:blazar, openstack:charms, openstack:chef, openstack:cinder, openstack:cloudkitty, openstack:congress, openstack:cyborg, openstack:designate, openstack:ec2api, openstack:freezer, openstack:glance, openstack:heat … (+37 more)
outscale — 13 components
outscale:client-vpn, outscale:compute, outscale:direct-connect, outscale:firewall, outscale:identity-and-access-management, outscale:internet-service, outscale:load-balancer, outscale:nat-service, outscale:net, outscale:outscale, outscale:simple-storage-service, outscale:site-to-site-vpng, outscale:storage
programming — 74 components
programming:action, programming:angular, programming:backbone, programming:bash, programming:c, programming:camel, programming:collate, programming:cpp, programming:csharp, programming:dapr, programming:dart, programming:database, programming:decision, programming:delay … (+60 more)
saas — 40 components
saas:adyen, saas:akamai, saas:amazon-pay, saas:auth0, saas:cloudflare, saas:cloudinary, saas:crowdstrike, saas:datadog, saas:dataform, saas:discord, saas:facebook, saas:fastly, saas:imperva, saas:intercom … (+26 more)
Bundled assets — portable files without path guessing
Manic scenes can use small, documented production assets through a stable
asset: URI. The URI works from the desktop CLI, a different working
directory, the Docker image, or the production backend. No render flag is
needed.
image(mark, (cx, 260), "asset:manic-logo.png", 180, 180);
model3(beacon, "asset:models/manic-pyramid.obj", (0,0,0), 1);
assembly3(console, "asset:models/manic-console.obj", (3,0,0), 1);
svg(logo, (cx, 480), "asset:svg/heart.svg", 320);
The same image URI can be used in a Creator profile:
creator(me, "@anish2good name=Manic logo=asset:manic-logo.png footer=signature");
socials(me);
Available public assets
| Stable URI | Kind | Useful for |
|---|---|---|
asset:manic-logo.png | PNG | image(...), Creator logo=, a Manic-branded example |
asset:models/manic-pyramid.obj | Geometry-only OBJ | model3(...), a beacon, monument, marker, or placeholder model |
asset:models/manic-console.obj | Grouped geometry-only OBJ | assembly3(...), addressable base/screen/key parts, staged technical stories |
asset:svg/lucide/<name>.svg | SVG icons — Lucide (ISC) | svg(...), themeable line icons (recolour to taste) |
asset:svg/heroicons/<name>.svg | SVG icons — Heroicons (MIT) | svg(...), solid glyphs |
asset:svg/flags/<cc>.svg | SVG flags — flag-icons (MIT) | svg(...), country flags in real colours |
asset:svg/emoji/<name-or-codepoint>.svg | SVG emoji — Twemoji (CC-BY 4.0) | svg(...), flat colour emoji; fire.svg/1f525.svg |
asset:svg/heart.svg · pin.svg · robot.svg · map-region.svg | SVG demo art (CC0) | svg(...), the import demos |
asset:svg/physics/effusion-reservoir.svg | SVG scientific apparatus (CC0) | reusable thermal-reservoir/nozzle shell for process stories |
asset:svg/physics/rocket-cutaway.svg | SVG scientific apparatus (CC0) | cutaway launch vehicle for payload, propellant, combustion, nozzle, and thrust stories |
asset:svg/physics/rocket-first-stage.svg | SVG scientific apparatus (CC0) | independently movable launch booster |
asset:svg/physics/rocket-second-stage.svg | SVG scientific apparatus (CC0) | independently movable second stage |
asset:svg/physics/rocket-upper-stage.svg | SVG scientific apparatus (CC0) | payload fairing and upper stage |
The SVG sets are a curated, permissively-licensed starter pack (see
assets/svg/LICENSES.md). List what’s bundled — and what a file imports as — with
manic svgcheck assets/svg.
The catalog stays deliberately small. Do not invent an asset name that is not
listed here. Manic reports a clear error if a bundled URI is missing, and it
rejects .. traversal. OBJ files also retain the normal file-size and geometry
safety limits.
Vector import — SVG as native geometry
svg() brings vector artwork in as native path entities — not a texture. Each
subpath of the SVG becomes a real Polyline/Polygon you can draw, morph, recolour,
and animate exactly like a shape you authored by hand. It’s the 2D twin of model3’s
OBJ import.
svg(logo, (cx, cy), "asset:svg/heart.svg", 320);
Reposition — the second argument is the centre. Move it, or animate it; every piece follows because the whole drawing shares one tag:
svg(pin, (cx, cy), "asset:svg/pin.svg", 200); // centred at (cx, cy)
move(pin, (cx, cy - 200), 1.0); // glide the whole drawing up
Resize — the last argument is the width in pixels; the height follows the artwork’s own aspect ratio:
svg(small, (300, cy), "asset:svg/robot.svg", 120); // 120 px wide
svg(big, (800, cy), "asset:svg/robot.svg", 360); // same file, 3× bigger — still crisp
Recolour — imported fills reproduce faithfully (they are not re-themed by the template, even if a colour happens to match a palette name). Change them whenever you like:
recolor(logo, cyan); // whole drawing → cyan
hue(logo, 200, 0.9, 0.6); // …or by HSL
gradient(logo, gold, magenta); // …or a gradient across it
Animate — one verb drives the whole drawing (it is tagged with the id):
hidden(logo); show(logo, 0.8); // reveal
pulse(logo, 0.9); // emphasise
Address one piece — subpaths are named {id}.p0, {id}.p1, … in document
order, so you can single one out:
recolor(pin.p1, gold); // just the pin's inner hole
Layering — imported art defaults to z = 0. Raise it above another layer
(a grid, a backdrop) with z:
z(logo, 6);
What v1 imports (and what it skips)
Imported: paths plus the basic shapes (rect, circle, ellipse, line,
polygon, polyline) with their transforms resolved, and solid fill/stroke
colours. Curves are flattened to smooth polylines.
Imported gradients: linear and radial fill gradients come in faithfully (mapped
onto Manic’s gradient(), multi-stop ramps resampled) — so gradient icons,
illustrations, and emblems keep their shading.
Skipped for now: SVG <text> and embedded <image>, clipPath/mask, filters and
blurs, pattern paint, and gradient strokes (fills are supported). Keep artwork
reasonably simple — there’s a point-budget guard and the usual file-size limit.
Two things worth knowing:
currentColor— most icon sets (Lucide, Heroicons, …) paint withcurrentColor, a host-themed placeholder. Manic imports those as themeable geometry: they take the entity’s own colour, sorecolor(icon, cyan)just works. Artwork with real colours (flags, illustrations) keeps them faithfully.- Concave fills are triangulated (ear-clipping), so stars, notched glyphs, and blobs fill correctly — not just convex shapes.
Screen a set before you rely on it
Downloaded a folder of SVGs? Vet them against the importer first:
$ manic svgcheck assets/svg/lucide
✓ rocket.svg — 4 paths, 199 pts, themeable (0 with fill/stroke)
✓ map-pin.svg — 2 paths, 147 pts, themeable (0 with fill/stroke)
…
Each file reports its subpath/point count and whether it’s themeable (recolour it)
or coloured (keeps its own colours); anything empty, over-budget, or unparseable is
flagged with ✗. scripts/fetch-svg-assets.sh vendors the bundled sets the same way.
Your own images and models
Ordinary paths still work:
image(photo, (cx,cy), "uploads/my-photo.jpg", 720, 480);
model3(product, "uploads/my-product.obj", (0,0,0), 1);
svg(logo, (cx,cy), "uploads/my-logo.svg", 300);
Bring in any SVG this way — an icon you downloaded, a logo, a map outline. The
caller must make those files available to the renderer. This is the right
choice for uploads and private brand assets; asset: is for the small catalog
that ships with Manic.
Adding a new bundled asset to Manic
- Put it under
assets/in a typed folder such asmodels/, with a lowercase, descriptive filename. - Add its stable URI to this page and
assets/README.md. - Add or update a checked
.manicexample using the URI. - Keep models geometry-only; do not add scripts, arbitrary shaders, or remote dependencies.
- Run the tests and mdBook build.
The release machinery copies the entire directory. Docker installs it at
/usr/local/share/manic/assets; Linux builds produce
dist/manic-assets.tar.gz; the EC2 deploy installs that archive; and the
playground sync mirrors the same catalog. Future entries therefore need no
per-file pipeline edit. A custom deployment may point MANIC_ASSETS_DIR at a
different catalog root.
SVG assets — reference & aliases
Every svg() import takes an asset:svg/<set>/<name>.svg URI (or your own
file path). The bundled catalogue covers 6,404 shapes across four sets,
vendored by scripts/fetch-svg-assets.sh and screened with
manic svgcheck assets/svg. Licences: assets/svg/LICENSES.md.
Reference notation
svg(logo, (cx, cy), "asset:svg/lucide/rocket.svg", 300); // an icon
svg(fin, (cx, cy), "asset:svg/flags/in.svg", 220); // a flag (ISO code)
svg(joy, (cx, cy), "asset:svg/emoji/fire.svg", 140); // an emoji (alias)
svg(joy, (cx, cy), "asset:svg/emoji/1f525.svg", 140); // …same, by codepoint
| Set | Count | Naming | Licence | Imports as |
|---|---|---|---|---|
lucide/ | ~1,756 | icon name (cpu, database, map-pin, …) | ISC | themeable outline (recolour it) |
heroicons/ | ~324 | icon name (bolt, beaker, rocket-launch, …) | MIT | themeable solid glyph |
flags/ | ~271 | ISO 3166-1 alpha-2 (us, in, jp, de, …) | MIT | full colour |
emoji/ | ~4,009 | Unicode codepoint (1f525.svg) or friendly alias | CC-BY 4.0 | full colour |
Themeable vs faithful. Lucide/Heroicons paint with currentColor, so they
take the entity’s colour — recolor(icon, cyan) just works. Flags and emoji keep
their real colours. Concave shapes (stars, emblems) fill correctly, and
stroke-drawn designs (stripes, Nordic crosses) import as filled bands.
Known limitation. A few flags place their stars with SVG
<use>instancing (e.g.cn— China) or<marker>; those instanced glyphs render incorrectly for now (tracked). The flag’s plain fields still show, and all icons/emoji and most flags render cleanly.svgchecka set before you rely on it.
Emoji friendly aliases
Twemoji files are named by Unicode codepoint; these short names resolve to the same file — either works:
| Alias | Codepoint | Alias | Codepoint | Alias | Codepoint | ||
|---|---|---|---|---|---|---|---|
grinning | 1f600 | heart | 2764 | sun | 2600 | ||
smile | 1f604 | fire | 1f525 | moon | 1f319 | ||
joy | 1f602 | rocket | 1f680 | cloud | 2601 | ||
wink | 1f609 | star | 2b50 | zap | 26a1 | ||
thumbsup | 1f44d | trophy | 1f3c6 | rainbow | 1f308 | ||
thumbsdown | 1f44e | party | 1f389 | earth | 1f30d | ||
check | 2705 | brain | 1f9e0 | target | 1f3af | ||
cross | 274c | bulb | 1f4a1 | flag | 1f6a9 | ||
warning | 26a0 | eyes | 1f440 | hourglass | 231b | ||
thinking | 1f914 | clap | 1f44f | chart | 1f4c8 | ||
sob | 1f62d | wave | 1f44b | computer | 1f4bb | ||
muscle | 1f4aa | money | 1f4b0 | phone | 1f4f1 | ||
lock | 1f512 | key | 1f511 | mail | 1f4e7 | ||
bell | 1f514 | door | 1f6aa | car | 1f697 | ||
goat | 1f410 |
Any other emoji works by codepoint — e.g. asset:svg/emoji/1f680.svg (🚀). Add
your own aliases in scripts/fetch-svg-assets.sh.
Discovering the full catalogue
Browse every bundled asset: the SVG asset catalogue lists
all 6,400+ files by set — searchable with ⌘/Ctrl-F, no CLI needed. That page is
generated from the assets themselves by scripts/gen-svg-index.py, so it always
matches what ships.
If you have the CLI, svgcheck also screens a folder — and shows what each file
imports as (paths, points, themeable vs coloured):
$ manic svgcheck assets/svg/lucide
✓ rocket.svg — 4 paths, 199 pts, themeable
✓ map-pin.svg — 2 paths, 147 pts, themeable
…
$ manic svgcheck assets/svg/flags # every country flag
$ manic svgcheck assets/svg/emoji # every Twemoji
scripts/fetch-svg-assets.sh re-vendors or updates every set from its upstream
repo (Lucide · Heroicons · flag-icons · Twemoji). Bring your own:
svg(id, pos, "path/to/any.svg") imports any SVG file — vet it with svgcheck
first. See the import how-to for
positioning, resizing, recolouring, and animating.
SVG asset catalogue
Every one of the 6,408 bundled SVG vectors, by set — so you can find an asset without the CLI. Drop any of them in with:
svg(id, (x, y), "asset:svg/<set>/<name>.svg", size);
Names are searchable — use your browser’s find (⌘/Ctrl-F). Licences are per set; see assets/svg/LICENSES.md for full attribution. For a quick start with plain-English emoji names, see the friendly aliases.
| Set | Count | Licence | Path |
|---|---|---|---|
| Lucide | 1,756 | ISC | asset:svg/lucide/<name>.svg |
| Heroicons | 324 | MIT | asset:svg/heroicons/<name>.svg |
| Flags | 271 | MIT | asset:svg/flags/<name>.svg |
| Emoji | 4,052 | CC-BY 4.0 | asset:svg/emoji/<name>.svg |
| Physics | 5 | CC0 | asset:svg/physics/<name>.svg |
Lucide — clean outline icons · 1,756 · ISC
asset:svg/lucide/<name>.svg
a-arrow-down · a-arrow-up · a-large-small · accessibility · activity · ad · air-vent · airplay · alarm-clock · alarm-clock-check · alarm-clock-minus · alarm-clock-off · alarm-clock-plus · alarm-smoke · album · align-center-horizontal · align-center-vertical · align-end-horizontal · align-end-vertical · align-horizontal-distribute-center · align-horizontal-distribute-end · align-horizontal-distribute-start · align-horizontal-justify-center · align-horizontal-justify-end · align-horizontal-justify-start · align-horizontal-space-around · align-horizontal-space-between · align-start-horizontal · align-start-vertical · align-vertical-distribute-center · align-vertical-distribute-end · align-vertical-distribute-start · align-vertical-justify-center · align-vertical-justify-end · align-vertical-justify-start · align-vertical-space-around · align-vertical-space-between · ambulance · ampersand · ampersands · amphora · anchor · angry · annoyed · antenna · anvil · aperture · app-window · app-window-mac · apple · archive · archive-restore · archive-x · armchair · arrow-big-down · arrow-big-down-dash · arrow-big-left · arrow-big-left-dash · arrow-big-right · arrow-big-right-dash · arrow-big-up · arrow-big-up-dash · arrow-down · arrow-down-0-1 · arrow-down-1-0 · arrow-down-a-z · arrow-down-from-line · arrow-down-left · arrow-down-narrow-wide · arrow-down-right · arrow-down-to-dot · arrow-down-to-line · arrow-down-up · arrow-down-wide-narrow · arrow-down-z-a · arrow-left · arrow-left-from-line · arrow-left-right · arrow-left-to-line · arrow-right · arrow-right-from-line · arrow-right-left · arrow-right-to-line · arrow-up · arrow-up-0-1 · arrow-up-1-0 · arrow-up-a-z · arrow-up-down · arrow-up-from-dot · arrow-up-from-line · arrow-up-left · arrow-up-narrow-wide · arrow-up-right · arrow-up-to-line · arrow-up-wide-narrow · arrow-up-z-a · arrows-up-from-line · asterisk · astroid · at-sign · atom · audio-lines · audio-waveform · award · axe · axis-3d · baby · backpack · badge · badge-alert · badge-cent · badge-check · badge-dollar-sign · badge-euro · badge-indian-rupee · badge-info · badge-japanese-yen · badge-minus · badge-percent · badge-plus · badge-pound-sterling · badge-question-mark · badge-russian-ruble · badge-swiss-franc · badge-turkish-lira · badge-x · baggage-claim · balloon · ban · banana · bandage · banknote · banknote-arrow-down · banknote-arrow-up · banknote-check · banknote-x · barcode · barrel · baseline · bath · battery · battery-charging · battery-full · battery-low · battery-medium · battery-plus · battery-warning · beaker · bean · bean-off · bed · bed-double · bed-single · beef · beef-off · beer · beer-off · bell · bell-check · bell-dot · bell-electric · bell-minus · bell-off · bell-plus · bell-ring · between-horizontal-end · between-horizontal-start · between-vertical-end · between-vertical-start · biceps-flexed · bike · binary · binoculars · biohazard · bird · birdhouse · bitcoin · blend · blender · blinds · blocks · bluetooth · bluetooth-connected · bluetooth-off · bluetooth-searching · bold · bolt · bomb · bone · bone-fracture · book · book-a · book-alert · book-audio · book-check · book-copy · book-dashed · book-down · book-headphones · book-heart · book-image · book-key · book-lock · book-marked · book-minus · book-open · book-open-check · book-open-text · book-plus · book-search · book-text · book-type · book-up · book-up-2 · book-user · book-x · bookmark · bookmark-check · bookmark-minus · bookmark-off · bookmark-plus · bookmark-x · boom-box · bot · bot-message-square · bot-off · bottle-wine · bow-arrow · box · boxes · braces · brackets · brain · brain-circuit · brain-cog · brick-wall · brick-wall-fire · brick-wall-shield · briefcase · briefcase-business · briefcase-conveyor-belt · briefcase-medical · bring-to-front · broccoli · brush · brush-cleaning · bubbles · bug · bug-off · bug-play · building · building-2 · bus · bus-front · cable · cable-car · cake · cake-slice · calculator · calendar · calendar-1 · calendar-arrow-down · calendar-arrow-up · calendar-check · calendar-check-2 · calendar-clock · calendar-cog · calendar-days · calendar-fold · calendar-heart · calendar-minus · calendar-minus-2 · calendar-off · calendar-plus · calendar-plus-2 · calendar-range · calendar-search · calendar-sync · calendar-x · calendar-x-2 · calendars · camera · camera-off · candy · candy-cane · candy-off · cannabis · cannabis-off · captions · captions-off · car · car-front · car-taxi-front · caravan · card-sim · carrot · case-lower · case-sensitive · case-upper · cassette-tape · cast · castle · cat · cctv · cctv-off · chart-area · chart-bar · chart-bar-big · chart-bar-decreasing · chart-bar-increasing · chart-bar-stacked · chart-candlestick · chart-column · chart-column-big · chart-column-decreasing · chart-column-increasing · chart-column-stacked · chart-gantt · chart-line · chart-network · chart-no-axes-column · chart-no-axes-column-decreasing · chart-no-axes-column-increasing · chart-no-axes-combined · chart-no-axes-gantt · chart-pie · chart-scatter · chart-spline · check · check-check · check-line · chef-hat · cherry · chess-bishop · chess-king · chess-knight · chess-pawn · chess-queen · chess-rook · chevron-down · chevron-first · chevron-last · chevron-left · chevron-right · chevron-up · chevrons-down · chevrons-down-up · chevrons-left · chevrons-left-right · chevrons-left-right-ellipsis · chevrons-right · chevrons-right-left · chevrons-up · chevrons-up-down · church · cigarette · cigarette-off · circle · circle-alert · circle-arrow-down · circle-arrow-left · circle-arrow-out-down-left · circle-arrow-out-down-right · circle-arrow-out-up-left · circle-arrow-out-up-right · circle-arrow-right · circle-arrow-up · circle-check · circle-check-big · circle-chevron-down · circle-chevron-left · circle-chevron-right · circle-chevron-up · circle-dashed · circle-divide · circle-dollar-sign · circle-dot · circle-dot-dashed · circle-ellipsis · circle-equal · circle-euro · circle-fading-arrow-up · circle-fading-plus · circle-gauge · circle-minus · circle-off · circle-parking · circle-parking-off · circle-pause · circle-percent · circle-pile · circle-play · circle-plus · circle-pound-sterling · circle-power · circle-question-mark · circle-slash · circle-slash-2 · circle-small · circle-star · circle-stop · circle-user · circle-user-round · circle-x · circuit-board · citrus · clapperboard · clipboard · clipboard-check · clipboard-clock · clipboard-copy · clipboard-list · clipboard-minus · clipboard-paste · clipboard-pen · clipboard-pen-line · clipboard-plus · clipboard-type · clipboard-x · clock · clock-1 · clock-10 · clock-11 · clock-12 · clock-2 · clock-3 · clock-4 · clock-5 · clock-6 · clock-7 · clock-8 · clock-9 · clock-alert · clock-arrow-down · clock-arrow-left · clock-arrow-right · clock-arrow-up · clock-check · clock-fading · clock-plus · closed-caption · cloud · cloud-alert · cloud-backup · cloud-check · cloud-cog · cloud-download · cloud-drizzle · cloud-fog · cloud-hail · cloud-lightning · cloud-moon · cloud-moon-rain · cloud-off · cloud-rain · cloud-rain-wind · cloud-snow · cloud-sun · cloud-sun-rain · cloud-sync · cloud-upload · cloudy · clover · club · code · code-xml · coffee · cog · coins · columns-2 · columns-3 · columns-3-cog · columns-4 · combine · command · compass · component · computer · concierge-bell · cone · construction · contact · contact-round · container · contrast · cookie · cooking-pot · copy · copy-check · copy-minus · copy-plus · copy-slash · copy-x · copyleft · copyright · corner-down-left · corner-down-right · corner-left-down · corner-left-up · corner-right-down · corner-right-up · corner-up-left · corner-up-right · cpu · creative-commons · credit-card · croissant · crop · cross · crosshair · crown · cuboid · cup-soda · currency · cylinder · dam · database · database-arrow-down · database-arrow-up · database-backup · database-check · database-minus · database-plus · database-search · database-x · database-zap · decimals-arrow-left · decimals-arrow-right · delete · dessert · diameter · diamond · diamond-minus · diamond-percent · diamond-plus · dice-1 · dice-2 · dice-3 · dice-4 · dice-5 · dice-6 · dices · diff · disc · disc-2 · disc-3 · disc-album · divide · dna · dna-off · dock · dog · dollar-sign · donut · door-closed · door-closed-locked · door-open · dot · download · drafting-compass · drama · drill · drone · droplet · droplet-off · droplets · drum · drumstick · dumbbell · ear · ear-off · earth · earth-lock · eclipse · egg · egg-fried · egg-off · ellipse · ellipsis · ellipsis-vertical · equal · equal-approximately · equal-not · eraser · ethernet-port · euro · ev-charger · expand · external-link · eye · eye-closed · eye-dashed · eye-off · factory · fan · fast-forward · feather · fence · ferris-wheel · file · file-archive · file-axis-3d · file-badge · file-box · file-braces · file-braces-corner · file-chart-column · file-chart-column-increasing · file-chart-line · file-chart-pie · file-check · file-check-corner · file-clock · file-code · file-code-corner · file-cog · file-diff · file-digit · file-down · file-exclamation-point · file-headphone · file-heart · file-image · file-input · file-key · file-lock · file-minus · file-minus-corner · file-music · file-output · file-pen · file-pen-line · file-play · file-plus · file-plus-corner · file-question-mark · file-scan · file-search · file-search-corner · file-signal · file-sliders · file-spreadsheet · file-stack · file-symlink · file-terminal · file-text · file-type · file-type-corner · file-up · file-user · file-video-camera · file-volume · file-x · file-x-corner · files · film · fingerprint-pattern · fire-extinguisher · fish · fish-off · fish-symbol · fishing-hook · fishing-rod · flag · flag-off · flag-triangle-left · flag-triangle-right · flame · flame-kindling · flashlight · flashlight-off · flask-conical · flask-conical-off · flask-round · flip-horizontal-2 · flip-vertical-2 · flower · flower-2 · focus · fold-horizontal · fold-vertical · folder · folder-archive · folder-bookmark · folder-check · folder-clock · folder-closed · folder-code · folder-cog · folder-dot · folder-down · folder-git · folder-git-2 · folder-heart · folder-input · folder-kanban · folder-key · folder-lock · folder-minus · folder-open · folder-open-dot · folder-output · folder-pen · folder-plus · folder-root · folder-search · folder-search-2 · folder-symlink · folder-sync · folder-tree · folder-up · folder-x · folders · footprints · forklift · form · forward · frame · frown · fuel · fullscreen · funnel · funnel-plus · funnel-x · gallery-horizontal · gallery-horizontal-end · gallery-thumbnails · gallery-vertical · gallery-vertical-end · gamepad · gamepad-2 · gamepad-directional · gauge · gavel · gem · georgian-lari · ghost · gift · git-branch · git-branch-minus · git-branch-plus · git-commit-horizontal · git-commit-vertical · git-compare · git-compare-arrows · git-fork · git-graph · git-merge · git-merge-conflict · git-pull-request · git-pull-request-arrow · git-pull-request-closed · git-pull-request-create · git-pull-request-create-arrow · git-pull-request-draft · glass-water · glasses · globe · globe-check · globe-lock · globe-off · globe-x · goal · gpu · graduation-cap · grape · grid-2x2 · grid-2x2-check · grid-2x2-plus · grid-2x2-x · grid-3x2 · grid-3x3 · grip · grip-horizontal · grip-vertical · group · guitar · ham · hamburger · hammer · hand · hand-coins · hand-fist · hand-grab · hand-heart · hand-helping · hand-metal · hand-platter · handbag · handshake · hard-drive · hard-drive-download · hard-drive-upload · hard-hat · hash · hat-glasses · haze · hd · hdmi-port · heading · heading-1 · heading-2 · heading-3 · heading-4 · heading-5 · heading-6 · headphone-off · headphones · headset · heart · heart-crack · heart-handshake · heart-minus · heart-off · heart-plus · heart-pulse · heart-x · heater · helicopter · hexagon · highlighter · hop · hop-off · hospital · hotel · hourglass · house · house-heart · house-plug · house-plus · house-wifi · ice-cream-bowl · ice-cream-cone · id-card · id-card-lanyard · image · image-down · image-minus · image-off · image-play · image-plus · image-up · image-upscale · images · import · inbox · indian-rupee · infinity · info · inspection-panel · italic · iteration-ccw · iteration-cw · japanese-yen · joystick · kanban · kayak · key · key-round · key-square · keyboard · keyboard-music · keyboard-off · lamp · lamp-ceiling · lamp-desk · lamp-floor · lamp-wall-down · lamp-wall-up · land-plot · landmark · languages · laptop · laptop-minimal · laptop-minimal-check · lasso · lasso-select · laugh · layers · layers-2 · layers-minus · layers-plus · layout-dashboard · layout-freeform · layout-grid · layout-list · layout-panel-left · layout-panel-top · layout-template · leaf · leafy-green · lectern · lens-concave · lens-convex · library · library-big · life-buoy · ligature · lightbulb · lightbulb-off · line-dot-right-horizontal · line-squiggle · line-style · link · link-2 · link-2-off · list · list-check · list-checks · list-chevrons-down-up · list-chevrons-up-down · list-collapse · list-end · list-filter · list-filter-plus · list-indent-decrease · list-indent-increase · list-minus · list-music · list-ordered · list-plus · list-restart · list-sort-ascending · list-sort-descending · list-start · list-todo · list-tree · list-video · list-x · loader · loader-circle · loader-pinwheel · locate · locate-fixed · locate-off · lock · lock-keyhole · lock-keyhole-open · lock-open · log-in · log-out · logs · lollipop · luggage · magnet · mail · mail-check · mail-minus · mail-open · mail-plus · mail-question-mark · mail-search · mail-warning · mail-x · mailbox · mails · map · map-minus · map-pin · map-pin-check · map-pin-check-inside · map-pin-house · map-pin-minus · map-pin-minus-inside · map-pin-off · map-pin-pen · map-pin-plus · map-pin-plus-inside · map-pin-search · map-pin-x · map-pin-x-inside · map-pinned · map-plus · mars · mars-stroke · martini · maximize · maximize-2 · medal · megaphone · megaphone-off · meh · memory-stick · menu · merge · message-circle · message-circle-check · message-circle-code · message-circle-dashed · message-circle-heart · message-circle-more · message-circle-off · message-circle-plus · message-circle-question-mark · message-circle-reply · message-circle-warning · message-circle-x · message-square · message-square-check · message-square-code · message-square-dashed · message-square-diff · message-square-dot · message-square-heart · message-square-lock · message-square-more · message-square-off · message-square-plus · message-square-quote · message-square-reply · message-square-share · message-square-text · message-square-warning · message-square-x · messages-square · metronome · mic · mic-audio-lines · mic-off · mic-signal · mic-vocal · microchip · microscope · microwave · milestone · milk · milk-off · minimize · minimize-2 · minus · mirror-rectangular · mirror-round · monitor · monitor-check · monitor-cloud · monitor-cog · monitor-dot · monitor-down · monitor-off · monitor-pause · monitor-play · monitor-smartphone · monitor-speaker · monitor-stop · monitor-up · monitor-x · moon · moon-star · mosque · motorbike · mountain · mountain-snow · mouse · mouse-left · mouse-off · mouse-pointer · mouse-pointer-2 · mouse-pointer-2-off · mouse-pointer-ban · mouse-pointer-click · mouse-right · move · move-3d · move-diagonal · move-diagonal-2 · move-down · move-down-left · move-down-right · move-horizontal · move-left · move-right · move-up · move-up-left · move-up-right · move-vertical · music · music-2 · music-3 · music-4 · navigation · navigation-2 · navigation-2-off · navigation-off · network · newspaper · nfc · non-binary · notebook · notebook-pen · notebook-tabs · notebook-text · notepad-text · notepad-text-dashed · nut · nut-off · octagon · octagon-alert · octagon-minus · octagon-pause · octagon-x · omega · option · orbit · origami · package · package-2 · package-check · package-minus · package-open · package-plus · package-search · package-x · paint-bucket · paint-roller · paintbrush · paintbrush-vertical · palette · panda · panel-bottom · panel-bottom-close · panel-bottom-dashed · panel-bottom-open · panel-left · panel-left-close · panel-left-dashed · panel-left-open · panel-left-right-dashed · panel-right · panel-right-close · panel-right-dashed · panel-right-open · panel-top · panel-top-bottom-dashed · panel-top-close · panel-top-dashed · panel-top-open · panels-left-bottom · panels-right-bottom · panels-top-left · paper-bag · paperclip · parasol · parentheses · parking-meter · party-popper · pause · paw-print · pc-case · pen · pen-line · pen-off · pen-tool · pencil · pencil-line · pencil-off · pencil-ruler · pencil-sparkles · pentagon · percent · person-standing · phi · philippine-peso · phone · phone-call · phone-forwarded · phone-incoming · phone-missed · phone-off · phone-outgoing · pi · piano · pickaxe · picture-in-picture · picture-in-picture-2 · piggy-bank · pilcrow · pilcrow-left · pilcrow-right · pill · pill-bottle · pin · pin-off · pipette · pizza · plane · plane-landing · plane-takeoff · play · play-off · plug · plug-2 · plug-zap · plus · pocket-knife · podium · pointer · pointer-off · popcorn · popsicle · pound-sterling · power · power-off · presentation · printer · printer-check · printer-x · projector · proportions · puzzle · pyramid · qr-code · quote · rabbit · radar · radiation · radical · radio · radio-off · radio-receiver · radio-tower · radius · rainbow · rat · ratio · receipt · receipt-cent · receipt-euro · receipt-indian-rupee · receipt-japanese-yen · receipt-pound-sterling · receipt-russian-ruble · receipt-swiss-franc · receipt-text · receipt-turkish-lira · rectangle-circle · rectangle-ellipsis · rectangle-goggles · rectangle-horizontal · rectangle-vertical · recycle · redo · redo-2 · redo-dot · refresh-ccw · refresh-ccw-dot · refresh-cw · refresh-cw-off · refrigerator · regex · remove-formatting · repeat · repeat-1 · repeat-2 · repeat-off · replace · replace-all · reply · reply-all · rewind · ribbon · road · rocket · rocking-chair · roller-coaster · rose · rotate-3d · rotate-ccw · rotate-ccw-clock · rotate-ccw-key · rotate-ccw-square · rotate-cw · rotate-cw-fading-clock · rotate-cw-square · route · route-off · router · rows-2 · rows-3 · rows-4 · rss · ruler · ruler-dimension-line · russian-ruble · sailboat · salad · sandwich · satellite · satellite-dish · saudi-riyal · save · save-all · save-check · save-off · save-pen · save-plus · scale · scale-3d · scaling · scan · scan-barcode · scan-box · scan-eye · scan-face · scan-heart · scan-line · scan-qr-code · scan-search · scan-square · scan-text · school · scissors · scissors-line-dashed · scooter · screen-share · screen-share-off · scroll · scroll-text · search · search-alert · search-check · search-code · search-slash · search-x · section · send · send-horizontal · send-to-back · separator-horizontal · separator-vertical · server · server-cog · server-crash · server-off · server-plus · settings · settings-2 · shapes · share · share-2 · sheet · shell · shelving-unit · shield · shield-alert · shield-ban · shield-check · shield-cog · shield-cog-corner · shield-ellipsis · shield-half · shield-keyhole · shield-minus · shield-off · shield-plus · shield-question-mark · shield-user · shield-x · ship · ship-wheel · shirt · shopping-bag · shopping-basket · shopping-cart · shovel · shower-head · shredder · shrimp · shrink · shrub · shuffle · sigma · signal · signal-high · signal-low · signal-medium · signal-zero · signature · signpost · signpost-big · siren · skip-back · skip-forward · skull · slash · slice · sliders-horizontal · sliders-vertical · smartphone · smartphone-charging · smartphone-nfc · smile · smile-plus · snail · snowflake · soap-dispenser-droplet · sofa · solar-panel · soup · space · spade · sparkle · sparkles · speaker · speech · spell-check · spell-check-2 · spline · spline-pointer · split · spool · sport-shoe · spotlight · spray-can · sprout · square · square-activity · square-arrow-down · square-arrow-down-left · square-arrow-down-right · square-arrow-left · square-arrow-out-down-left · square-arrow-out-down-right · square-arrow-out-up-left · square-arrow-out-up-right · square-arrow-right · square-arrow-right-enter · square-arrow-right-exit · square-arrow-up · square-arrow-up-left · square-arrow-up-right · square-asterisk · square-bottom-dashed-scissors · square-centerline-dashed-horizontal · square-centerline-dashed-vertical · square-chart-gantt · square-check · square-check-big · square-chevron-down · square-chevron-left · square-chevron-right · square-chevron-up · square-code · square-dashed · square-dashed-bottom · square-dashed-bottom-code · square-dashed-kanban · square-dashed-mouse-pointer · square-dashed-text · square-dashed-top-solid · square-divide · square-dot · square-equal · square-function · square-kanban · square-library · square-m · square-menu · square-minus · square-mouse-pointer · square-off · square-parking · square-parking-off · square-pause · square-pen · square-percent · square-pi · square-pilcrow · square-play · square-plus · square-power · square-radical · square-round-corner · square-scissors · square-sigma · square-slash · square-split-horizontal · square-split-vertical · square-square · square-stack · square-star · square-stop · square-terminal · square-user · square-user-round · square-x · squares-exclude · squares-intersect · squares-subtract · squares-unite · squircle · squircle-dashed · squirrel · stamp · star · star-check · star-half · star-minus · star-off · star-plus · star-x · step-back · step-forward · stethoscope · sticker · sticky-note · sticky-note-check · sticky-note-minus · sticky-note-off · sticky-note-plus · sticky-note-x · sticky-notes · stone · store · stretch-horizontal · stretch-vertical · strikethrough · subscript · summary · sun · sun-dim · sun-medium · sun-moon · sun-snow · sunrise · sunset · superscript · swatch-book · swiss-franc · switch-camera · sword · swords · syringe · table · table-2 · table-cells-merge · table-cells-split · table-columns-split · table-of-contents · table-properties · table-rows-split · tablet · tablet-smartphone · tablets · tag · tag-plus · tag-x · tags · tally-1 · tally-2 · tally-3 · tally-4 · tally-5 · tangent · target · telescope · tent · tent-tree · terminal · test-tube · test-tube-diagonal · test-tubes · text-align-center · text-align-end · text-align-justify · text-align-start · text-cursor · text-cursor-input · text-initial · text-quote · text-search · text-wrap · theater · thermometer · thermometer-snowflake · thermometer-sun · thumbs-down · thumbs-up · ticket · ticket-check · ticket-minus · ticket-percent · ticket-plus · ticket-slash · ticket-x · tickets · tickets-plane · timeline · timer · timer-off · timer-reset · toggle-left · toggle-right · toilet · tool-case · toolbox · tornado · torus · touchpad · touchpad-off · towel-rack · tower-control · toy-brick · tractor · traffic-cone · train-front · train-front-tunnel · train-track · tram-front · transgender · trash · trash-2 · tree-deciduous · tree-palm · tree-pine · trees · trending-down · trending-up · trending-up-down · triangle · triangle-alert · triangle-dashed · triangle-right · trophy · truck · truck-electric · turkish-lira · turntable · turtle · tv · tv-minimal · tv-minimal-play · type · type-outline · umbrella · umbrella-off · underline · undo · undo-2 · undo-dot · unfold-horizontal · unfold-vertical · ungroup · university · unlink · unlink-2 · unplug · upload · usb · user · user-check · user-cog · user-key · user-lock · user-minus · user-pen · user-plus · user-round · user-round-arrow-left · user-round-check · user-round-cog · user-round-key · user-round-minus · user-round-pen · user-round-plus · user-round-search · user-round-x · user-search · user-shield · user-star · user-x · users · users-round · utensils · utensils-crossed · utility-pole · van · variable · vault · vector-square · vegan · venetian-mask · venus · venus-and-mars · vibrate · vibrate-off · video · video-off · videotape · view · voicemail · volleyball · volume · volume-1 · volume-2 · volume-off · volume-x · vote · wallet · wallet-cards · wallet-minimal · wallpaper · wand · wand-sparkles · warehouse · washing-machine · watch · waves-arrow-down · waves-arrow-up · waves-horizontal · waves-ladder · waves-vertical · waypoints · webcam · webcam-off · webhook · webhook-off · weight · weight-tilde · wheat · wheat-off · whole-word · wifi · wifi-cog · wifi-high · wifi-low · wifi-off · wifi-pen · wifi-sync · wifi-zero · wind · wind-arrow-down · wine · wine-off · workflow · worm · wrench · wrench-off · x · x-line-top · zap · zap-off · zodiac-aquarius · zodiac-aries · zodiac-cancer · zodiac-capricorn · zodiac-gemini · zodiac-leo · zodiac-libra · zodiac-ophiuchus · zodiac-pisces · zodiac-sagittarius · zodiac-scorpio · zodiac-taurus · zodiac-virgo · zoom-in · zoom-out
Heroicons — Tailwind’s UI icon set · 324 · MIT
asset:svg/heroicons/<name>.svg
academic-cap · adjustments-horizontal · adjustments-vertical · archive-box · archive-box-arrow-down · archive-box-x-mark · arrow-down · arrow-down-circle · arrow-down-left · arrow-down-on-square · arrow-down-on-square-stack · arrow-down-right · arrow-down-tray · arrow-left · arrow-left-circle · arrow-left-end-on-rectangle · arrow-left-on-rectangle · arrow-left-start-on-rectangle · arrow-long-down · arrow-long-left · arrow-long-right · arrow-long-up · arrow-path · arrow-path-rounded-square · arrow-right · arrow-right-circle · arrow-right-end-on-rectangle · arrow-right-on-rectangle · arrow-right-start-on-rectangle · arrow-small-down · arrow-small-left · arrow-small-right · arrow-small-up · arrow-top-right-on-square · arrow-trending-down · arrow-trending-up · arrow-turn-down-left · arrow-turn-down-right · arrow-turn-left-down · arrow-turn-left-up · arrow-turn-right-down · arrow-turn-right-up · arrow-turn-up-left · arrow-turn-up-right · arrow-up · arrow-up-circle · arrow-up-left · arrow-up-on-square · arrow-up-on-square-stack · arrow-up-right · arrow-up-tray · arrow-uturn-down · arrow-uturn-left · arrow-uturn-right · arrow-uturn-up · arrows-pointing-in · arrows-pointing-out · arrows-right-left · arrows-up-down · at-symbol · backspace · backward · banknotes · bars-2 · bars-3 · bars-3-bottom-left · bars-3-bottom-right · bars-3-center-left · bars-4 · bars-arrow-down · bars-arrow-up · battery-0 · battery-100 · battery-50 · beaker · bell · bell-alert · bell-slash · bell-snooze · bold · bolt · bolt-slash · book-open · bookmark · bookmark-slash · bookmark-square · briefcase · bug-ant · building-library · building-office · building-office-2 · building-storefront · cake · calculator · calendar · calendar-date-range · calendar-days · camera · chart-bar · chart-bar-square · chart-pie · chat-bubble-bottom-center · chat-bubble-bottom-center-text · chat-bubble-left · chat-bubble-left-ellipsis · chat-bubble-left-right · chat-bubble-oval-left · chat-bubble-oval-left-ellipsis · check · check-badge · check-circle · chevron-double-down · chevron-double-left · chevron-double-right · chevron-double-up · chevron-down · chevron-left · chevron-right · chevron-up · chevron-up-down · circle-stack · clipboard · clipboard-document · clipboard-document-check · clipboard-document-list · clock · cloud · cloud-arrow-down · cloud-arrow-up · code-bracket · code-bracket-square · cog · cog-6-tooth · cog-8-tooth · command-line · computer-desktop · cpu-chip · credit-card · cube · cube-transparent · currency-bangladeshi · currency-dollar · currency-euro · currency-pound · currency-rupee · currency-yen · cursor-arrow-rays · cursor-arrow-ripple · device-phone-mobile · device-tablet · divide · document · document-arrow-down · document-arrow-up · document-chart-bar · document-check · document-currency-bangladeshi · document-currency-dollar · document-currency-euro · document-currency-pound · document-currency-rupee · document-currency-yen · document-duplicate · document-magnifying-glass · document-minus · document-plus · document-text · ellipsis-horizontal · ellipsis-horizontal-circle · ellipsis-vertical · envelope · envelope-open · equals · exclamation-circle · exclamation-triangle · eye · eye-dropper · eye-slash · face-frown · face-smile · film · finger-print · fire · flag · folder · folder-arrow-down · folder-minus · folder-open · folder-plus · forward · funnel · gif · gift · gift-top · globe-alt · globe-americas · globe-asia-australia · globe-europe-africa · h1 · h2 · h3 · hand-raised · hand-thumb-down · hand-thumb-up · hashtag · heart · home · home-modern · identification · inbox · inbox-arrow-down · inbox-stack · information-circle · italic · key · language · lifebuoy · light-bulb · link · link-slash · list-bullet · lock-closed · lock-open · magnifying-glass · magnifying-glass-circle · magnifying-glass-minus · magnifying-glass-plus · map · map-pin · megaphone · microphone · minus · minus-circle · minus-small · moon · musical-note · newspaper · no-symbol · numbered-list · paint-brush · paper-airplane · paper-clip · pause · pause-circle · pencil · pencil-square · percent-badge · phone · phone-arrow-down-left · phone-arrow-up-right · phone-x-mark · photo · play · play-circle · play-pause · plus · plus-circle · plus-small · power · presentation-chart-bar · presentation-chart-line · printer · puzzle-piece · qr-code · question-mark-circle · queue-list · radio · receipt-percent · receipt-refund · rectangle-group · rectangle-stack · rocket-launch · rss · scale · scissors · server · server-stack · share · shield-check · shield-exclamation · shopping-bag · shopping-cart · signal · signal-slash · slash · sparkles · speaker-wave · speaker-x-mark · square-2-stack · square-3-stack-3d · squares-2x2 · squares-plus · star · stop · stop-circle · strikethrough · sun · swatch · table-cells · tag · ticket · trash · trophy · truck · tv · underline · user · user-circle · user-group · user-minus · user-plus · users · variable · video-camera · video-camera-slash · view-columns · viewfinder-circle · wallet · wifi · window · wrench · wrench-screwdriver · x-circle · x-mark
Flags — country flags, two-letter ISO 3166-1 codes · 271 · MIT
asset:svg/flags/<name>.svg
ad · ae · af · ag · ai · al · am · ao · aq · ar · arab · as · asean · at · au · aw · ax · az · ba · bb · bd · be · bf · bg · bh · bi · bj · bl · bm · bn · bo · bq · br · bs · bt · bv · bw · by · bz · ca · cc · cd · cefta · cf · cg · ch · ci · ck · cl · cm · cn · co · cp · cr · cu · cv · cw · cx · cy · cz · de · dg · dj · dk · dm · do · dz · eac · ec · ee · eg · eh · er · es · es-ct · es-ga · es-pv · et · eu · fi · fj · fk · fm · fo · fr · ga · gb · gb-eng · gb-nir · gb-sct · gb-wls · gd · ge · gf · gg · gh · gi · gl · gm · gn · gp · gq · gr · gs · gt · gu · gw · gy · hk · hm · hn · hr · ht · hu · ic · id · ie · il · im · in · io · iq · ir · is · it · je · jm · jo · jp · ke · kg · kh · ki · km · kn · kp · kr · kw · ky · kz · la · lb · lc · li · lk · lr · ls · lt · lu · lv · ly · ma · mc · md · me · mf · mg · mh · mk · ml · mm · mn · mo · mp · mq · mr · ms · mt · mu · mv · mw · mx · my · mz · na · nc · ne · nf · ng · ni · nl · no · np · nr · nu · nz · om · pa · pc · pe · pf · pg · ph · pk · pl · pm · pn · pr · ps · pt · pw · py · qa · re · ro · rs · ru · rw · sa · sb · sc · sd · se · sg · sh · sh-ac · sh-hl · sh-ta · si · sj · sk · sl · sm · sn · so · sr · ss · st · sv · sx · sy · sz · tc · td · tf · tg · th · tj · tk · tl · tm · tn · to · tr · tt · tv · tw · tz · ua · ug · um · un · us · uy · uz · va · vc · ve · vg · vi · vn · vu · wf · ws · xk · xx · ye · yt · za · zm · zw
Emoji — Twemoji — full-colour emoji · 4,052 · CC-BY 4.0
asset:svg/emoji/<name>.svg
Friendly aliases (43) — plain-English names, the easy way in:
bell · brain · bulb · car · chart · check · clap · cloud · computer · cross · door · earth · eyes · fire · flag · goat · grinning · heart · hourglass · joy · key · lock · mail · money · moon · muscle · party · phone · rainbow · rocket · smile · sob · star · sun · target · thinking · thumbsdown · thumbsup · trophy · warning · wave · wink · zap
All emoji (4,009) are also addressable by their Unicode codepoint filename (e.g. 1f680 = 🚀). Expand to browse every one:
Show all 4,009 emoji codepoints
1f004 · 1f0cf · 1f170 · 1f171 · 1f17e · 1f17f · 1f18e · 1f191 · 1f192 · 1f193 · 1f194 · 1f195 · 1f196 · 1f197 · 1f198 · 1f199 · 1f19a · 1f1e6 · 1f1e6-1f1e8 · 1f1e6-1f1e9 · 1f1e6-1f1ea · 1f1e6-1f1eb · 1f1e6-1f1ec · 1f1e6-1f1ee · 1f1e6-1f1f1 · 1f1e6-1f1f2 · 1f1e6-1f1f4 · 1f1e6-1f1f6 · 1f1e6-1f1f7 · 1f1e6-1f1f8 · 1f1e6-1f1f9 · 1f1e6-1f1fa · 1f1e6-1f1fc · 1f1e6-1f1fd · 1f1e6-1f1ff · 1f1e7 · 1f1e7-1f1e6 · 1f1e7-1f1e7 · 1f1e7-1f1e9 · 1f1e7-1f1ea · 1f1e7-1f1eb · 1f1e7-1f1ec · 1f1e7-1f1ed · 1f1e7-1f1ee · 1f1e7-1f1ef · 1f1e7-1f1f1 · 1f1e7-1f1f2 · 1f1e7-1f1f3 · 1f1e7-1f1f4 · 1f1e7-1f1f6 · 1f1e7-1f1f7 · 1f1e7-1f1f8 · 1f1e7-1f1f9 · 1f1e7-1f1fb · 1f1e7-1f1fc · 1f1e7-1f1fe · 1f1e7-1f1ff · 1f1e8 · 1f1e8-1f1e6 · 1f1e8-1f1e8 · 1f1e8-1f1e9 · 1f1e8-1f1eb · 1f1e8-1f1ec · 1f1e8-1f1ed · 1f1e8-1f1ee · 1f1e8-1f1f0 · 1f1e8-1f1f1 · 1f1e8-1f1f2 · 1f1e8-1f1f3 · 1f1e8-1f1f4 · 1f1e8-1f1f5 · 1f1e8-1f1f6 · 1f1e8-1f1f7 · 1f1e8-1f1fa · 1f1e8-1f1fb · 1f1e8-1f1fc · 1f1e8-1f1fd · 1f1e8-1f1fe · 1f1e8-1f1ff · 1f1e9 · 1f1e9-1f1ea · 1f1e9-1f1ec · 1f1e9-1f1ef · 1f1e9-1f1f0 · 1f1e9-1f1f2 · 1f1e9-1f1f4 · 1f1e9-1f1ff · 1f1ea · 1f1ea-1f1e6 · 1f1ea-1f1e8 · 1f1ea-1f1ea · 1f1ea-1f1ec · 1f1ea-1f1ed · 1f1ea-1f1f7 · 1f1ea-1f1f8 · 1f1ea-1f1f9 · 1f1ea-1f1fa · 1f1eb · 1f1eb-1f1ee · 1f1eb-1f1ef · 1f1eb-1f1f0 · 1f1eb-1f1f2 · 1f1eb-1f1f4 · 1f1eb-1f1f7 · 1f1ec · 1f1ec-1f1e6 · 1f1ec-1f1e7 · 1f1ec-1f1e9 · 1f1ec-1f1ea · 1f1ec-1f1eb · 1f1ec-1f1ec · 1f1ec-1f1ed · 1f1ec-1f1ee · 1f1ec-1f1f1 · 1f1ec-1f1f2 · 1f1ec-1f1f3 · 1f1ec-1f1f5 · 1f1ec-1f1f6 · 1f1ec-1f1f7 · 1f1ec-1f1f8 · 1f1ec-1f1f9 · 1f1ec-1f1fa · 1f1ec-1f1fc · 1f1ec-1f1fe · 1f1ed · 1f1ed-1f1f0 · 1f1ed-1f1f2 · 1f1ed-1f1f3 · 1f1ed-1f1f7 · 1f1ed-1f1f9 · 1f1ed-1f1fa · 1f1ee · 1f1ee-1f1e8 · 1f1ee-1f1e9 · 1f1ee-1f1ea · 1f1ee-1f1f1 · 1f1ee-1f1f2 · 1f1ee-1f1f3 · 1f1ee-1f1f4 · 1f1ee-1f1f6 · 1f1ee-1f1f7 · 1f1ee-1f1f8 · 1f1ee-1f1f9 · 1f1ef · 1f1ef-1f1ea · 1f1ef-1f1f2 · 1f1ef-1f1f4 · 1f1ef-1f1f5 · 1f1f0 · 1f1f0-1f1ea · 1f1f0-1f1ec · 1f1f0-1f1ed · 1f1f0-1f1ee · 1f1f0-1f1f2 · 1f1f0-1f1f3 · 1f1f0-1f1f5 · 1f1f0-1f1f7 · 1f1f0-1f1fc · 1f1f0-1f1fe · 1f1f0-1f1ff · 1f1f1 · 1f1f1-1f1e6 · 1f1f1-1f1e7 · 1f1f1-1f1e8 · 1f1f1-1f1ee · 1f1f1-1f1f0 · 1f1f1-1f1f7 · 1f1f1-1f1f8 · 1f1f1-1f1f9 · 1f1f1-1f1fa · 1f1f1-1f1fb · 1f1f1-1f1fe · 1f1f2 · 1f1f2-1f1e6 · 1f1f2-1f1e8 · 1f1f2-1f1e9 · 1f1f2-1f1ea · 1f1f2-1f1eb · 1f1f2-1f1ec · 1f1f2-1f1ed · 1f1f2-1f1f0 · 1f1f2-1f1f1 · 1f1f2-1f1f2 · 1f1f2-1f1f3 · 1f1f2-1f1f4 · 1f1f2-1f1f5 · 1f1f2-1f1f6 · 1f1f2-1f1f7 · 1f1f2-1f1f8 · 1f1f2-1f1f9 · 1f1f2-1f1fa · 1f1f2-1f1fb · 1f1f2-1f1fc · 1f1f2-1f1fd · 1f1f2-1f1fe · 1f1f2-1f1ff · 1f1f3 · 1f1f3-1f1e6 · 1f1f3-1f1e8 · 1f1f3-1f1ea · 1f1f3-1f1eb · 1f1f3-1f1ec · 1f1f3-1f1ee · 1f1f3-1f1f1 · 1f1f3-1f1f4 · 1f1f3-1f1f5 · 1f1f3-1f1f7 · 1f1f3-1f1fa · 1f1f3-1f1ff · 1f1f4 · 1f1f4-1f1f2 · 1f1f5 · 1f1f5-1f1e6 · 1f1f5-1f1ea · 1f1f5-1f1eb · 1f1f5-1f1ec · 1f1f5-1f1ed · 1f1f5-1f1f0 · 1f1f5-1f1f1 · 1f1f5-1f1f2 · 1f1f5-1f1f3 · 1f1f5-1f1f7 · 1f1f5-1f1f8 · 1f1f5-1f1f9 · 1f1f5-1f1fc · 1f1f5-1f1fe · 1f1f6 · 1f1f6-1f1e6 · 1f1f7 · 1f1f7-1f1ea · 1f1f7-1f1f4 · 1f1f7-1f1f8 · 1f1f7-1f1fa · 1f1f7-1f1fc · 1f1f8 · 1f1f8-1f1e6 · 1f1f8-1f1e7 · 1f1f8-1f1e8 · 1f1f8-1f1e9 · 1f1f8-1f1ea · 1f1f8-1f1ec · 1f1f8-1f1ed · 1f1f8-1f1ee · 1f1f8-1f1ef · 1f1f8-1f1f0 · 1f1f8-1f1f1 · 1f1f8-1f1f2 · 1f1f8-1f1f3 · 1f1f8-1f1f4 · 1f1f8-1f1f7 · 1f1f8-1f1f8 · 1f1f8-1f1f9 · 1f1f8-1f1fb · 1f1f8-1f1fd · 1f1f8-1f1fe · 1f1f8-1f1ff · 1f1f9 · 1f1f9-1f1e6 · 1f1f9-1f1e8 · 1f1f9-1f1e9 · 1f1f9-1f1eb · 1f1f9-1f1ec · 1f1f9-1f1ed · 1f1f9-1f1ef · 1f1f9-1f1f0 · 1f1f9-1f1f1 · 1f1f9-1f1f2 · 1f1f9-1f1f3 · 1f1f9-1f1f4 · 1f1f9-1f1f7 · 1f1f9-1f1f9 · 1f1f9-1f1fb · 1f1f9-1f1fc · 1f1f9-1f1ff · 1f1fa · 1f1fa-1f1e6 · 1f1fa-1f1ec · 1f1fa-1f1f2 · 1f1fa-1f1f3 · 1f1fa-1f1f8 · 1f1fa-1f1fe · 1f1fa-1f1ff · 1f1fb · 1f1fb-1f1e6 · 1f1fb-1f1e8 · 1f1fb-1f1ea · 1f1fb-1f1ec · 1f1fb-1f1ee · 1f1fb-1f1f3 · 1f1fb-1f1fa · 1f1fc · 1f1fc-1f1eb · 1f1fc-1f1f8 · 1f1fd · 1f1fd-1f1f0 · 1f1fe · 1f1fe-1f1ea · 1f1fe-1f1f9 · 1f1ff · 1f1ff-1f1e6 · 1f1ff-1f1f2 · 1f1ff-1f1fc · 1f201 · 1f202 · 1f21a · 1f22f · 1f232 · 1f233 · 1f234 · 1f235 · 1f236 · 1f237 · 1f238 · 1f239 · 1f23a · 1f250 · 1f251 · 1f300 · 1f301 · 1f302 · 1f303 · 1f304 · 1f305 · 1f306 · 1f307 · 1f308 · 1f309 · 1f30a · 1f30b · 1f30c · 1f30d · 1f30e · 1f30f · 1f310 · 1f311 · 1f312 · 1f313 · 1f314 · 1f315 · 1f316 · 1f317 · 1f318 · 1f319 · 1f31a · 1f31b · 1f31c · 1f31d · 1f31e · 1f31f · 1f320 · 1f321 · 1f324 · 1f325 · 1f326 · 1f327 · 1f328 · 1f329 · 1f32a · 1f32b · 1f32c · 1f32d · 1f32e · 1f32f · 1f330 · 1f331 · 1f332 · 1f333 · 1f334 · 1f335 · 1f336 · 1f337 · 1f338 · 1f339 · 1f33a · 1f33b · 1f33c · 1f33d · 1f33e · 1f33f · 1f340 · 1f341 · 1f342 · 1f343 · 1f344 · 1f344-200d-1f7eb · 1f345 · 1f346 · 1f347 · 1f348 · 1f349 · 1f34a · 1f34b · 1f34b-200d-1f7e9 · 1f34c · 1f34d · 1f34e · 1f34f · 1f350 · 1f351 · 1f352 · 1f353 · 1f354 · 1f355 · 1f356 · 1f357 · 1f358 · 1f359 · 1f35a · 1f35b · 1f35c · 1f35d · 1f35e · 1f35f · 1f360 · 1f361 · 1f362 · 1f363 · 1f364 · 1f365 · 1f366 · 1f367 · 1f368 · 1f369 · 1f36a · 1f36b · 1f36c · 1f36d · 1f36e · 1f36f · 1f370 · 1f371 · 1f372 · 1f373 · 1f374 · 1f375 · 1f376 · 1f377 · 1f378 · 1f379 · 1f37a · 1f37b · 1f37c · 1f37d · 1f37e · 1f37f · 1f380 · 1f381 · 1f382 · 1f383 · 1f384 · 1f385 · 1f385-1f3fb · 1f385-1f3fc · 1f385-1f3fd · 1f385-1f3fe · 1f385-1f3ff · 1f386 · 1f387 · 1f388 · 1f389 · 1f38a · 1f38b · 1f38c · 1f38d · 1f38e · 1f38f · 1f390 · 1f391 · 1f392 · 1f393 · 1f396 · 1f397 · 1f399 · 1f39a · 1f39b · 1f39e · 1f39f · 1f3a0 · 1f3a1 · 1f3a2 · 1f3a3 · 1f3a4 · 1f3a5 · 1f3a6 · 1f3a7 · 1f3a8 · 1f3a9 · 1f3aa · 1f3ab · 1f3ac · 1f3ad · 1f3ae · 1f3af · 1f3b0 · 1f3b1 · 1f3b2 · 1f3b3 · 1f3b4 · 1f3b5 · 1f3b6 · 1f3b7 · 1f3b8 · 1f3b9 · 1f3ba · 1f3bb · 1f3bc · 1f3bd · 1f3be · 1f3bf · 1f3c0 · 1f3c1 · 1f3c2 · 1f3c2-1f3fb · 1f3c2-1f3fc · 1f3c2-1f3fd · 1f3c2-1f3fe · 1f3c2-1f3ff · 1f3c3 · 1f3c3-1f3fb · 1f3c3-1f3fb-200d-2640-fe0f · 1f3c3-1f3fb-200d-2640-fe0f-200d-27a1-fe0f · 1f3c3-1f3fb-200d-2642-fe0f · 1f3c3-1f3fb-200d-2642-fe0f-200d-27a1-fe0f · 1f3c3-1f3fb-200d-27a1-fe0f · 1f3c3-1f3fc · 1f3c3-1f3fc-200d-2640-fe0f · 1f3c3-1f3fc-200d-2640-fe0f-200d-27a1-fe0f · 1f3c3-1f3fc-200d-2642-fe0f · 1f3c3-1f3fc-200d-2642-fe0f-200d-27a1-fe0f · 1f3c3-1f3fc-200d-27a1-fe0f · 1f3c3-1f3fd · 1f3c3-1f3fd-200d-2640-fe0f · 1f3c3-1f3fd-200d-2640-fe0f-200d-27a1-fe0f · 1f3c3-1f3fd-200d-2642-fe0f · 1f3c3-1f3fd-200d-2642-fe0f-200d-27a1-fe0f · 1f3c3-1f3fd-200d-27a1-fe0f · 1f3c3-1f3fe · 1f3c3-1f3fe-200d-2640-fe0f · 1f3c3-1f3fe-200d-2640-fe0f-200d-27a1-fe0f · 1f3c3-1f3fe-200d-2642-fe0f · 1f3c3-1f3fe-200d-2642-fe0f-200d-27a1-fe0f · 1f3c3-1f3fe-200d-27a1-fe0f · 1f3c3-1f3ff · 1f3c3-1f3ff-200d-2640-fe0f · 1f3c3-1f3ff-200d-2640-fe0f-200d-27a1-fe0f · 1f3c3-1f3ff-200d-2642-fe0f · 1f3c3-1f3ff-200d-2642-fe0f-200d-27a1-fe0f · 1f3c3-1f3ff-200d-27a1-fe0f · 1f3c3-200d-2640-fe0f · 1f3c3-200d-2640-fe0f-200d-27a1-fe0f · 1f3c3-200d-2642-fe0f · 1f3c3-200d-2642-fe0f-200d-27a1-fe0f · 1f3c3-200d-27a1-fe0f · 1f3c4 · 1f3c4-1f3fb · 1f3c4-1f3fb-200d-2640-fe0f · 1f3c4-1f3fb-200d-2642-fe0f · 1f3c4-1f3fc · 1f3c4-1f3fc-200d-2640-fe0f · 1f3c4-1f3fc-200d-2642-fe0f · 1f3c4-1f3fd · 1f3c4-1f3fd-200d-2640-fe0f · 1f3c4-1f3fd-200d-2642-fe0f · 1f3c4-1f3fe · 1f3c4-1f3fe-200d-2640-fe0f · 1f3c4-1f3fe-200d-2642-fe0f · 1f3c4-1f3ff · 1f3c4-1f3ff-200d-2640-fe0f · 1f3c4-1f3ff-200d-2642-fe0f · 1f3c4-200d-2640-fe0f · 1f3c4-200d-2642-fe0f · 1f3c5 · 1f3c6 · 1f3c7 · 1f3c7-1f3fb · 1f3c7-1f3fc · 1f3c7-1f3fd · 1f3c7-1f3fe · 1f3c7-1f3ff · 1f3c8 · 1f3c9 · 1f3ca · 1f3ca-1f3fb · 1f3ca-1f3fb-200d-2640-fe0f · 1f3ca-1f3fb-200d-2642-fe0f · 1f3ca-1f3fc · 1f3ca-1f3fc-200d-2640-fe0f · 1f3ca-1f3fc-200d-2642-fe0f · 1f3ca-1f3fd · 1f3ca-1f3fd-200d-2640-fe0f · 1f3ca-1f3fd-200d-2642-fe0f · 1f3ca-1f3fe · 1f3ca-1f3fe-200d-2640-fe0f · 1f3ca-1f3fe-200d-2642-fe0f · 1f3ca-1f3ff · 1f3ca-1f3ff-200d-2640-fe0f · 1f3ca-1f3ff-200d-2642-fe0f · 1f3ca-200d-2640-fe0f · 1f3ca-200d-2642-fe0f · 1f3cb · 1f3cb-1f3fb · 1f3cb-1f3fb-200d-2640-fe0f · 1f3cb-1f3fb-200d-2642-fe0f · 1f3cb-1f3fc · 1f3cb-1f3fc-200d-2640-fe0f · 1f3cb-1f3fc-200d-2642-fe0f · 1f3cb-1f3fd · 1f3cb-1f3fd-200d-2640-fe0f · 1f3cb-1f3fd-200d-2642-fe0f · 1f3cb-1f3fe · 1f3cb-1f3fe-200d-2640-fe0f · 1f3cb-1f3fe-200d-2642-fe0f · 1f3cb-1f3ff · 1f3cb-1f3ff-200d-2640-fe0f · 1f3cb-1f3ff-200d-2642-fe0f · 1f3cb-fe0f-200d-2640-fe0f · 1f3cb-fe0f-200d-2642-fe0f · 1f3cc · 1f3cc-1f3fb · 1f3cc-1f3fb-200d-2640-fe0f · 1f3cc-1f3fb-200d-2642-fe0f · 1f3cc-1f3fc · 1f3cc-1f3fc-200d-2640-fe0f · 1f3cc-1f3fc-200d-2642-fe0f · 1f3cc-1f3fd · 1f3cc-1f3fd-200d-2640-fe0f · 1f3cc-1f3fd-200d-2642-fe0f · 1f3cc-1f3fe · 1f3cc-1f3fe-200d-2640-fe0f · 1f3cc-1f3fe-200d-2642-fe0f · 1f3cc-1f3ff · 1f3cc-1f3ff-200d-2640-fe0f · 1f3cc-1f3ff-200d-2642-fe0f · 1f3cc-fe0f-200d-2640-fe0f · 1f3cc-fe0f-200d-2642-fe0f · 1f3cd · 1f3ce · 1f3cf · 1f3d0 · 1f3d1 · 1f3d2 · 1f3d3 · 1f3d4 · 1f3d5 · 1f3d6 · 1f3d7 · 1f3d8 · 1f3d9 · 1f3da · 1f3db · 1f3dc · 1f3dd · 1f3de · 1f3df · 1f3e0 · 1f3e1 · 1f3e2 · 1f3e3 · 1f3e4 · 1f3e5 · 1f3e6 · 1f3e7 · 1f3e8 · 1f3e9 · 1f3ea · 1f3eb · 1f3ec · 1f3ed · 1f3ee · 1f3ef · 1f3f0 · 1f3f3 · 1f3f3-fe0f-200d-1f308 · 1f3f3-fe0f-200d-26a7-fe0f · 1f3f4 · 1f3f4-200d-2620-fe0f · 1f3f4-e0067-e0062-e0065-e006e-e0067-e007f · 1f3f4-e0067-e0062-e0073-e0063-e0074-e007f · 1f3f4-e0067-e0062-e0077-e006c-e0073-e007f · 1f3f5 · 1f3f7 · 1f3f8 · 1f3f9 · 1f3fa · 1f3fb · 1f3fc · 1f3fd · 1f3fe · 1f3ff · 1f400 · 1f401 · 1f402 · 1f403 · 1f404 · 1f405 · 1f406 · 1f407 · 1f408 · 1f408-200d-2b1b · 1f409 · 1f40a · 1f40b · 1f40c · 1f40d · 1f40e · 1f40f · 1f410 · 1f411 · 1f412 · 1f413 · 1f414 · 1f415 · 1f415-200d-1f9ba · 1f416 · 1f417 · 1f418 · 1f419 · 1f41a · 1f41b · 1f41c · 1f41d · 1f41e · 1f41f · 1f420 · 1f421 · 1f422 · 1f423 · 1f424 · 1f425 · 1f426 · 1f426-200d-1f525 · 1f426-200d-2b1b · 1f427 · 1f428 · 1f429 · 1f42a · 1f42b · 1f42c · 1f42d · 1f42e · 1f42f · 1f430 · 1f431 · 1f432 · 1f433 · 1f434 · 1f435 · 1f436 · 1f437 · 1f438 · 1f439 · 1f43a · 1f43b · 1f43b-200d-2744-fe0f · 1f43c · 1f43d · 1f43e · 1f43f · 1f440 · 1f441 · 1f441-200d-1f5e8 · 1f442 · 1f442-1f3fb · 1f442-1f3fc · 1f442-1f3fd · 1f442-1f3fe · 1f442-1f3ff · 1f443 · 1f443-1f3fb · 1f443-1f3fc · 1f443-1f3fd · 1f443-1f3fe · 1f443-1f3ff · 1f444 · 1f445 · 1f446 · 1f446-1f3fb · 1f446-1f3fc · 1f446-1f3fd · 1f446-1f3fe · 1f446-1f3ff · 1f447 · 1f447-1f3fb · 1f447-1f3fc · 1f447-1f3fd · 1f447-1f3fe · 1f447-1f3ff · 1f448 · 1f448-1f3fb · 1f448-1f3fc · 1f448-1f3fd · 1f448-1f3fe · 1f448-1f3ff · 1f449 · 1f449-1f3fb · 1f449-1f3fc · 1f449-1f3fd · 1f449-1f3fe · 1f449-1f3ff · 1f44a · 1f44a-1f3fb · 1f44a-1f3fc · 1f44a-1f3fd · 1f44a-1f3fe · 1f44a-1f3ff · 1f44b · 1f44b-1f3fb · 1f44b-1f3fc · 1f44b-1f3fd · 1f44b-1f3fe · 1f44b-1f3ff · 1f44c · 1f44c-1f3fb · 1f44c-1f3fc · 1f44c-1f3fd · 1f44c-1f3fe · 1f44c-1f3ff · 1f44d · 1f44d-1f3fb · 1f44d-1f3fc · 1f44d-1f3fd · 1f44d-1f3fe · 1f44d-1f3ff · 1f44e · 1f44e-1f3fb · 1f44e-1f3fc · 1f44e-1f3fd · 1f44e-1f3fe · 1f44e-1f3ff · 1f44f · 1f44f-1f3fb · 1f44f-1f3fc · 1f44f-1f3fd · 1f44f-1f3fe · 1f44f-1f3ff · 1f450 · 1f450-1f3fb · 1f450-1f3fc · 1f450-1f3fd · 1f450-1f3fe · 1f450-1f3ff · 1f451 · 1f452 · 1f453 · 1f454 · 1f455 · 1f456 · 1f457 · 1f458 · 1f459 · 1f45a · 1f45b · 1f45c · 1f45d · 1f45e · 1f45f · 1f460 · 1f461 · 1f462 · 1f463 · 1f464 · 1f465 · 1f466 · 1f466-1f3fb · 1f466-1f3fc · 1f466-1f3fd · 1f466-1f3fe · 1f466-1f3ff · 1f467 · 1f467-1f3fb · 1f467-1f3fc · 1f467-1f3fd · 1f467-1f3fe · 1f467-1f3ff · 1f468 · 1f468-1f3fb · 1f468-1f3fb-200d-1f33e · 1f468-1f3fb-200d-1f373 · 1f468-1f3fb-200d-1f37c · 1f468-1f3fb-200d-1f384 · 1f468-1f3fb-200d-1f393 · 1f468-1f3fb-200d-1f3a4 · 1f468-1f3fb-200d-1f3a8 · 1f468-1f3fb-200d-1f3eb · 1f468-1f3fb-200d-1f3ed · 1f468-1f3fb-200d-1f430-200d-1f468-1f3fc · 1f468-1f3fb-200d-1f430-200d-1f468-1f3fd · 1f468-1f3fb-200d-1f430-200d-1f468-1f3fe · 1f468-1f3fb-200d-1f430-200d-1f468-1f3ff · 1f468-1f3fb-200d-1f4bb · 1f468-1f3fb-200d-1f4bc · 1f468-1f3fb-200d-1f527 · 1f468-1f3fb-200d-1f52c · 1f468-1f3fb-200d-1f680 · 1f468-1f3fb-200d-1f692 · 1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc · 1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd · 1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe · 1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff · 1f468-1f3fb-200d-1f9af · 1f468-1f3fb-200d-1f9af-200d-27a1-fe0f · 1f468-1f3fb-200d-1f9b0 · 1f468-1f3fb-200d-1f9b1 · 1f468-1f3fb-200d-1f9b2 · 1f468-1f3fb-200d-1f9b3 · 1f468-1f3fb-200d-1f9bc · 1f468-1f3fb-200d-1f9bc-200d-27a1-fe0f · 1f468-1f3fb-200d-1f9bd · 1f468-1f3fb-200d-1f9bd-200d-27a1-fe0f · 1f468-1f3fb-200d-1faef-200d-1f468-1f3fc · 1f468-1f3fb-200d-1faef-200d-1f468-1f3fd · 1f468-1f3fb-200d-1faef-200d-1f468-1f3fe · 1f468-1f3fb-200d-1faef-200d-1f468-1f3ff · 1f468-1f3fb-200d-2695-fe0f · 1f468-1f3fb-200d-2696-fe0f · 1f468-1f3fb-200d-2708-fe0f · 1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb · 1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc · 1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd · 1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe · 1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff · 1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb · 1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc · 1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd · 1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe · 1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff · 1f468-1f3fc · 1f468-1f3fc-200d-1f33e · 1f468-1f3fc-200d-1f373 · 1f468-1f3fc-200d-1f37c · 1f468-1f3fc-200d-1f384 · 1f468-1f3fc-200d-1f393 · 1f468-1f3fc-200d-1f3a4 · 1f468-1f3fc-200d-1f3a8 · 1f468-1f3fc-200d-1f3eb · 1f468-1f3fc-200d-1f3ed · 1f468-1f3fc-200d-1f430-200d-1f468-1f3fb · 1f468-1f3fc-200d-1f430-200d-1f468-1f3fd · 1f468-1f3fc-200d-1f430-200d-1f468-1f3fe · 1f468-1f3fc-200d-1f430-200d-1f468-1f3ff · 1f468-1f3fc-200d-1f4bb · 1f468-1f3fc-200d-1f4bc · 1f468-1f3fc-200d-1f527 · 1f468-1f3fc-200d-1f52c · 1f468-1f3fc-200d-1f680 · 1f468-1f3fc-200d-1f692 · 1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb · 1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd · 1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe · 1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff · 1f468-1f3fc-200d-1f9af · 1f468-1f3fc-200d-1f9af-200d-27a1-fe0f · 1f468-1f3fc-200d-1f9b0 · 1f468-1f3fc-200d-1f9b1 · 1f468-1f3fc-200d-1f9b2 · 1f468-1f3fc-200d-1f9b3 · 1f468-1f3fc-200d-1f9bc · 1f468-1f3fc-200d-1f9bc-200d-27a1-fe0f · 1f468-1f3fc-200d-1f9bd · 1f468-1f3fc-200d-1f9bd-200d-27a1-fe0f · 1f468-1f3fc-200d-1faef-200d-1f468-1f3fb · 1f468-1f3fc-200d-1faef-200d-1f468-1f3fd · 1f468-1f3fc-200d-1faef-200d-1f468-1f3fe · 1f468-1f3fc-200d-1faef-200d-1f468-1f3ff · 1f468-1f3fc-200d-2695-fe0f · 1f468-1f3fc-200d-2696-fe0f · 1f468-1f3fc-200d-2708-fe0f · 1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb · 1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc · 1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd · 1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe · 1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff · 1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb · 1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc · 1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd · 1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe · 1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff · 1f468-1f3fd · 1f468-1f3fd-200d-1f33e · 1f468-1f3fd-200d-1f373 · 1f468-1f3fd-200d-1f37c · 1f468-1f3fd-200d-1f384 · 1f468-1f3fd-200d-1f393 · 1f468-1f3fd-200d-1f3a4 · 1f468-1f3fd-200d-1f3a8 · 1f468-1f3fd-200d-1f3eb · 1f468-1f3fd-200d-1f3ed · 1f468-1f3fd-200d-1f430-200d-1f468-1f3fb · 1f468-1f3fd-200d-1f430-200d-1f468-1f3fc · 1f468-1f3fd-200d-1f430-200d-1f468-1f3fe · 1f468-1f3fd-200d-1f430-200d-1f468-1f3ff · 1f468-1f3fd-200d-1f4bb · 1f468-1f3fd-200d-1f4bc · 1f468-1f3fd-200d-1f527 · 1f468-1f3fd-200d-1f52c · 1f468-1f3fd-200d-1f680 · 1f468-1f3fd-200d-1f692 · 1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb · 1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc · 1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe · 1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff · 1f468-1f3fd-200d-1f9af · 1f468-1f3fd-200d-1f9af-200d-27a1-fe0f · 1f468-1f3fd-200d-1f9b0 · 1f468-1f3fd-200d-1f9b1 · 1f468-1f3fd-200d-1f9b2 · 1f468-1f3fd-200d-1f9b3 · 1f468-1f3fd-200d-1f9bc · 1f468-1f3fd-200d-1f9bc-200d-27a1-fe0f · 1f468-1f3fd-200d-1f9bd · 1f468-1f3fd-200d-1f9bd-200d-27a1-fe0f · 1f468-1f3fd-200d-1faef-200d-1f468-1f3fb · 1f468-1f3fd-200d-1faef-200d-1f468-1f3fc · 1f468-1f3fd-200d-1faef-200d-1f468-1f3fe · 1f468-1f3fd-200d-1faef-200d-1f468-1f3ff · 1f468-1f3fd-200d-2695-fe0f · 1f468-1f3fd-200d-2696-fe0f · 1f468-1f3fd-200d-2708-fe0f · 1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb · 1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc · 1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd · 1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe · 1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff · 1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb · 1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc · 1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd · 1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe · 1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff · 1f468-1f3fe · 1f468-1f3fe-200d-1f33e · 1f468-1f3fe-200d-1f373 · 1f468-1f3fe-200d-1f37c · 1f468-1f3fe-200d-1f384 · 1f468-1f3fe-200d-1f393 · 1f468-1f3fe-200d-1f3a4 · 1f468-1f3fe-200d-1f3a8 · 1f468-1f3fe-200d-1f3eb · 1f468-1f3fe-200d-1f3ed · 1f468-1f3fe-200d-1f430-200d-1f468-1f3fb · 1f468-1f3fe-200d-1f430-200d-1f468-1f3fc · 1f468-1f3fe-200d-1f430-200d-1f468-1f3fd · 1f468-1f3fe-200d-1f430-200d-1f468-1f3ff · 1f468-1f3fe-200d-1f4bb · 1f468-1f3fe-200d-1f4bc · 1f468-1f3fe-200d-1f527 · 1f468-1f3fe-200d-1f52c · 1f468-1f3fe-200d-1f680 · 1f468-1f3fe-200d-1f692 · 1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb · 1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc · 1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd · 1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff · 1f468-1f3fe-200d-1f9af · 1f468-1f3fe-200d-1f9af-200d-27a1-fe0f · 1f468-1f3fe-200d-1f9b0 · 1f468-1f3fe-200d-1f9b1 · 1f468-1f3fe-200d-1f9b2 · 1f468-1f3fe-200d-1f9b3 · 1f468-1f3fe-200d-1f9bc · 1f468-1f3fe-200d-1f9bc-200d-27a1-fe0f · 1f468-1f3fe-200d-1f9bd · 1f468-1f3fe-200d-1f9bd-200d-27a1-fe0f · 1f468-1f3fe-200d-1faef-200d-1f468-1f3fb · 1f468-1f3fe-200d-1faef-200d-1f468-1f3fc · 1f468-1f3fe-200d-1faef-200d-1f468-1f3fd · 1f468-1f3fe-200d-1faef-200d-1f468-1f3ff · 1f468-1f3fe-200d-2695-fe0f · 1f468-1f3fe-200d-2696-fe0f · 1f468-1f3fe-200d-2708-fe0f · 1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb · 1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc · 1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd · 1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe · 1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff · 1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb · 1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc · 1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd · 1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe · 1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff · 1f468-1f3ff · 1f468-1f3ff-200d-1f33e · 1f468-1f3ff-200d-1f373 · 1f468-1f3ff-200d-1f37c · 1f468-1f3ff-200d-1f384 · 1f468-1f3ff-200d-1f393 · 1f468-1f3ff-200d-1f3a4 · 1f468-1f3ff-200d-1f3a8 · 1f468-1f3ff-200d-1f3eb · 1f468-1f3ff-200d-1f3ed · 1f468-1f3ff-200d-1f430-200d-1f468-1f3fb · 1f468-1f3ff-200d-1f430-200d-1f468-1f3fc · 1f468-1f3ff-200d-1f430-200d-1f468-1f3fd · 1f468-1f3ff-200d-1f430-200d-1f468-1f3fe · 1f468-1f3ff-200d-1f4bb · 1f468-1f3ff-200d-1f4bc · 1f468-1f3ff-200d-1f527 · 1f468-1f3ff-200d-1f52c · 1f468-1f3ff-200d-1f680 · 1f468-1f3ff-200d-1f692 · 1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb · 1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc · 1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd · 1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe · 1f468-1f3ff-200d-1f9af · 1f468-1f3ff-200d-1f9af-200d-27a1-fe0f · 1f468-1f3ff-200d-1f9b0 · 1f468-1f3ff-200d-1f9b1 · 1f468-1f3ff-200d-1f9b2 · 1f468-1f3ff-200d-1f9b3 · 1f468-1f3ff-200d-1f9bc · 1f468-1f3ff-200d-1f9bc-200d-27a1-fe0f · 1f468-1f3ff-200d-1f9bd · 1f468-1f3ff-200d-1f9bd-200d-27a1-fe0f · 1f468-1f3ff-200d-1faef-200d-1f468-1f3fb · 1f468-1f3ff-200d-1faef-200d-1f468-1f3fc · 1f468-1f3ff-200d-1faef-200d-1f468-1f3fd · 1f468-1f3ff-200d-1faef-200d-1f468-1f3fe · 1f468-1f3ff-200d-2695-fe0f · 1f468-1f3ff-200d-2696-fe0f · 1f468-1f3ff-200d-2708-fe0f · 1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb · 1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc · 1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd · 1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe · 1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff · 1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb · 1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc · 1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd · 1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe · 1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff · 1f468-200d-1f33e · 1f468-200d-1f373 · 1f468-200d-1f37c · 1f468-200d-1f384 · 1f468-200d-1f393 · 1f468-200d-1f3a4 · 1f468-200d-1f3a8 · 1f468-200d-1f3eb · 1f468-200d-1f3ed · 1f468-200d-1f466 · 1f468-200d-1f466-200d-1f466 · 1f468-200d-1f467 · 1f468-200d-1f467-200d-1f466 · 1f468-200d-1f467-200d-1f467 · 1f468-200d-1f468-200d-1f466 · 1f468-200d-1f468-200d-1f466-200d-1f466 · 1f468-200d-1f468-200d-1f467 · 1f468-200d-1f468-200d-1f467-200d-1f466 · 1f468-200d-1f468-200d-1f467-200d-1f467 · 1f468-200d-1f469-200d-1f466 · 1f468-200d-1f469-200d-1f466-200d-1f466 · 1f468-200d-1f469-200d-1f467 · 1f468-200d-1f469-200d-1f467-200d-1f466 · 1f468-200d-1f469-200d-1f467-200d-1f467 · 1f468-200d-1f4bb · 1f468-200d-1f4bc · 1f468-200d-1f527 · 1f468-200d-1f52c · 1f468-200d-1f680 · 1f468-200d-1f692 · 1f468-200d-1f9af · 1f468-200d-1f9af-200d-27a1-fe0f · 1f468-200d-1f9b0 · 1f468-200d-1f9b1 · 1f468-200d-1f9b2 · 1f468-200d-1f9b3 · 1f468-200d-1f9bc · 1f468-200d-1f9bc-200d-27a1-fe0f · 1f468-200d-1f9bd · 1f468-200d-1f9bd-200d-27a1-fe0f · 1f468-200d-2695-fe0f · 1f468-200d-2696-fe0f · 1f468-200d-2708-fe0f · 1f468-200d-2764-fe0f-200d-1f468 · 1f468-200d-2764-fe0f-200d-1f48b-200d-1f468 · 1f469 · 1f469-1f3fb · 1f469-1f3fb-200d-1f33e · 1f469-1f3fb-200d-1f373 · 1f469-1f3fb-200d-1f37c · 1f469-1f3fb-200d-1f384 · 1f469-1f3fb-200d-1f393 · 1f469-1f3fb-200d-1f3a4 · 1f469-1f3fb-200d-1f3a8 · 1f469-1f3fb-200d-1f3eb · 1f469-1f3fb-200d-1f3ed · 1f469-1f3fb-200d-1f430-200d-1f469-1f3fc · 1f469-1f3fb-200d-1f430-200d-1f469-1f3fd · 1f469-1f3fb-200d-1f430-200d-1f469-1f3fe · 1f469-1f3fb-200d-1f430-200d-1f469-1f3ff · 1f469-1f3fb-200d-1f4bb · 1f469-1f3fb-200d-1f4bc · 1f469-1f3fb-200d-1f527 · 1f469-1f3fb-200d-1f52c · 1f469-1f3fb-200d-1f680 · 1f469-1f3fb-200d-1f692 · 1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc · 1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd · 1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe · 1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff · 1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc · 1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd · 1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe · 1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff · 1f469-1f3fb-200d-1f9af · 1f469-1f3fb-200d-1f9af-200d-27a1-fe0f · 1f469-1f3fb-200d-1f9b0 · 1f469-1f3fb-200d-1f9b1 · 1f469-1f3fb-200d-1f9b2 · 1f469-1f3fb-200d-1f9b3 · 1f469-1f3fb-200d-1f9bc · 1f469-1f3fb-200d-1f9bc-200d-27a1-fe0f · 1f469-1f3fb-200d-1f9bd · 1f469-1f3fb-200d-1f9bd-200d-27a1-fe0f · 1f469-1f3fb-200d-1faef-200d-1f469-1f3fc · 1f469-1f3fb-200d-1faef-200d-1f469-1f3fd · 1f469-1f3fb-200d-1faef-200d-1f469-1f3fe · 1f469-1f3fb-200d-1faef-200d-1f469-1f3ff · 1f469-1f3fb-200d-2695-fe0f · 1f469-1f3fb-200d-2696-fe0f · 1f469-1f3fb-200d-2708-fe0f · 1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb · 1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc · 1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd · 1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe · 1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff · 1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb · 1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc · 1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd · 1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe · 1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff · 1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb · 1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc · 1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd · 1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe · 1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff · 1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb · 1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc · 1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd · 1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe · 1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff · 1f469-1f3fc · 1f469-1f3fc-200d-1f33e · 1f469-1f3fc-200d-1f373 · 1f469-1f3fc-200d-1f37c · 1f469-1f3fc-200d-1f384 · 1f469-1f3fc-200d-1f393 · 1f469-1f3fc-200d-1f3a4 · 1f469-1f3fc-200d-1f3a8 · 1f469-1f3fc-200d-1f3eb · 1f469-1f3fc-200d-1f3ed · 1f469-1f3fc-200d-1f430-200d-1f469-1f3fb · 1f469-1f3fc-200d-1f430-200d-1f469-1f3fd · 1f469-1f3fc-200d-1f430-200d-1f469-1f3fe · 1f469-1f3fc-200d-1f430-200d-1f469-1f3ff · 1f469-1f3fc-200d-1f4bb · 1f469-1f3fc-200d-1f4bc · 1f469-1f3fc-200d-1f527 · 1f469-1f3fc-200d-1f52c · 1f469-1f3fc-200d-1f680 · 1f469-1f3fc-200d-1f692 · 1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb · 1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd · 1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe · 1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff · 1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb · 1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd · 1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe · 1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff · 1f469-1f3fc-200d-1f9af · 1f469-1f3fc-200d-1f9af-200d-27a1-fe0f · 1f469-1f3fc-200d-1f9b0 · 1f469-1f3fc-200d-1f9b1 · 1f469-1f3fc-200d-1f9b2 · 1f469-1f3fc-200d-1f9b3 · 1f469-1f3fc-200d-1f9bc · 1f469-1f3fc-200d-1f9bc-200d-27a1-fe0f · 1f469-1f3fc-200d-1f9bd · 1f469-1f3fc-200d-1f9bd-200d-27a1-fe0f · 1f469-1f3fc-200d-1faef-200d-1f469-1f3fb · 1f469-1f3fc-200d-1faef-200d-1f469-1f3fd · 1f469-1f3fc-200d-1faef-200d-1f469-1f3fe · 1f469-1f3fc-200d-1faef-200d-1f469-1f3ff · 1f469-1f3fc-200d-2695-fe0f · 1f469-1f3fc-200d-2696-fe0f · 1f469-1f3fc-200d-2708-fe0f · 1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb · 1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc · 1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd · 1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe · 1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff · 1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb · 1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc · 1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd · 1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe · 1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff · 1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb · 1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc · 1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd · 1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe · 1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff · 1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb · 1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc · 1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd · 1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe · 1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff · 1f469-1f3fd · 1f469-1f3fd-200d-1f33e · 1f469-1f3fd-200d-1f373 · 1f469-1f3fd-200d-1f37c · 1f469-1f3fd-200d-1f384 · 1f469-1f3fd-200d-1f393 · 1f469-1f3fd-200d-1f3a4 · 1f469-1f3fd-200d-1f3a8 · 1f469-1f3fd-200d-1f3eb · 1f469-1f3fd-200d-1f3ed · 1f469-1f3fd-200d-1f430-200d-1f469-1f3fb · 1f469-1f3fd-200d-1f430-200d-1f469-1f3fc · 1f469-1f3fd-200d-1f430-200d-1f469-1f3fe · 1f469-1f3fd-200d-1f430-200d-1f469-1f3ff · 1f469-1f3fd-200d-1f4bb · 1f469-1f3fd-200d-1f4bc · 1f469-1f3fd-200d-1f527 · 1f469-1f3fd-200d-1f52c · 1f469-1f3fd-200d-1f680 · 1f469-1f3fd-200d-1f692 · 1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb · 1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc · 1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe · 1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff · 1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb · 1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc · 1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe · 1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff · 1f469-1f3fd-200d-1f9af · 1f469-1f3fd-200d-1f9af-200d-27a1-fe0f · 1f469-1f3fd-200d-1f9b0 · 1f469-1f3fd-200d-1f9b1 · 1f469-1f3fd-200d-1f9b2 · 1f469-1f3fd-200d-1f9b3 · 1f469-1f3fd-200d-1f9bc · 1f469-1f3fd-200d-1f9bc-200d-27a1-fe0f · 1f469-1f3fd-200d-1f9bd · 1f469-1f3fd-200d-1f9bd-200d-27a1-fe0f · 1f469-1f3fd-200d-1faef-200d-1f469-1f3fb · 1f469-1f3fd-200d-1faef-200d-1f469-1f3fc · 1f469-1f3fd-200d-1faef-200d-1f469-1f3fe · 1f469-1f3fd-200d-1faef-200d-1f469-1f3ff · 1f469-1f3fd-200d-2695-fe0f · 1f469-1f3fd-200d-2696-fe0f · 1f469-1f3fd-200d-2708-fe0f · 1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb · 1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc · 1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd · 1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe · 1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff · 1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb · 1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc · 1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd · 1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe · 1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff · 1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb · 1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc · 1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd · 1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe · 1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff · 1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb · 1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc · 1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd · 1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe · 1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff · 1f469-1f3fe · 1f469-1f3fe-200d-1f33e · 1f469-1f3fe-200d-1f373 · 1f469-1f3fe-200d-1f37c · 1f469-1f3fe-200d-1f384 · 1f469-1f3fe-200d-1f393 · 1f469-1f3fe-200d-1f3a4 · 1f469-1f3fe-200d-1f3a8 · 1f469-1f3fe-200d-1f3eb · 1f469-1f3fe-200d-1f3ed · 1f469-1f3fe-200d-1f430-200d-1f469-1f3fb · 1f469-1f3fe-200d-1f430-200d-1f469-1f3fc · 1f469-1f3fe-200d-1f430-200d-1f469-1f3fd · 1f469-1f3fe-200d-1f430-200d-1f469-1f3ff · 1f469-1f3fe-200d-1f4bb · 1f469-1f3fe-200d-1f4bc · 1f469-1f3fe-200d-1f527 · 1f469-1f3fe-200d-1f52c · 1f469-1f3fe-200d-1f680 · 1f469-1f3fe-200d-1f692 · 1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb · 1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc · 1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd · 1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff · 1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb · 1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc · 1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd · 1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff · 1f469-1f3fe-200d-1f9af · 1f469-1f3fe-200d-1f9af-200d-27a1-fe0f · 1f469-1f3fe-200d-1f9b0 · 1f469-1f3fe-200d-1f9b1 · 1f469-1f3fe-200d-1f9b2 · 1f469-1f3fe-200d-1f9b3 · 1f469-1f3fe-200d-1f9bc · 1f469-1f3fe-200d-1f9bc-200d-27a1-fe0f · 1f469-1f3fe-200d-1f9bd · 1f469-1f3fe-200d-1f9bd-200d-27a1-fe0f · 1f469-1f3fe-200d-1faef-200d-1f469-1f3fb · 1f469-1f3fe-200d-1faef-200d-1f469-1f3fc · 1f469-1f3fe-200d-1faef-200d-1f469-1f3fd · 1f469-1f3fe-200d-1faef-200d-1f469-1f3ff · 1f469-1f3fe-200d-2695-fe0f · 1f469-1f3fe-200d-2696-fe0f · 1f469-1f3fe-200d-2708-fe0f · 1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb · 1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc · 1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd · 1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe · 1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff · 1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb · 1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc · 1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd · 1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe · 1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff · 1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb · 1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc · 1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd · 1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe · 1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff · 1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb · 1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc · 1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd · 1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe · 1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff · 1f469-1f3ff · 1f469-1f3ff-200d-1f33e · 1f469-1f3ff-200d-1f373 · 1f469-1f3ff-200d-1f37c · 1f469-1f3ff-200d-1f384 · 1f469-1f3ff-200d-1f393 · 1f469-1f3ff-200d-1f3a4 · 1f469-1f3ff-200d-1f3a8 · 1f469-1f3ff-200d-1f3eb · 1f469-1f3ff-200d-1f3ed · 1f469-1f3ff-200d-1f430-200d-1f469-1f3fb · 1f469-1f3ff-200d-1f430-200d-1f469-1f3fc · 1f469-1f3ff-200d-1f430-200d-1f469-1f3fd · 1f469-1f3ff-200d-1f430-200d-1f469-1f3fe · 1f469-1f3ff-200d-1f4bb · 1f469-1f3ff-200d-1f4bc · 1f469-1f3ff-200d-1f527 · 1f469-1f3ff-200d-1f52c · 1f469-1f3ff-200d-1f680 · 1f469-1f3ff-200d-1f692 · 1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb · 1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc · 1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd · 1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe · 1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb · 1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc · 1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd · 1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe · 1f469-1f3ff-200d-1f9af · 1f469-1f3ff-200d-1f9af-200d-27a1-fe0f · 1f469-1f3ff-200d-1f9b0 · 1f469-1f3ff-200d-1f9b1 · 1f469-1f3ff-200d-1f9b2 · 1f469-1f3ff-200d-1f9b3 · 1f469-1f3ff-200d-1f9bc · 1f469-1f3ff-200d-1f9bc-200d-27a1-fe0f · 1f469-1f3ff-200d-1f9bd · 1f469-1f3ff-200d-1f9bd-200d-27a1-fe0f · 1f469-1f3ff-200d-1faef-200d-1f469-1f3fb · 1f469-1f3ff-200d-1faef-200d-1f469-1f3fc · 1f469-1f3ff-200d-1faef-200d-1f469-1f3fd · 1f469-1f3ff-200d-1faef-200d-1f469-1f3fe · 1f469-1f3ff-200d-2695-fe0f · 1f469-1f3ff-200d-2696-fe0f · 1f469-1f3ff-200d-2708-fe0f · 1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb · 1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc · 1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd · 1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe · 1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff · 1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb · 1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc · 1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd · 1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe · 1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff · 1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb · 1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc · 1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd · 1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe · 1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff · 1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb · 1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc · 1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd · 1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe · 1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff · 1f469-200d-1f33e · 1f469-200d-1f373 · 1f469-200d-1f37c · 1f469-200d-1f384 · 1f469-200d-1f393 · 1f469-200d-1f3a4 · 1f469-200d-1f3a8 · 1f469-200d-1f3eb · 1f469-200d-1f3ed · 1f469-200d-1f466 · 1f469-200d-1f466-200d-1f466 · 1f469-200d-1f467 · 1f469-200d-1f467-200d-1f466 · 1f469-200d-1f467-200d-1f467 · 1f469-200d-1f469-200d-1f466 · 1f469-200d-1f469-200d-1f466-200d-1f466 · 1f469-200d-1f469-200d-1f467 · 1f469-200d-1f469-200d-1f467-200d-1f466 · 1f469-200d-1f469-200d-1f467-200d-1f467 · 1f469-200d-1f4bb · 1f469-200d-1f4bc · 1f469-200d-1f527 · 1f469-200d-1f52c · 1f469-200d-1f680 · 1f469-200d-1f692 · 1f469-200d-1f9af · 1f469-200d-1f9af-200d-27a1-fe0f · 1f469-200d-1f9b0 · 1f469-200d-1f9b1 · 1f469-200d-1f9b2 · 1f469-200d-1f9b3 · 1f469-200d-1f9bc · 1f469-200d-1f9bc-200d-27a1-fe0f · 1f469-200d-1f9bd · 1f469-200d-1f9bd-200d-27a1-fe0f · 1f469-200d-2695-fe0f · 1f469-200d-2696-fe0f · 1f469-200d-2708-fe0f · 1f469-200d-2764-fe0f-200d-1f468 · 1f469-200d-2764-fe0f-200d-1f469 · 1f469-200d-2764-fe0f-200d-1f48b-200d-1f468 · 1f469-200d-2764-fe0f-200d-1f48b-200d-1f469 · 1f46a · 1f46b · 1f46b-1f3fb · 1f46b-1f3fc · 1f46b-1f3fd · 1f46b-1f3fe · 1f46b-1f3ff · 1f46c · 1f46c-1f3fb · 1f46c-1f3fc · 1f46c-1f3fd · 1f46c-1f3fe · 1f46c-1f3ff · 1f46d · 1f46d-1f3fb · 1f46d-1f3fc · 1f46d-1f3fd · 1f46d-1f3fe · 1f46d-1f3ff · 1f46e · 1f46e-1f3fb · 1f46e-1f3fb-200d-2640-fe0f · 1f46e-1f3fb-200d-2642-fe0f · 1f46e-1f3fc · 1f46e-1f3fc-200d-2640-fe0f · 1f46e-1f3fc-200d-2642-fe0f · 1f46e-1f3fd · 1f46e-1f3fd-200d-2640-fe0f · 1f46e-1f3fd-200d-2642-fe0f · 1f46e-1f3fe · 1f46e-1f3fe-200d-2640-fe0f · 1f46e-1f3fe-200d-2642-fe0f · 1f46e-1f3ff · 1f46e-1f3ff-200d-2640-fe0f · 1f46e-1f3ff-200d-2642-fe0f · 1f46e-200d-2640-fe0f · 1f46e-200d-2642-fe0f · 1f46f · 1f46f-1f3fb · 1f46f-1f3fb-200d-2640-fe0f · 1f46f-1f3fb-200d-2642-fe0f · 1f46f-1f3fc · 1f46f-1f3fc-200d-2640-fe0f · 1f46f-1f3fc-200d-2642-fe0f · 1f46f-1f3fd · 1f46f-1f3fd-200d-2640-fe0f · 1f46f-1f3fd-200d-2642-fe0f · 1f46f-1f3fe · 1f46f-1f3fe-200d-2640-fe0f · 1f46f-1f3fe-200d-2642-fe0f · 1f46f-1f3ff · 1f46f-1f3ff-200d-2640-fe0f · 1f46f-1f3ff-200d-2642-fe0f · 1f46f-200d-2640-fe0f · 1f46f-200d-2642-fe0f · 1f470 · 1f470-1f3fb · 1f470-1f3fb-200d-2640-fe0f · 1f470-1f3fb-200d-2642-fe0f · 1f470-1f3fc · 1f470-1f3fc-200d-2640-fe0f · 1f470-1f3fc-200d-2642-fe0f · 1f470-1f3fd · 1f470-1f3fd-200d-2640-fe0f · 1f470-1f3fd-200d-2642-fe0f · 1f470-1f3fe · 1f470-1f3fe-200d-2640-fe0f · 1f470-1f3fe-200d-2642-fe0f · 1f470-1f3ff · 1f470-1f3ff-200d-2640-fe0f · 1f470-1f3ff-200d-2642-fe0f · 1f470-200d-2640-fe0f · 1f470-200d-2642-fe0f · 1f471 · 1f471-1f3fb · 1f471-1f3fb-200d-2640-fe0f · 1f471-1f3fb-200d-2642-fe0f · 1f471-1f3fc · 1f471-1f3fc-200d-2640-fe0f · 1f471-1f3fc-200d-2642-fe0f · 1f471-1f3fd · 1f471-1f3fd-200d-2640-fe0f · 1f471-1f3fd-200d-2642-fe0f · 1f471-1f3fe · 1f471-1f3fe-200d-2640-fe0f · 1f471-1f3fe-200d-2642-fe0f · 1f471-1f3ff · 1f471-1f3ff-200d-2640-fe0f · 1f471-1f3ff-200d-2642-fe0f · 1f471-200d-2640-fe0f · 1f471-200d-2642-fe0f · 1f472 · 1f472-1f3fb · 1f472-1f3fc · 1f472-1f3fd · 1f472-1f3fe · 1f472-1f3ff · 1f473 · 1f473-1f3fb · 1f473-1f3fb-200d-2640-fe0f · 1f473-1f3fb-200d-2642-fe0f · 1f473-1f3fc · 1f473-1f3fc-200d-2640-fe0f · 1f473-1f3fc-200d-2642-fe0f · 1f473-1f3fd · 1f473-1f3fd-200d-2640-fe0f · 1f473-1f3fd-200d-2642-fe0f · 1f473-1f3fe · 1f473-1f3fe-200d-2640-fe0f · 1f473-1f3fe-200d-2642-fe0f · 1f473-1f3ff · 1f473-1f3ff-200d-2640-fe0f · 1f473-1f3ff-200d-2642-fe0f · 1f473-200d-2640-fe0f · 1f473-200d-2642-fe0f · 1f474 · 1f474-1f3fb · 1f474-1f3fc · 1f474-1f3fd · 1f474-1f3fe · 1f474-1f3ff · 1f475 · 1f475-1f3fb · 1f475-1f3fc · 1f475-1f3fd · 1f475-1f3fe · 1f475-1f3ff · 1f476 · 1f476-1f3fb · 1f476-1f3fc · 1f476-1f3fd · 1f476-1f3fe · 1f476-1f3ff · 1f477 · 1f477-1f3fb · 1f477-1f3fb-200d-2640-fe0f · 1f477-1f3fb-200d-2642-fe0f · 1f477-1f3fc · 1f477-1f3fc-200d-2640-fe0f · 1f477-1f3fc-200d-2642-fe0f · 1f477-1f3fd · 1f477-1f3fd-200d-2640-fe0f · 1f477-1f3fd-200d-2642-fe0f · 1f477-1f3fe · 1f477-1f3fe-200d-2640-fe0f · 1f477-1f3fe-200d-2642-fe0f · 1f477-1f3ff · 1f477-1f3ff-200d-2640-fe0f · 1f477-1f3ff-200d-2642-fe0f · 1f477-200d-2640-fe0f · 1f477-200d-2642-fe0f · 1f478 · 1f478-1f3fb · 1f478-1f3fc · 1f478-1f3fd · 1f478-1f3fe · 1f478-1f3ff · 1f479 · 1f47a · 1f47b · 1f47c · 1f47c-1f3fb · 1f47c-1f3fc · 1f47c-1f3fd · 1f47c-1f3fe · 1f47c-1f3ff · 1f47d · 1f47e · 1f47f · 1f480 · 1f481 · 1f481-1f3fb · 1f481-1f3fb-200d-2640-fe0f · 1f481-1f3fb-200d-2642-fe0f · 1f481-1f3fc · 1f481-1f3fc-200d-2640-fe0f · 1f481-1f3fc-200d-2642-fe0f · 1f481-1f3fd · 1f481-1f3fd-200d-2640-fe0f · 1f481-1f3fd-200d-2642-fe0f · 1f481-1f3fe · 1f481-1f3fe-200d-2640-fe0f · 1f481-1f3fe-200d-2642-fe0f · 1f481-1f3ff · 1f481-1f3ff-200d-2640-fe0f · 1f481-1f3ff-200d-2642-fe0f · 1f481-200d-2640-fe0f · 1f481-200d-2642-fe0f · 1f482 · 1f482-1f3fb · 1f482-1f3fb-200d-2640-fe0f · 1f482-1f3fb-200d-2642-fe0f · 1f482-1f3fc · 1f482-1f3fc-200d-2640-fe0f · 1f482-1f3fc-200d-2642-fe0f · 1f482-1f3fd · 1f482-1f3fd-200d-2640-fe0f · 1f482-1f3fd-200d-2642-fe0f · 1f482-1f3fe · 1f482-1f3fe-200d-2640-fe0f · 1f482-1f3fe-200d-2642-fe0f · 1f482-1f3ff · 1f482-1f3ff-200d-2640-fe0f · 1f482-1f3ff-200d-2642-fe0f · 1f482-200d-2640-fe0f · 1f482-200d-2642-fe0f · 1f483 · 1f483-1f3fb · 1f483-1f3fc · 1f483-1f3fd · 1f483-1f3fe · 1f483-1f3ff · 1f484 · 1f485 · 1f485-1f3fb · 1f485-1f3fc · 1f485-1f3fd · 1f485-1f3fe · 1f485-1f3ff · 1f486 · 1f486-1f3fb · 1f486-1f3fb-200d-2640-fe0f · 1f486-1f3fb-200d-2642-fe0f · 1f486-1f3fc · 1f486-1f3fc-200d-2640-fe0f · 1f486-1f3fc-200d-2642-fe0f · 1f486-1f3fd · 1f486-1f3fd-200d-2640-fe0f · 1f486-1f3fd-200d-2642-fe0f · 1f486-1f3fe · 1f486-1f3fe-200d-2640-fe0f · 1f486-1f3fe-200d-2642-fe0f · 1f486-1f3ff · 1f486-1f3ff-200d-2640-fe0f · 1f486-1f3ff-200d-2642-fe0f · 1f486-200d-2640-fe0f · 1f486-200d-2642-fe0f · 1f487 · 1f487-1f3fb · 1f487-1f3fb-200d-2640-fe0f · 1f487-1f3fb-200d-2642-fe0f · 1f487-1f3fc · 1f487-1f3fc-200d-2640-fe0f · 1f487-1f3fc-200d-2642-fe0f · 1f487-1f3fd · 1f487-1f3fd-200d-2640-fe0f · 1f487-1f3fd-200d-2642-fe0f · 1f487-1f3fe · 1f487-1f3fe-200d-2640-fe0f · 1f487-1f3fe-200d-2642-fe0f · 1f487-1f3ff · 1f487-1f3ff-200d-2640-fe0f · 1f487-1f3ff-200d-2642-fe0f · 1f487-200d-2640-fe0f · 1f487-200d-2642-fe0f · 1f488 · 1f489 · 1f48a · 1f48b · 1f48c · 1f48d · 1f48e · 1f48f · 1f48f-1f3fb · 1f48f-1f3fc · 1f48f-1f3fd · 1f48f-1f3fe · 1f48f-1f3ff · 1f490 · 1f491 · 1f491-1f3fb · 1f491-1f3fc · 1f491-1f3fd · 1f491-1f3fe · 1f491-1f3ff · 1f492 · 1f493 · 1f494 · 1f495 · 1f496 · 1f497 · 1f498 · 1f499 · 1f49a · 1f49b · 1f49c · 1f49d · 1f49e · 1f49f · 1f4a0 · 1f4a1 · 1f4a2 · 1f4a3 · 1f4a4 · 1f4a5 · 1f4a6 · 1f4a7 · 1f4a8 · 1f4a9 · 1f4aa · 1f4aa-1f3fb · 1f4aa-1f3fc · 1f4aa-1f3fd · 1f4aa-1f3fe · 1f4aa-1f3ff · 1f4ab · 1f4ac · 1f4ad · 1f4ae · 1f4af · 1f4b0 · 1f4b1 · 1f4b2 · 1f4b3 · 1f4b4 · 1f4b5 · 1f4b6 · 1f4b7 · 1f4b8 · 1f4b9 · 1f4ba · 1f4bb · 1f4bc · 1f4bd · 1f4be · 1f4bf · 1f4c0 · 1f4c1 · 1f4c2 · 1f4c3 · 1f4c4 · 1f4c5 · 1f4c6 · 1f4c7 · 1f4c8 · 1f4c9 · 1f4ca · 1f4cb · 1f4cc · 1f4cd · 1f4ce · 1f4cf · 1f4d0 · 1f4d1 · 1f4d2 · 1f4d3 · 1f4d4 · 1f4d5 · 1f4d6 · 1f4d7 · 1f4d8 · 1f4d9 · 1f4da · 1f4db · 1f4dc · 1f4dd · 1f4de · 1f4df · 1f4e0 · 1f4e1 · 1f4e2 · 1f4e3 · 1f4e4 · 1f4e5 · 1f4e6 · 1f4e7 · 1f4e8 · 1f4e9 · 1f4ea · 1f4eb · 1f4ec · 1f4ed · 1f4ee · 1f4ef · 1f4f0 · 1f4f1 · 1f4f2 · 1f4f3 · 1f4f4 · 1f4f5 · 1f4f6 · 1f4f7 · 1f4f8 · 1f4f9 · 1f4fa · 1f4fb · 1f4fc · 1f4fd · 1f4ff · 1f500 · 1f501 · 1f502 · 1f503 · 1f504 · 1f505 · 1f506 · 1f507 · 1f508 · 1f509 · 1f50a · 1f50b · 1f50c · 1f50d · 1f50e · 1f50f · 1f510 · 1f511 · 1f512 · 1f513 · 1f514 · 1f515 · 1f516 · 1f517 · 1f518 · 1f519 · 1f51a · 1f51b · 1f51c · 1f51d · 1f51e · 1f51f · 1f520 · 1f521 · 1f522 · 1f523 · 1f524 · 1f525 · 1f526 · 1f527 · 1f528 · 1f529 · 1f52a · 1f52b · 1f52c · 1f52d · 1f52e · 1f52f · 1f530 · 1f531 · 1f532 · 1f533 · 1f534 · 1f535 · 1f536 · 1f537 · 1f538 · 1f539 · 1f53a · 1f53b · 1f53c · 1f53d · 1f549 · 1f54a · 1f54b · 1f54c · 1f54d · 1f54e · 1f550 · 1f551 · 1f552 · 1f553 · 1f554 · 1f555 · 1f556 · 1f557 · 1f558 · 1f559 · 1f55a · 1f55b · 1f55c · 1f55d · 1f55e · 1f55f · 1f560 · 1f561 · 1f562 · 1f563 · 1f564 · 1f565 · 1f566 · 1f567 · 1f56f · 1f570 · 1f573 · 1f574 · 1f574-1f3fb · 1f574-1f3fb-200d-2640-fe0f · 1f574-1f3fb-200d-2642-fe0f · 1f574-1f3fc · 1f574-1f3fc-200d-2640-fe0f · 1f574-1f3fc-200d-2642-fe0f · 1f574-1f3fd · 1f574-1f3fd-200d-2640-fe0f · 1f574-1f3fd-200d-2642-fe0f · 1f574-1f3fe · 1f574-1f3fe-200d-2640-fe0f · 1f574-1f3fe-200d-2642-fe0f · 1f574-1f3ff · 1f574-1f3ff-200d-2640-fe0f · 1f574-1f3ff-200d-2642-fe0f · 1f574-fe0f-200d-2640-fe0f · 1f574-fe0f-200d-2642-fe0f · 1f575 · 1f575-1f3fb · 1f575-1f3fb-200d-2640-fe0f · 1f575-1f3fb-200d-2642-fe0f · 1f575-1f3fc · 1f575-1f3fc-200d-2640-fe0f · 1f575-1f3fc-200d-2642-fe0f · 1f575-1f3fd · 1f575-1f3fd-200d-2640-fe0f · 1f575-1f3fd-200d-2642-fe0f · 1f575-1f3fe · 1f575-1f3fe-200d-2640-fe0f · 1f575-1f3fe-200d-2642-fe0f · 1f575-1f3ff · 1f575-1f3ff-200d-2640-fe0f · 1f575-1f3ff-200d-2642-fe0f · 1f575-fe0f-200d-2640-fe0f · 1f575-fe0f-200d-2642-fe0f · 1f576 · 1f577 · 1f578 · 1f579 · 1f57a · 1f57a-1f3fb · 1f57a-1f3fc · 1f57a-1f3fd · 1f57a-1f3fe · 1f57a-1f3ff · 1f587 · 1f58a · 1f58b · 1f58c · 1f58d · 1f590 · 1f590-1f3fb · 1f590-1f3fc · 1f590-1f3fd · 1f590-1f3fe · 1f590-1f3ff · 1f595 · 1f595-1f3fb · 1f595-1f3fc · 1f595-1f3fd · 1f595-1f3fe · 1f595-1f3ff · 1f596 · 1f596-1f3fb · 1f596-1f3fc · 1f596-1f3fd · 1f596-1f3fe · 1f596-1f3ff · 1f5a4 · 1f5a5 · 1f5a8 · 1f5b1 · 1f5b2 · 1f5bc · 1f5c2 · 1f5c3 · 1f5c4 · 1f5d1 · 1f5d2 · 1f5d3 · 1f5dc · 1f5dd · 1f5de · 1f5e1 · 1f5e3 · 1f5e8 · 1f5ef · 1f5f3 · 1f5fa · 1f5fb · 1f5fc · 1f5fd · 1f5fe · 1f5ff · 1f600 · 1f601 · 1f602 · 1f603 · 1f604 · 1f605 · 1f606 · 1f607 · 1f608 · 1f609 · 1f60a · 1f60b · 1f60c · 1f60d · 1f60e · 1f60f · 1f610 · 1f611 · 1f612 · 1f613 · 1f614 · 1f615 · 1f616 · 1f617 · 1f618 · 1f619 · 1f61a · 1f61b · 1f61c · 1f61d · 1f61e · 1f61f · 1f620 · 1f621 · 1f622 · 1f623 · 1f624 · 1f625 · 1f626 · 1f627 · 1f628 · 1f629 · 1f62a · 1f62b · 1f62c · 1f62d · 1f62e · 1f62e-200d-1f4a8 · 1f62f · 1f630 · 1f631 · 1f632 · 1f633 · 1f634 · 1f635 · 1f635-200d-1f4ab · 1f636 · 1f636-200d-1f32b-fe0f · 1f637 · 1f638 · 1f639 · 1f63a · 1f63b · 1f63c · 1f63d · 1f63e · 1f63f · 1f640 · 1f641 · 1f642 · 1f642-200d-2194-fe0f · 1f642-200d-2195-fe0f · 1f643 · 1f644 · 1f645 · 1f645-1f3fb · 1f645-1f3fb-200d-2640-fe0f · 1f645-1f3fb-200d-2642-fe0f · 1f645-1f3fc · 1f645-1f3fc-200d-2640-fe0f · 1f645-1f3fc-200d-2642-fe0f · 1f645-1f3fd · 1f645-1f3fd-200d-2640-fe0f · 1f645-1f3fd-200d-2642-fe0f · 1f645-1f3fe · 1f645-1f3fe-200d-2640-fe0f · 1f645-1f3fe-200d-2642-fe0f · 1f645-1f3ff · 1f645-1f3ff-200d-2640-fe0f · 1f645-1f3ff-200d-2642-fe0f · 1f645-200d-2640-fe0f · 1f645-200d-2642-fe0f · 1f646 · 1f646-1f3fb · 1f646-1f3fb-200d-2640-fe0f · 1f646-1f3fb-200d-2642-fe0f · 1f646-1f3fc · 1f646-1f3fc-200d-2640-fe0f · 1f646-1f3fc-200d-2642-fe0f · 1f646-1f3fd · 1f646-1f3fd-200d-2640-fe0f · 1f646-1f3fd-200d-2642-fe0f · 1f646-1f3fe · 1f646-1f3fe-200d-2640-fe0f · 1f646-1f3fe-200d-2642-fe0f · 1f646-1f3ff · 1f646-1f3ff-200d-2640-fe0f · 1f646-1f3ff-200d-2642-fe0f · 1f646-200d-2640-fe0f · 1f646-200d-2642-fe0f · 1f647 · 1f647-1f3fb · 1f647-1f3fb-200d-2640-fe0f · 1f647-1f3fb-200d-2642-fe0f · 1f647-1f3fc · 1f647-1f3fc-200d-2640-fe0f · 1f647-1f3fc-200d-2642-fe0f · 1f647-1f3fd · 1f647-1f3fd-200d-2640-fe0f · 1f647-1f3fd-200d-2642-fe0f · 1f647-1f3fe · 1f647-1f3fe-200d-2640-fe0f · 1f647-1f3fe-200d-2642-fe0f · 1f647-1f3ff · 1f647-1f3ff-200d-2640-fe0f · 1f647-1f3ff-200d-2642-fe0f · 1f647-200d-2640-fe0f · 1f647-200d-2642-fe0f · 1f648 · 1f649 · 1f64a · 1f64b · 1f64b-1f3fb · 1f64b-1f3fb-200d-2640-fe0f · 1f64b-1f3fb-200d-2642-fe0f · 1f64b-1f3fc · 1f64b-1f3fc-200d-2640-fe0f · 1f64b-1f3fc-200d-2642-fe0f · 1f64b-1f3fd · 1f64b-1f3fd-200d-2640-fe0f · 1f64b-1f3fd-200d-2642-fe0f · 1f64b-1f3fe · 1f64b-1f3fe-200d-2640-fe0f · 1f64b-1f3fe-200d-2642-fe0f · 1f64b-1f3ff · 1f64b-1f3ff-200d-2640-fe0f · 1f64b-1f3ff-200d-2642-fe0f · 1f64b-200d-2640-fe0f · 1f64b-200d-2642-fe0f · 1f64c · 1f64c-1f3fb · 1f64c-1f3fc · 1f64c-1f3fd · 1f64c-1f3fe · 1f64c-1f3ff · 1f64d · 1f64d-1f3fb · 1f64d-1f3fb-200d-2640-fe0f · 1f64d-1f3fb-200d-2642-fe0f · 1f64d-1f3fc · 1f64d-1f3fc-200d-2640-fe0f · 1f64d-1f3fc-200d-2642-fe0f · 1f64d-1f3fd · 1f64d-1f3fd-200d-2640-fe0f · 1f64d-1f3fd-200d-2642-fe0f · 1f64d-1f3fe · 1f64d-1f3fe-200d-2640-fe0f · 1f64d-1f3fe-200d-2642-fe0f · 1f64d-1f3ff · 1f64d-1f3ff-200d-2640-fe0f · 1f64d-1f3ff-200d-2642-fe0f · 1f64d-200d-2640-fe0f · 1f64d-200d-2642-fe0f · 1f64e · 1f64e-1f3fb · 1f64e-1f3fb-200d-2640-fe0f · 1f64e-1f3fb-200d-2642-fe0f · 1f64e-1f3fc · 1f64e-1f3fc-200d-2640-fe0f · 1f64e-1f3fc-200d-2642-fe0f · 1f64e-1f3fd · 1f64e-1f3fd-200d-2640-fe0f · 1f64e-1f3fd-200d-2642-fe0f · 1f64e-1f3fe · 1f64e-1f3fe-200d-2640-fe0f · 1f64e-1f3fe-200d-2642-fe0f · 1f64e-1f3ff · 1f64e-1f3ff-200d-2640-fe0f · 1f64e-1f3ff-200d-2642-fe0f · 1f64e-200d-2640-fe0f · 1f64e-200d-2642-fe0f · 1f64f · 1f64f-1f3fb · 1f64f-1f3fc · 1f64f-1f3fd · 1f64f-1f3fe · 1f64f-1f3ff · 1f680 · 1f681 · 1f682 · 1f683 · 1f684 · 1f685 · 1f686 · 1f687 · 1f688 · 1f689 · 1f68a · 1f68b · 1f68c · 1f68d · 1f68e · 1f68f · 1f690 · 1f691 · 1f692 · 1f693 · 1f694 · 1f695 · 1f696 · 1f697 · 1f698 · 1f699 · 1f69a · 1f69b · 1f69c · 1f69d · 1f69e · 1f69f · 1f6a0 · 1f6a1 · 1f6a2 · 1f6a3 · 1f6a3-1f3fb · 1f6a3-1f3fb-200d-2640-fe0f · 1f6a3-1f3fb-200d-2642-fe0f · 1f6a3-1f3fc · 1f6a3-1f3fc-200d-2640-fe0f · 1f6a3-1f3fc-200d-2642-fe0f · 1f6a3-1f3fd · 1f6a3-1f3fd-200d-2640-fe0f · 1f6a3-1f3fd-200d-2642-fe0f · 1f6a3-1f3fe · 1f6a3-1f3fe-200d-2640-fe0f · 1f6a3-1f3fe-200d-2642-fe0f · 1f6a3-1f3ff · 1f6a3-1f3ff-200d-2640-fe0f · 1f6a3-1f3ff-200d-2642-fe0f · 1f6a3-200d-2640-fe0f · 1f6a3-200d-2642-fe0f · 1f6a4 · 1f6a5 · 1f6a6 · 1f6a7 · 1f6a8 · 1f6a9 · 1f6aa · 1f6ab · 1f6ac · 1f6ad · 1f6ae · 1f6af · 1f6b0 · 1f6b1 · 1f6b2 · 1f6b3 · 1f6b4 · 1f6b4-1f3fb · 1f6b4-1f3fb-200d-2640-fe0f · 1f6b4-1f3fb-200d-2642-fe0f · 1f6b4-1f3fc · 1f6b4-1f3fc-200d-2640-fe0f · 1f6b4-1f3fc-200d-2642-fe0f · 1f6b4-1f3fd · 1f6b4-1f3fd-200d-2640-fe0f · 1f6b4-1f3fd-200d-2642-fe0f · 1f6b4-1f3fe · 1f6b4-1f3fe-200d-2640-fe0f · 1f6b4-1f3fe-200d-2642-fe0f · 1f6b4-1f3ff · 1f6b4-1f3ff-200d-2640-fe0f · 1f6b4-1f3ff-200d-2642-fe0f · 1f6b4-200d-2640-fe0f · 1f6b4-200d-2642-fe0f · 1f6b5 · 1f6b5-1f3fb · 1f6b5-1f3fb-200d-2640-fe0f · 1f6b5-1f3fb-200d-2642-fe0f · 1f6b5-1f3fc · 1f6b5-1f3fc-200d-2640-fe0f · 1f6b5-1f3fc-200d-2642-fe0f · 1f6b5-1f3fd · 1f6b5-1f3fd-200d-2640-fe0f · 1f6b5-1f3fd-200d-2642-fe0f · 1f6b5-1f3fe · 1f6b5-1f3fe-200d-2640-fe0f · 1f6b5-1f3fe-200d-2642-fe0f · 1f6b5-1f3ff · 1f6b5-1f3ff-200d-2640-fe0f · 1f6b5-1f3ff-200d-2642-fe0f · 1f6b5-200d-2640-fe0f · 1f6b5-200d-2642-fe0f · 1f6b6 · 1f6b6-1f3fb · 1f6b6-1f3fb-200d-2640-fe0f · 1f6b6-1f3fb-200d-2640-fe0f-200d-27a1-fe0f · 1f6b6-1f3fb-200d-2642-fe0f · 1f6b6-1f3fb-200d-2642-fe0f-200d-27a1-fe0f · 1f6b6-1f3fb-200d-27a1-fe0f · 1f6b6-1f3fc · 1f6b6-1f3fc-200d-2640-fe0f · 1f6b6-1f3fc-200d-2640-fe0f-200d-27a1-fe0f · 1f6b6-1f3fc-200d-2642-fe0f · 1f6b6-1f3fc-200d-2642-fe0f-200d-27a1-fe0f · 1f6b6-1f3fc-200d-27a1-fe0f · 1f6b6-1f3fd · 1f6b6-1f3fd-200d-2640-fe0f · 1f6b6-1f3fd-200d-2640-fe0f-200d-27a1-fe0f · 1f6b6-1f3fd-200d-2642-fe0f · 1f6b6-1f3fd-200d-2642-fe0f-200d-27a1-fe0f · 1f6b6-1f3fd-200d-27a1-fe0f · 1f6b6-1f3fe · 1f6b6-1f3fe-200d-2640-fe0f · 1f6b6-1f3fe-200d-2640-fe0f-200d-27a1-fe0f · 1f6b6-1f3fe-200d-2642-fe0f · 1f6b6-1f3fe-200d-2642-fe0f-200d-27a1-fe0f · 1f6b6-1f3fe-200d-27a1-fe0f · 1f6b6-1f3ff · 1f6b6-1f3ff-200d-2640-fe0f · 1f6b6-1f3ff-200d-2640-fe0f-200d-27a1-fe0f · 1f6b6-1f3ff-200d-2642-fe0f · 1f6b6-1f3ff-200d-2642-fe0f-200d-27a1-fe0f · 1f6b6-1f3ff-200d-27a1-fe0f · 1f6b6-200d-2640-fe0f · 1f6b6-200d-2640-fe0f-200d-27a1-fe0f · 1f6b6-200d-2642-fe0f · 1f6b6-200d-2642-fe0f-200d-27a1-fe0f · 1f6b6-200d-27a1-fe0f · 1f6b7 · 1f6b8 · 1f6b9 · 1f6ba · 1f6bb · 1f6bc · 1f6bd · 1f6be · 1f6bf · 1f6c0 · 1f6c0-1f3fb · 1f6c0-1f3fc · 1f6c0-1f3fd · 1f6c0-1f3fe · 1f6c0-1f3ff · 1f6c1 · 1f6c2 · 1f6c3 · 1f6c4 · 1f6c5 · 1f6cb · 1f6cc · 1f6cc-1f3fb · 1f6cc-1f3fc · 1f6cc-1f3fd · 1f6cc-1f3fe · 1f6cc-1f3ff · 1f6cd · 1f6ce · 1f6cf · 1f6d0 · 1f6d1 · 1f6d2 · 1f6d5 · 1f6d6 · 1f6d7 · 1f6d8 · 1f6dc · 1f6dd · 1f6de · 1f6df · 1f6e0 · 1f6e1 · 1f6e2 · 1f6e3 · 1f6e4 · 1f6e5 · 1f6e9 · 1f6eb · 1f6ec · 1f6f0 · 1f6f3 · 1f6f4 · 1f6f5 · 1f6f6 · 1f6f7 · 1f6f8 · 1f6f9 · 1f6fa · 1f6fb · 1f6fc · 1f7e0 · 1f7e1 · 1f7e2 · 1f7e3 · 1f7e4 · 1f7e5 · 1f7e6 · 1f7e7 · 1f7e8 · 1f7e9 · 1f7ea · 1f7eb · 1f7f0 · 1f90c · 1f90c-1f3fb · 1f90c-1f3fc · 1f90c-1f3fd · 1f90c-1f3fe · 1f90c-1f3ff · 1f90d · 1f90e · 1f90f · 1f90f-1f3fb · 1f90f-1f3fc · 1f90f-1f3fd · 1f90f-1f3fe · 1f90f-1f3ff · 1f910 · 1f911 · 1f912 · 1f913 · 1f914 · 1f915 · 1f916 · 1f917 · 1f918 · 1f918-1f3fb · 1f918-1f3fc · 1f918-1f3fd · 1f918-1f3fe · 1f918-1f3ff · 1f919 · 1f919-1f3fb · 1f919-1f3fc · 1f919-1f3fd · 1f919-1f3fe · 1f919-1f3ff · 1f91a · 1f91a-1f3fb · 1f91a-1f3fc · 1f91a-1f3fd · 1f91a-1f3fe · 1f91a-1f3ff · 1f91b · 1f91b-1f3fb · 1f91b-1f3fc · 1f91b-1f3fd · 1f91b-1f3fe · 1f91b-1f3ff · 1f91c · 1f91c-1f3fb · 1f91c-1f3fc · 1f91c-1f3fd · 1f91c-1f3fe · 1f91c-1f3ff · 1f91d · 1f91d-1f3fb · 1f91d-1f3fc · 1f91d-1f3fd · 1f91d-1f3fe · 1f91d-1f3ff · 1f91e · 1f91e-1f3fb · 1f91e-1f3fc · 1f91e-1f3fd · 1f91e-1f3fe · 1f91e-1f3ff · 1f91f · 1f91f-1f3fb · 1f91f-1f3fc · 1f91f-1f3fd · 1f91f-1f3fe · 1f91f-1f3ff · 1f920 · 1f921 · 1f922 · 1f923 · 1f924 · 1f925 · 1f926 · 1f926-1f3fb · 1f926-1f3fb-200d-2640-fe0f · 1f926-1f3fb-200d-2642-fe0f · 1f926-1f3fc · 1f926-1f3fc-200d-2640-fe0f · 1f926-1f3fc-200d-2642-fe0f · 1f926-1f3fd · 1f926-1f3fd-200d-2640-fe0f · 1f926-1f3fd-200d-2642-fe0f · 1f926-1f3fe · 1f926-1f3fe-200d-2640-fe0f · 1f926-1f3fe-200d-2642-fe0f · 1f926-1f3ff · 1f926-1f3ff-200d-2640-fe0f · 1f926-1f3ff-200d-2642-fe0f · 1f926-200d-2640-fe0f · 1f926-200d-2642-fe0f · 1f927 · 1f928 · 1f929 · 1f92a · 1f92b · 1f92c · 1f92d · 1f92e · 1f92f · 1f930 · 1f930-1f3fb · 1f930-1f3fc · 1f930-1f3fd · 1f930-1f3fe · 1f930-1f3ff · 1f931 · 1f931-1f3fb · 1f931-1f3fc · 1f931-1f3fd · 1f931-1f3fe · 1f931-1f3ff · 1f932 · 1f932-1f3fb · 1f932-1f3fc · 1f932-1f3fd · 1f932-1f3fe · 1f932-1f3ff · 1f933 · 1f933-1f3fb · 1f933-1f3fc · 1f933-1f3fd · 1f933-1f3fe · 1f933-1f3ff · 1f934 · 1f934-1f3fb · 1f934-1f3fc · 1f934-1f3fd · 1f934-1f3fe · 1f934-1f3ff · 1f935 · 1f935-1f3fb · 1f935-1f3fb-200d-2640-fe0f · 1f935-1f3fb-200d-2642-fe0f · 1f935-1f3fc · 1f935-1f3fc-200d-2640-fe0f · 1f935-1f3fc-200d-2642-fe0f · 1f935-1f3fd · 1f935-1f3fd-200d-2640-fe0f · 1f935-1f3fd-200d-2642-fe0f · 1f935-1f3fe · 1f935-1f3fe-200d-2640-fe0f · 1f935-1f3fe-200d-2642-fe0f · 1f935-1f3ff · 1f935-1f3ff-200d-2640-fe0f · 1f935-1f3ff-200d-2642-fe0f · 1f935-200d-2640-fe0f · 1f935-200d-2642-fe0f · 1f936 · 1f936-1f3fb · 1f936-1f3fc · 1f936-1f3fd · 1f936-1f3fe · 1f936-1f3ff · 1f937 · 1f937-1f3fb · 1f937-1f3fb-200d-2640-fe0f · 1f937-1f3fb-200d-2642-fe0f · 1f937-1f3fc · 1f937-1f3fc-200d-2640-fe0f · 1f937-1f3fc-200d-2642-fe0f · 1f937-1f3fd · 1f937-1f3fd-200d-2640-fe0f · 1f937-1f3fd-200d-2642-fe0f · 1f937-1f3fe · 1f937-1f3fe-200d-2640-fe0f · 1f937-1f3fe-200d-2642-fe0f · 1f937-1f3ff · 1f937-1f3ff-200d-2640-fe0f · 1f937-1f3ff-200d-2642-fe0f · 1f937-200d-2640-fe0f · 1f937-200d-2642-fe0f · 1f938 · 1f938-1f3fb · 1f938-1f3fb-200d-2640-fe0f · 1f938-1f3fb-200d-2642-fe0f · 1f938-1f3fc · 1f938-1f3fc-200d-2640-fe0f · 1f938-1f3fc-200d-2642-fe0f · 1f938-1f3fd · 1f938-1f3fd-200d-2640-fe0f · 1f938-1f3fd-200d-2642-fe0f · 1f938-1f3fe · 1f938-1f3fe-200d-2640-fe0f · 1f938-1f3fe-200d-2642-fe0f · 1f938-1f3ff · 1f938-1f3ff-200d-2640-fe0f · 1f938-1f3ff-200d-2642-fe0f · 1f938-200d-2640-fe0f · 1f938-200d-2642-fe0f · 1f939 · 1f939-1f3fb · 1f939-1f3fb-200d-2640-fe0f · 1f939-1f3fb-200d-2642-fe0f · 1f939-1f3fc · 1f939-1f3fc-200d-2640-fe0f · 1f939-1f3fc-200d-2642-fe0f · 1f939-1f3fd · 1f939-1f3fd-200d-2640-fe0f · 1f939-1f3fd-200d-2642-fe0f · 1f939-1f3fe · 1f939-1f3fe-200d-2640-fe0f · 1f939-1f3fe-200d-2642-fe0f · 1f939-1f3ff · 1f939-1f3ff-200d-2640-fe0f · 1f939-1f3ff-200d-2642-fe0f · 1f939-200d-2640-fe0f · 1f939-200d-2642-fe0f · 1f93a · 1f93c · 1f93c-1f3fb · 1f93c-1f3fb-200d-2640-fe0f · 1f93c-1f3fb-200d-2642-fe0f · 1f93c-1f3fc · 1f93c-1f3fc-200d-2640-fe0f · 1f93c-1f3fc-200d-2642-fe0f · 1f93c-1f3fd · 1f93c-1f3fd-200d-2640-fe0f · 1f93c-1f3fd-200d-2642-fe0f · 1f93c-1f3fe · 1f93c-1f3fe-200d-2640-fe0f · 1f93c-1f3fe-200d-2642-fe0f · 1f93c-1f3ff · 1f93c-1f3ff-200d-2640-fe0f · 1f93c-1f3ff-200d-2642-fe0f · 1f93c-200d-2640-fe0f · 1f93c-200d-2642-fe0f · 1f93d · 1f93d-1f3fb · 1f93d-1f3fb-200d-2640-fe0f · 1f93d-1f3fb-200d-2642-fe0f · 1f93d-1f3fc · 1f93d-1f3fc-200d-2640-fe0f · 1f93d-1f3fc-200d-2642-fe0f · 1f93d-1f3fd · 1f93d-1f3fd-200d-2640-fe0f · 1f93d-1f3fd-200d-2642-fe0f · 1f93d-1f3fe · 1f93d-1f3fe-200d-2640-fe0f · 1f93d-1f3fe-200d-2642-fe0f · 1f93d-1f3ff · 1f93d-1f3ff-200d-2640-fe0f · 1f93d-1f3ff-200d-2642-fe0f · 1f93d-200d-2640-fe0f · 1f93d-200d-2642-fe0f · 1f93e · 1f93e-1f3fb · 1f93e-1f3fb-200d-2640-fe0f · 1f93e-1f3fb-200d-2642-fe0f · 1f93e-1f3fc · 1f93e-1f3fc-200d-2640-fe0f · 1f93e-1f3fc-200d-2642-fe0f · 1f93e-1f3fd · 1f93e-1f3fd-200d-2640-fe0f · 1f93e-1f3fd-200d-2642-fe0f · 1f93e-1f3fe · 1f93e-1f3fe-200d-2640-fe0f · 1f93e-1f3fe-200d-2642-fe0f · 1f93e-1f3ff · 1f93e-1f3ff-200d-2640-fe0f · 1f93e-1f3ff-200d-2642-fe0f · 1f93e-200d-2640-fe0f · 1f93e-200d-2642-fe0f · 1f93f · 1f940 · 1f941 · 1f942 · 1f943 · 1f944 · 1f945 · 1f947 · 1f948 · 1f949 · 1f94a · 1f94b · 1f94c · 1f94d · 1f94e · 1f94f · 1f950 · 1f951 · 1f952 · 1f953 · 1f954 · 1f955 · 1f956 · 1f957 · 1f958 · 1f959 · 1f95a · 1f95b · 1f95c · 1f95d · 1f95e · 1f95f · 1f960 · 1f961 · 1f962 · 1f963 · 1f964 · 1f965 · 1f966 · 1f967 · 1f968 · 1f969 · 1f96a · 1f96b · 1f96c · 1f96d · 1f96e · 1f96f · 1f970 · 1f971 · 1f972 · 1f973 · 1f974 · 1f975 · 1f976 · 1f977 · 1f977-1f3fb · 1f977-1f3fc · 1f977-1f3fd · 1f977-1f3fe · 1f977-1f3ff · 1f978 · 1f979 · 1f97a · 1f97b · 1f97c · 1f97d · 1f97e · 1f97f · 1f980 · 1f981 · 1f982 · 1f983 · 1f984 · 1f985 · 1f986 · 1f987 · 1f988 · 1f989 · 1f98a · 1f98b · 1f98c · 1f98d · 1f98e · 1f98f · 1f990 · 1f991 · 1f992 · 1f993 · 1f994 · 1f995 · 1f996 · 1f997 · 1f998 · 1f999 · 1f99a · 1f99b · 1f99c · 1f99d · 1f99e · 1f99f · 1f9a0 · 1f9a1 · 1f9a2 · 1f9a3 · 1f9a4 · 1f9a5 · 1f9a6 · 1f9a7 · 1f9a8 · 1f9a9 · 1f9aa · 1f9ab · 1f9ac · 1f9ad · 1f9ae · 1f9af · 1f9b0 · 1f9b1 · 1f9b2 · 1f9b3 · 1f9b4 · 1f9b5 · 1f9b5-1f3fb · 1f9b5-1f3fc · 1f9b5-1f3fd · 1f9b5-1f3fe · 1f9b5-1f3ff · 1f9b6 · 1f9b6-1f3fb · 1f9b6-1f3fc · 1f9b6-1f3fd · 1f9b6-1f3fe · 1f9b6-1f3ff · 1f9b7 · 1f9b8 · 1f9b8-1f3fb · 1f9b8-1f3fb-200d-2640-fe0f · 1f9b8-1f3fb-200d-2642-fe0f · 1f9b8-1f3fc · 1f9b8-1f3fc-200d-2640-fe0f · 1f9b8-1f3fc-200d-2642-fe0f · 1f9b8-1f3fd · 1f9b8-1f3fd-200d-2640-fe0f · 1f9b8-1f3fd-200d-2642-fe0f · 1f9b8-1f3fe · 1f9b8-1f3fe-200d-2640-fe0f · 1f9b8-1f3fe-200d-2642-fe0f · 1f9b8-1f3ff · 1f9b8-1f3ff-200d-2640-fe0f · 1f9b8-1f3ff-200d-2642-fe0f · 1f9b8-200d-2640-fe0f · 1f9b8-200d-2642-fe0f · 1f9b9 · 1f9b9-1f3fb · 1f9b9-1f3fb-200d-2640-fe0f · 1f9b9-1f3fb-200d-2642-fe0f · 1f9b9-1f3fc · 1f9b9-1f3fc-200d-2640-fe0f · 1f9b9-1f3fc-200d-2642-fe0f · 1f9b9-1f3fd · 1f9b9-1f3fd-200d-2640-fe0f · 1f9b9-1f3fd-200d-2642-fe0f · 1f9b9-1f3fe · 1f9b9-1f3fe-200d-2640-fe0f · 1f9b9-1f3fe-200d-2642-fe0f · 1f9b9-1f3ff · 1f9b9-1f3ff-200d-2640-fe0f · 1f9b9-1f3ff-200d-2642-fe0f · 1f9b9-200d-2640-fe0f · 1f9b9-200d-2642-fe0f · 1f9ba · 1f9bb · 1f9bb-1f3fb · 1f9bb-1f3fc · 1f9bb-1f3fd · 1f9bb-1f3fe · 1f9bb-1f3ff · 1f9bc · 1f9bd · 1f9be · 1f9bf · 1f9c0 · 1f9c1 · 1f9c2 · 1f9c3 · 1f9c4 · 1f9c5 · 1f9c6 · 1f9c7 · 1f9c8 · 1f9c9 · 1f9ca · 1f9cb · 1f9cc · 1f9cd · 1f9cd-1f3fb · 1f9cd-1f3fb-200d-2640-fe0f · 1f9cd-1f3fb-200d-2642-fe0f · 1f9cd-1f3fc · 1f9cd-1f3fc-200d-2640-fe0f · 1f9cd-1f3fc-200d-2642-fe0f · 1f9cd-1f3fd · 1f9cd-1f3fd-200d-2640-fe0f · 1f9cd-1f3fd-200d-2642-fe0f · 1f9cd-1f3fe · 1f9cd-1f3fe-200d-2640-fe0f · 1f9cd-1f3fe-200d-2642-fe0f · 1f9cd-1f3ff · 1f9cd-1f3ff-200d-2640-fe0f · 1f9cd-1f3ff-200d-2642-fe0f · 1f9cd-200d-2640-fe0f · 1f9cd-200d-2642-fe0f · 1f9ce · 1f9ce-1f3fb · 1f9ce-1f3fb-200d-2640-fe0f · 1f9ce-1f3fb-200d-2640-fe0f-200d-27a1-fe0f · 1f9ce-1f3fb-200d-2642-fe0f · 1f9ce-1f3fb-200d-2642-fe0f-200d-27a1-fe0f · 1f9ce-1f3fb-200d-27a1-fe0f · 1f9ce-1f3fc · 1f9ce-1f3fc-200d-2640-fe0f · 1f9ce-1f3fc-200d-2640-fe0f-200d-27a1-fe0f · 1f9ce-1f3fc-200d-2642-fe0f · 1f9ce-1f3fc-200d-2642-fe0f-200d-27a1-fe0f · 1f9ce-1f3fc-200d-27a1-fe0f · 1f9ce-1f3fd · 1f9ce-1f3fd-200d-2640-fe0f · 1f9ce-1f3fd-200d-2640-fe0f-200d-27a1-fe0f · 1f9ce-1f3fd-200d-2642-fe0f · 1f9ce-1f3fd-200d-2642-fe0f-200d-27a1-fe0f · 1f9ce-1f3fd-200d-27a1-fe0f · 1f9ce-1f3fe · 1f9ce-1f3fe-200d-2640-fe0f · 1f9ce-1f3fe-200d-2640-fe0f-200d-27a1-fe0f · 1f9ce-1f3fe-200d-2642-fe0f · 1f9ce-1f3fe-200d-2642-fe0f-200d-27a1-fe0f · 1f9ce-1f3fe-200d-27a1-fe0f · 1f9ce-1f3ff · 1f9ce-1f3ff-200d-2640-fe0f · 1f9ce-1f3ff-200d-2640-fe0f-200d-27a1-fe0f · 1f9ce-1f3ff-200d-2642-fe0f · 1f9ce-1f3ff-200d-2642-fe0f-200d-27a1-fe0f · 1f9ce-1f3ff-200d-27a1-fe0f · 1f9ce-200d-2640-fe0f · 1f9ce-200d-2640-fe0f-200d-27a1-fe0f · 1f9ce-200d-2642-fe0f · 1f9ce-200d-2642-fe0f-200d-27a1-fe0f · 1f9ce-200d-27a1-fe0f · 1f9cf · 1f9cf-1f3fb · 1f9cf-1f3fb-200d-2640-fe0f · 1f9cf-1f3fb-200d-2642-fe0f · 1f9cf-1f3fc · 1f9cf-1f3fc-200d-2640-fe0f · 1f9cf-1f3fc-200d-2642-fe0f · 1f9cf-1f3fd · 1f9cf-1f3fd-200d-2640-fe0f · 1f9cf-1f3fd-200d-2642-fe0f · 1f9cf-1f3fe · 1f9cf-1f3fe-200d-2640-fe0f · 1f9cf-1f3fe-200d-2642-fe0f · 1f9cf-1f3ff · 1f9cf-1f3ff-200d-2640-fe0f · 1f9cf-1f3ff-200d-2642-fe0f · 1f9cf-200d-2640-fe0f · 1f9cf-200d-2642-fe0f · 1f9d0 · 1f9d1 · 1f9d1-1f3fb · 1f9d1-1f3fb-200d-1f33e · 1f9d1-1f3fb-200d-1f373 · 1f9d1-1f3fb-200d-1f37c · 1f9d1-1f3fb-200d-1f384 · 1f9d1-1f3fb-200d-1f393 · 1f9d1-1f3fb-200d-1f3a4 · 1f9d1-1f3fb-200d-1f3a8 · 1f9d1-1f3fb-200d-1f3eb · 1f9d1-1f3fb-200d-1f3ed · 1f9d1-1f3fb-200d-1f430-200d-1f9d1-1f3fc · 1f9d1-1f3fb-200d-1f430-200d-1f9d1-1f3fd · 1f9d1-1f3fb-200d-1f430-200d-1f9d1-1f3fe · 1f9d1-1f3fb-200d-1f430-200d-1f9d1-1f3ff · 1f9d1-1f3fb-200d-1f4bb · 1f9d1-1f3fb-200d-1f4bc · 1f9d1-1f3fb-200d-1f527 · 1f9d1-1f3fb-200d-1f52c · 1f9d1-1f3fb-200d-1f680 · 1f9d1-1f3fb-200d-1f692 · 1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb · 1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc · 1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd · 1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe · 1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff · 1f9d1-1f3fb-200d-1f9af · 1f9d1-1f3fb-200d-1f9af-200d-27a1-fe0f · 1f9d1-1f3fb-200d-1f9b0 · 1f9d1-1f3fb-200d-1f9b1 · 1f9d1-1f3fb-200d-1f9b2 · 1f9d1-1f3fb-200d-1f9b3 · 1f9d1-1f3fb-200d-1f9bc · 1f9d1-1f3fb-200d-1f9bc-200d-27a1-fe0f · 1f9d1-1f3fb-200d-1f9bd · 1f9d1-1f3fb-200d-1f9bd-200d-27a1-fe0f · 1f9d1-1f3fb-200d-1fa70 · 1f9d1-1f3fb-200d-1faef-200d-1f9d1-1f3fc · 1f9d1-1f3fb-200d-1faef-200d-1f9d1-1f3fd · 1f9d1-1f3fb-200d-1faef-200d-1f9d1-1f3fe · 1f9d1-1f3fb-200d-1faef-200d-1f9d1-1f3ff · 1f9d1-1f3fb-200d-2695-fe0f · 1f9d1-1f3fb-200d-2696-fe0f · 1f9d1-1f3fb-200d-2708-fe0f · 1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc · 1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd · 1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe · 1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff · 1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc · 1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd · 1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe · 1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff · 1f9d1-1f3fc · 1f9d1-1f3fc-200d-1f33e · 1f9d1-1f3fc-200d-1f373 · 1f9d1-1f3fc-200d-1f37c · 1f9d1-1f3fc-200d-1f384 · 1f9d1-1f3fc-200d-1f393 · 1f9d1-1f3fc-200d-1f3a4 · 1f9d1-1f3fc-200d-1f3a8 · 1f9d1-1f3fc-200d-1f3eb · 1f9d1-1f3fc-200d-1f3ed · 1f9d1-1f3fc-200d-1f430-200d-1f9d1-1f3fb · 1f9d1-1f3fc-200d-1f430-200d-1f9d1-1f3fd · 1f9d1-1f3fc-200d-1f430-200d-1f9d1-1f3fe · 1f9d1-1f3fc-200d-1f430-200d-1f9d1-1f3ff · 1f9d1-1f3fc-200d-1f4bb · 1f9d1-1f3fc-200d-1f4bc · 1f9d1-1f3fc-200d-1f527 · 1f9d1-1f3fc-200d-1f52c · 1f9d1-1f3fc-200d-1f680 · 1f9d1-1f3fc-200d-1f692 · 1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb · 1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc · 1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd · 1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe · 1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff · 1f9d1-1f3fc-200d-1f9af · 1f9d1-1f3fc-200d-1f9af-200d-27a1-fe0f · 1f9d1-1f3fc-200d-1f9b0 · 1f9d1-1f3fc-200d-1f9b1 · 1f9d1-1f3fc-200d-1f9b2 · 1f9d1-1f3fc-200d-1f9b3 · 1f9d1-1f3fc-200d-1f9bc · 1f9d1-1f3fc-200d-1f9bc-200d-27a1-fe0f · 1f9d1-1f3fc-200d-1f9bd · 1f9d1-1f3fc-200d-1f9bd-200d-27a1-fe0f · 1f9d1-1f3fc-200d-1fa70 · 1f9d1-1f3fc-200d-1faef-200d-1f9d1-1f3fb · 1f9d1-1f3fc-200d-1faef-200d-1f9d1-1f3fd · 1f9d1-1f3fc-200d-1faef-200d-1f9d1-1f3fe · 1f9d1-1f3fc-200d-1faef-200d-1f9d1-1f3ff · 1f9d1-1f3fc-200d-2695-fe0f · 1f9d1-1f3fc-200d-2696-fe0f · 1f9d1-1f3fc-200d-2708-fe0f · 1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb · 1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd · 1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe · 1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff · 1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb · 1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd · 1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe · 1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff · 1f9d1-1f3fd · 1f9d1-1f3fd-200d-1f33e · 1f9d1-1f3fd-200d-1f373 · 1f9d1-1f3fd-200d-1f37c · 1f9d1-1f3fd-200d-1f384 · 1f9d1-1f3fd-200d-1f393 · 1f9d1-1f3fd-200d-1f3a4 · 1f9d1-1f3fd-200d-1f3a8 · 1f9d1-1f3fd-200d-1f3eb · 1f9d1-1f3fd-200d-1f3ed · 1f9d1-1f3fd-200d-1f430-200d-1f9d1-1f3fb · 1f9d1-1f3fd-200d-1f430-200d-1f9d1-1f3fc · 1f9d1-1f3fd-200d-1f430-200d-1f9d1-1f3fe · 1f9d1-1f3fd-200d-1f430-200d-1f9d1-1f3ff · 1f9d1-1f3fd-200d-1f4bb · 1f9d1-1f3fd-200d-1f4bc · 1f9d1-1f3fd-200d-1f527 · 1f9d1-1f3fd-200d-1f52c · 1f9d1-1f3fd-200d-1f680 · 1f9d1-1f3fd-200d-1f692 · 1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb · 1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc · 1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd · 1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe · 1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff · 1f9d1-1f3fd-200d-1f9af · 1f9d1-1f3fd-200d-1f9af-200d-27a1-fe0f · 1f9d1-1f3fd-200d-1f9b0 · 1f9d1-1f3fd-200d-1f9b1 · 1f9d1-1f3fd-200d-1f9b2 · 1f9d1-1f3fd-200d-1f9b3 · 1f9d1-1f3fd-200d-1f9bc · 1f9d1-1f3fd-200d-1f9bc-200d-27a1-fe0f · 1f9d1-1f3fd-200d-1f9bd · 1f9d1-1f3fd-200d-1f9bd-200d-27a1-fe0f · 1f9d1-1f3fd-200d-1fa70 · 1f9d1-1f3fd-200d-1faef-200d-1f9d1-1f3fb · 1f9d1-1f3fd-200d-1faef-200d-1f9d1-1f3fc · 1f9d1-1f3fd-200d-1faef-200d-1f9d1-1f3fe · 1f9d1-1f3fd-200d-1faef-200d-1f9d1-1f3ff · 1f9d1-1f3fd-200d-2695-fe0f · 1f9d1-1f3fd-200d-2696-fe0f · 1f9d1-1f3fd-200d-2708-fe0f · 1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb · 1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc · 1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe · 1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff · 1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb · 1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc · 1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe · 1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff · 1f9d1-1f3fe · 1f9d1-1f3fe-200d-1f33e · 1f9d1-1f3fe-200d-1f373 · 1f9d1-1f3fe-200d-1f37c · 1f9d1-1f3fe-200d-1f384 · 1f9d1-1f3fe-200d-1f393 · 1f9d1-1f3fe-200d-1f3a4 · 1f9d1-1f3fe-200d-1f3a8 · 1f9d1-1f3fe-200d-1f3eb · 1f9d1-1f3fe-200d-1f3ed · 1f9d1-1f3fe-200d-1f430-200d-1f9d1-1f3fb · 1f9d1-1f3fe-200d-1f430-200d-1f9d1-1f3fc · 1f9d1-1f3fe-200d-1f430-200d-1f9d1-1f3fd · 1f9d1-1f3fe-200d-1f430-200d-1f9d1-1f3ff · 1f9d1-1f3fe-200d-1f4bb · 1f9d1-1f3fe-200d-1f4bc · 1f9d1-1f3fe-200d-1f527 · 1f9d1-1f3fe-200d-1f52c · 1f9d1-1f3fe-200d-1f680 · 1f9d1-1f3fe-200d-1f692 · 1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb · 1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc · 1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd · 1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe · 1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff · 1f9d1-1f3fe-200d-1f9af · 1f9d1-1f3fe-200d-1f9af-200d-27a1-fe0f · 1f9d1-1f3fe-200d-1f9b0 · 1f9d1-1f3fe-200d-1f9b1 · 1f9d1-1f3fe-200d-1f9b2 · 1f9d1-1f3fe-200d-1f9b3 · 1f9d1-1f3fe-200d-1f9bc · 1f9d1-1f3fe-200d-1f9bc-200d-27a1-fe0f · 1f9d1-1f3fe-200d-1f9bd · 1f9d1-1f3fe-200d-1f9bd-200d-27a1-fe0f · 1f9d1-1f3fe-200d-1fa70 · 1f9d1-1f3fe-200d-1faef-200d-1f9d1-1f3fb · 1f9d1-1f3fe-200d-1faef-200d-1f9d1-1f3fc · 1f9d1-1f3fe-200d-1faef-200d-1f9d1-1f3fd · 1f9d1-1f3fe-200d-1faef-200d-1f9d1-1f3ff · 1f9d1-1f3fe-200d-2695-fe0f · 1f9d1-1f3fe-200d-2696-fe0f · 1f9d1-1f3fe-200d-2708-fe0f · 1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb · 1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc · 1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd · 1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff · 1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb · 1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc · 1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd · 1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff · 1f9d1-1f3ff · 1f9d1-1f3ff-200d-1f33e · 1f9d1-1f3ff-200d-1f373 · 1f9d1-1f3ff-200d-1f37c · 1f9d1-1f3ff-200d-1f384 · 1f9d1-1f3ff-200d-1f393 · 1f9d1-1f3ff-200d-1f3a4 · 1f9d1-1f3ff-200d-1f3a8 · 1f9d1-1f3ff-200d-1f3eb · 1f9d1-1f3ff-200d-1f3ed · 1f9d1-1f3ff-200d-1f430-200d-1f9d1-1f3fb · 1f9d1-1f3ff-200d-1f430-200d-1f9d1-1f3fc · 1f9d1-1f3ff-200d-1f430-200d-1f9d1-1f3fd · 1f9d1-1f3ff-200d-1f430-200d-1f9d1-1f3fe · 1f9d1-1f3ff-200d-1f4bb · 1f9d1-1f3ff-200d-1f4bc · 1f9d1-1f3ff-200d-1f527 · 1f9d1-1f3ff-200d-1f52c · 1f9d1-1f3ff-200d-1f680 · 1f9d1-1f3ff-200d-1f692 · 1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb · 1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc · 1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd · 1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe · 1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff · 1f9d1-1f3ff-200d-1f9af · 1f9d1-1f3ff-200d-1f9af-200d-27a1-fe0f · 1f9d1-1f3ff-200d-1f9b0 · 1f9d1-1f3ff-200d-1f9b1 · 1f9d1-1f3ff-200d-1f9b2 · 1f9d1-1f3ff-200d-1f9b3 · 1f9d1-1f3ff-200d-1f9bc · 1f9d1-1f3ff-200d-1f9bc-200d-27a1-fe0f · 1f9d1-1f3ff-200d-1f9bd · 1f9d1-1f3ff-200d-1f9bd-200d-27a1-fe0f · 1f9d1-1f3ff-200d-1fa70 · 1f9d1-1f3ff-200d-1faef-200d-1f9d1-1f3fb · 1f9d1-1f3ff-200d-1faef-200d-1f9d1-1f3fc · 1f9d1-1f3ff-200d-1faef-200d-1f9d1-1f3fd · 1f9d1-1f3ff-200d-1faef-200d-1f9d1-1f3fe · 1f9d1-1f3ff-200d-2695-fe0f · 1f9d1-1f3ff-200d-2696-fe0f · 1f9d1-1f3ff-200d-2708-fe0f · 1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb · 1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc · 1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd · 1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe · 1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb · 1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc · 1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd · 1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe · 1f9d1-200d-1f33e · 1f9d1-200d-1f373 · 1f9d1-200d-1f37c · 1f9d1-200d-1f384 · 1f9d1-200d-1f393 · 1f9d1-200d-1f3a4 · 1f9d1-200d-1f3a8 · 1f9d1-200d-1f3eb · 1f9d1-200d-1f3ed · 1f9d1-200d-1f4bb · 1f9d1-200d-1f4bc · 1f9d1-200d-1f527 · 1f9d1-200d-1f52c · 1f9d1-200d-1f680 · 1f9d1-200d-1f692 · 1f9d1-200d-1f91d-200d-1f9d1 · 1f9d1-200d-1f9af · 1f9d1-200d-1f9af-200d-27a1-fe0f · 1f9d1-200d-1f9b0 · 1f9d1-200d-1f9b1 · 1f9d1-200d-1f9b2 · 1f9d1-200d-1f9b3 · 1f9d1-200d-1f9bc · 1f9d1-200d-1f9bc-200d-27a1-fe0f · 1f9d1-200d-1f9bd · 1f9d1-200d-1f9bd-200d-27a1-fe0f · 1f9d1-200d-1f9d1-200d-1f9d2 · 1f9d1-200d-1f9d1-200d-1f9d2-200d-1f9d2 · 1f9d1-200d-1f9d2 · 1f9d1-200d-1f9d2-200d-1f9d2 · 1f9d1-200d-1fa70 · 1f9d1-200d-2695-fe0f · 1f9d1-200d-2696-fe0f · 1f9d1-200d-2708-fe0f · 1f9d2 · 1f9d2-1f3fb · 1f9d2-1f3fc · 1f9d2-1f3fd · 1f9d2-1f3fe · 1f9d2-1f3ff · 1f9d3 · 1f9d3-1f3fb · 1f9d3-1f3fc · 1f9d3-1f3fd · 1f9d3-1f3fe · 1f9d3-1f3ff · 1f9d4 · 1f9d4-1f3fb · 1f9d4-1f3fb-200d-2640-fe0f · 1f9d4-1f3fb-200d-2642-fe0f · 1f9d4-1f3fc · 1f9d4-1f3fc-200d-2640-fe0f · 1f9d4-1f3fc-200d-2642-fe0f · 1f9d4-1f3fd · 1f9d4-1f3fd-200d-2640-fe0f · 1f9d4-1f3fd-200d-2642-fe0f · 1f9d4-1f3fe · 1f9d4-1f3fe-200d-2640-fe0f · 1f9d4-1f3fe-200d-2642-fe0f · 1f9d4-1f3ff · 1f9d4-1f3ff-200d-2640-fe0f · 1f9d4-1f3ff-200d-2642-fe0f · 1f9d4-200d-2640-fe0f · 1f9d4-200d-2642-fe0f · 1f9d5 · 1f9d5-1f3fb · 1f9d5-1f3fc · 1f9d5-1f3fd · 1f9d5-1f3fe · 1f9d5-1f3ff · 1f9d6 · 1f9d6-1f3fb · 1f9d6-1f3fb-200d-2640-fe0f · 1f9d6-1f3fb-200d-2642-fe0f · 1f9d6-1f3fc · 1f9d6-1f3fc-200d-2640-fe0f · 1f9d6-1f3fc-200d-2642-fe0f · 1f9d6-1f3fd · 1f9d6-1f3fd-200d-2640-fe0f · 1f9d6-1f3fd-200d-2642-fe0f · 1f9d6-1f3fe · 1f9d6-1f3fe-200d-2640-fe0f · 1f9d6-1f3fe-200d-2642-fe0f · 1f9d6-1f3ff · 1f9d6-1f3ff-200d-2640-fe0f · 1f9d6-1f3ff-200d-2642-fe0f · 1f9d6-200d-2640-fe0f · 1f9d6-200d-2642-fe0f · 1f9d7 · 1f9d7-1f3fb · 1f9d7-1f3fb-200d-2640-fe0f · 1f9d7-1f3fb-200d-2642-fe0f · 1f9d7-1f3fc · 1f9d7-1f3fc-200d-2640-fe0f · 1f9d7-1f3fc-200d-2642-fe0f · 1f9d7-1f3fd · 1f9d7-1f3fd-200d-2640-fe0f · 1f9d7-1f3fd-200d-2642-fe0f · 1f9d7-1f3fe · 1f9d7-1f3fe-200d-2640-fe0f · 1f9d7-1f3fe-200d-2642-fe0f · 1f9d7-1f3ff · 1f9d7-1f3ff-200d-2640-fe0f · 1f9d7-1f3ff-200d-2642-fe0f · 1f9d7-200d-2640-fe0f · 1f9d7-200d-2642-fe0f · 1f9d8 · 1f9d8-1f3fb · 1f9d8-1f3fb-200d-2640-fe0f · 1f9d8-1f3fb-200d-2642-fe0f · 1f9d8-1f3fc · 1f9d8-1f3fc-200d-2640-fe0f · 1f9d8-1f3fc-200d-2642-fe0f · 1f9d8-1f3fd · 1f9d8-1f3fd-200d-2640-fe0f · 1f9d8-1f3fd-200d-2642-fe0f · 1f9d8-1f3fe · 1f9d8-1f3fe-200d-2640-fe0f · 1f9d8-1f3fe-200d-2642-fe0f · 1f9d8-1f3ff · 1f9d8-1f3ff-200d-2640-fe0f · 1f9d8-1f3ff-200d-2642-fe0f · 1f9d8-200d-2640-fe0f · 1f9d8-200d-2642-fe0f · 1f9d9 · 1f9d9-1f3fb · 1f9d9-1f3fb-200d-2640-fe0f · 1f9d9-1f3fb-200d-2642-fe0f · 1f9d9-1f3fc · 1f9d9-1f3fc-200d-2640-fe0f · 1f9d9-1f3fc-200d-2642-fe0f · 1f9d9-1f3fd · 1f9d9-1f3fd-200d-2640-fe0f · 1f9d9-1f3fd-200d-2642-fe0f · 1f9d9-1f3fe · 1f9d9-1f3fe-200d-2640-fe0f · 1f9d9-1f3fe-200d-2642-fe0f · 1f9d9-1f3ff · 1f9d9-1f3ff-200d-2640-fe0f · 1f9d9-1f3ff-200d-2642-fe0f · 1f9d9-200d-2640-fe0f · 1f9d9-200d-2642-fe0f · 1f9da · 1f9da-1f3fb · 1f9da-1f3fb-200d-2640-fe0f · 1f9da-1f3fb-200d-2642-fe0f · 1f9da-1f3fc · 1f9da-1f3fc-200d-2640-fe0f · 1f9da-1f3fc-200d-2642-fe0f · 1f9da-1f3fd · 1f9da-1f3fd-200d-2640-fe0f · 1f9da-1f3fd-200d-2642-fe0f · 1f9da-1f3fe · 1f9da-1f3fe-200d-2640-fe0f · 1f9da-1f3fe-200d-2642-fe0f · 1f9da-1f3ff · 1f9da-1f3ff-200d-2640-fe0f · 1f9da-1f3ff-200d-2642-fe0f · 1f9da-200d-2640-fe0f · 1f9da-200d-2642-fe0f · 1f9db · 1f9db-1f3fb · 1f9db-1f3fb-200d-2640-fe0f · 1f9db-1f3fb-200d-2642-fe0f · 1f9db-1f3fc · 1f9db-1f3fc-200d-2640-fe0f · 1f9db-1f3fc-200d-2642-fe0f · 1f9db-1f3fd · 1f9db-1f3fd-200d-2640-fe0f · 1f9db-1f3fd-200d-2642-fe0f · 1f9db-1f3fe · 1f9db-1f3fe-200d-2640-fe0f · 1f9db-1f3fe-200d-2642-fe0f · 1f9db-1f3ff · 1f9db-1f3ff-200d-2640-fe0f · 1f9db-1f3ff-200d-2642-fe0f · 1f9db-200d-2640-fe0f · 1f9db-200d-2642-fe0f · 1f9dc · 1f9dc-1f3fb · 1f9dc-1f3fb-200d-2640-fe0f · 1f9dc-1f3fb-200d-2642-fe0f · 1f9dc-1f3fc · 1f9dc-1f3fc-200d-2640-fe0f · 1f9dc-1f3fc-200d-2642-fe0f · 1f9dc-1f3fd · 1f9dc-1f3fd-200d-2640-fe0f · 1f9dc-1f3fd-200d-2642-fe0f · 1f9dc-1f3fe · 1f9dc-1f3fe-200d-2640-fe0f · 1f9dc-1f3fe-200d-2642-fe0f · 1f9dc-1f3ff · 1f9dc-1f3ff-200d-2640-fe0f · 1f9dc-1f3ff-200d-2642-fe0f · 1f9dc-200d-2640-fe0f · 1f9dc-200d-2642-fe0f · 1f9dd · 1f9dd-1f3fb · 1f9dd-1f3fb-200d-2640-fe0f · 1f9dd-1f3fb-200d-2642-fe0f · 1f9dd-1f3fc · 1f9dd-1f3fc-200d-2640-fe0f · 1f9dd-1f3fc-200d-2642-fe0f · 1f9dd-1f3fd · 1f9dd-1f3fd-200d-2640-fe0f · 1f9dd-1f3fd-200d-2642-fe0f · 1f9dd-1f3fe · 1f9dd-1f3fe-200d-2640-fe0f · 1f9dd-1f3fe-200d-2642-fe0f · 1f9dd-1f3ff · 1f9dd-1f3ff-200d-2640-fe0f · 1f9dd-1f3ff-200d-2642-fe0f · 1f9dd-200d-2640-fe0f · 1f9dd-200d-2642-fe0f · 1f9de · 1f9de-200d-2640-fe0f · 1f9de-200d-2642-fe0f · 1f9df · 1f9df-200d-2640-fe0f · 1f9df-200d-2642-fe0f · 1f9e0 · 1f9e1 · 1f9e2 · 1f9e3 · 1f9e4 · 1f9e5 · 1f9e6 · 1f9e7 · 1f9e8 · 1f9e9 · 1f9ea · 1f9eb · 1f9ec · 1f9ed · 1f9ee · 1f9ef · 1f9f0 · 1f9f1 · 1f9f2 · 1f9f3 · 1f9f4 · 1f9f5 · 1f9f6 · 1f9f7 · 1f9f8 · 1f9f9 · 1f9fa · 1f9fb · 1f9fc · 1f9fd · 1f9fe · 1f9ff · 1fa70 · 1fa71 · 1fa72 · 1fa73 · 1fa74 · 1fa75 · 1fa76 · 1fa77 · 1fa78 · 1fa79 · 1fa7a · 1fa7b · 1fa7c · 1fa80 · 1fa81 · 1fa82 · 1fa83 · 1fa84 · 1fa85 · 1fa86 · 1fa87 · 1fa88 · 1fa89 · 1fa8a · 1fa8e · 1fa8f · 1fa90 · 1fa91 · 1fa92 · 1fa93 · 1fa94 · 1fa95 · 1fa96 · 1fa97 · 1fa98 · 1fa99 · 1fa9a · 1fa9b · 1fa9c · 1fa9d · 1fa9e · 1fa9f · 1faa0 · 1faa1 · 1faa2 · 1faa3 · 1faa4 · 1faa5 · 1faa6 · 1faa7 · 1faa8 · 1faa9 · 1faaa · 1faab · 1faac · 1faad · 1faae · 1faaf · 1fab0 · 1fab1 · 1fab2 · 1fab3 · 1fab4 · 1fab5 · 1fab6 · 1fab7 · 1fab8 · 1fab9 · 1faba · 1fabb · 1fabc · 1fabd · 1fabe · 1fabf · 1fac0 · 1fac1 · 1fac2 · 1fac3 · 1fac3-1f3fb · 1fac3-1f3fc · 1fac3-1f3fd · 1fac3-1f3fe · 1fac3-1f3ff · 1fac4 · 1fac4-1f3fb · 1fac4-1f3fc · 1fac4-1f3fd · 1fac4-1f3fe · 1fac4-1f3ff · 1fac5 · 1fac5-1f3fb · 1fac5-1f3fc · 1fac5-1f3fd · 1fac5-1f3fe · 1fac5-1f3ff · 1fac6 · 1fac8 · 1facd · 1face · 1facf · 1fad0 · 1fad1 · 1fad2 · 1fad3 · 1fad4 · 1fad5 · 1fad6 · 1fad7 · 1fad8 · 1fad9 · 1fada · 1fadb · 1fadc · 1fadf · 1fae0 · 1fae1 · 1fae2 · 1fae3 · 1fae4 · 1fae5 · 1fae6 · 1fae7 · 1fae8 · 1fae9 · 1faea · 1faef · 1faf0 · 1faf0-1f3fb · 1faf0-1f3fc · 1faf0-1f3fd · 1faf0-1f3fe · 1faf0-1f3ff · 1faf1 · 1faf1-1f3fb · 1faf1-1f3fb-200d-1faf2-1f3fc · 1faf1-1f3fb-200d-1faf2-1f3fd · 1faf1-1f3fb-200d-1faf2-1f3fe · 1faf1-1f3fb-200d-1faf2-1f3ff · 1faf1-1f3fc · 1faf1-1f3fc-200d-1faf2-1f3fb · 1faf1-1f3fc-200d-1faf2-1f3fd · 1faf1-1f3fc-200d-1faf2-1f3fe · 1faf1-1f3fc-200d-1faf2-1f3ff · 1faf1-1f3fd · 1faf1-1f3fd-200d-1faf2-1f3fb · 1faf1-1f3fd-200d-1faf2-1f3fc · 1faf1-1f3fd-200d-1faf2-1f3fe · 1faf1-1f3fd-200d-1faf2-1f3ff · 1faf1-1f3fe · 1faf1-1f3fe-200d-1faf2-1f3fb · 1faf1-1f3fe-200d-1faf2-1f3fc · 1faf1-1f3fe-200d-1faf2-1f3fd · 1faf1-1f3fe-200d-1faf2-1f3ff · 1faf1-1f3ff · 1faf1-1f3ff-200d-1faf2-1f3fb · 1faf1-1f3ff-200d-1faf2-1f3fc · 1faf1-1f3ff-200d-1faf2-1f3fd · 1faf1-1f3ff-200d-1faf2-1f3fe · 1faf2 · 1faf2-1f3fb · 1faf2-1f3fc · 1faf2-1f3fd · 1faf2-1f3fe · 1faf2-1f3ff · 1faf3 · 1faf3-1f3fb · 1faf3-1f3fc · 1faf3-1f3fd · 1faf3-1f3fe · 1faf3-1f3ff · 1faf4 · 1faf4-1f3fb · 1faf4-1f3fc · 1faf4-1f3fd · 1faf4-1f3fe · 1faf4-1f3ff · 1faf5 · 1faf5-1f3fb · 1faf5-1f3fc · 1faf5-1f3fd · 1faf5-1f3fe · 1faf5-1f3ff · 1faf6 · 1faf6-1f3fb · 1faf6-1f3fc · 1faf6-1f3fd · 1faf6-1f3fe · 1faf6-1f3ff · 1faf7 · 1faf7-1f3fb · 1faf7-1f3fc · 1faf7-1f3fd · 1faf7-1f3fe · 1faf7-1f3ff · 1faf8 · 1faf8-1f3fb · 1faf8-1f3fc · 1faf8-1f3fd · 1faf8-1f3fe · 1faf8-1f3ff · 203c · 2049 · 2122 · 2139 · 2194 · 2195 · 2196 · 2197 · 2198 · 2199 · 21a9 · 21aa · 23-20e3 · 231a · 231b · 2328 · 23cf · 23e9 · 23ea · 23eb · 23ec · 23ed · 23ee · 23ef · 23f0 · 23f1 · 23f2 · 23f3 · 23f8 · 23f9 · 23fa · 24c2 · 25aa · 25ab · 25b6 · 25c0 · 25fb · 25fc · 25fd · 25fe · 2600 · 2601 · 2602 · 2603 · 2604 · 260e · 2611 · 2614 · 2615 · 2618 · 261d · 261d-1f3fb · 261d-1f3fc · 261d-1f3fd · 261d-1f3fe · 261d-1f3ff · 2620 · 2622 · 2623 · 2626 · 262a · 262e · 262f · 2638 · 2639 · 263a · 2640 · 2642 · 2648 · 2649 · 264a · 264b · 264c · 264d · 264e · 264f · 2650 · 2651 · 2652 · 2653 · 265f · 2660 · 2663 · 2665 · 2666 · 2668 · 267b · 267e · 267f · 2692 · 2693 · 2694 · 2695 · 2696 · 2697 · 2699 · 269b · 269c · 26a0 · 26a1 · 26a7 · 26aa · 26ab · 26b0 · 26b1 · 26bd · 26be · 26c4 · 26c5 · 26c8 · 26ce · 26cf · 26d1 · 26d3 · 26d3-fe0f-200d-1f4a5 · 26d4 · 26e9 · 26ea · 26f0 · 26f1 · 26f2 · 26f3 · 26f4 · 26f5 · 26f7 · 26f7-1f3fb · 26f7-1f3fc · 26f7-1f3fd · 26f7-1f3fe · 26f7-1f3ff · 26f8 · 26f9 · 26f9-1f3fb · 26f9-1f3fb-200d-2640-fe0f · 26f9-1f3fb-200d-2642-fe0f · 26f9-1f3fc · 26f9-1f3fc-200d-2640-fe0f · 26f9-1f3fc-200d-2642-fe0f · 26f9-1f3fd · 26f9-1f3fd-200d-2640-fe0f · 26f9-1f3fd-200d-2642-fe0f · 26f9-1f3fe · 26f9-1f3fe-200d-2640-fe0f · 26f9-1f3fe-200d-2642-fe0f · 26f9-1f3ff · 26f9-1f3ff-200d-2640-fe0f · 26f9-1f3ff-200d-2642-fe0f · 26f9-fe0f-200d-2640-fe0f · 26f9-fe0f-200d-2642-fe0f · 26fa · 26fd · 2702 · 2705 · 2708 · 2709 · 270a · 270a-1f3fb · 270a-1f3fc · 270a-1f3fd · 270a-1f3fe · 270a-1f3ff · 270b · 270b-1f3fb · 270b-1f3fc · 270b-1f3fd · 270b-1f3fe · 270b-1f3ff · 270c · 270c-1f3fb · 270c-1f3fc · 270c-1f3fd · 270c-1f3fe · 270c-1f3ff · 270d · 270d-1f3fb · 270d-1f3fc · 270d-1f3fd · 270d-1f3fe · 270d-1f3ff · 270f · 2712 · 2714 · 2716 · 271d · 2721 · 2728 · 2733 · 2734 · 2744 · 2747 · 274c · 274e · 2753 · 2754 · 2755 · 2757 · 2763 · 2764 · 2764-fe0f-200d-1f525 · 2764-fe0f-200d-1fa79 · 2795 · 2796 · 2797 · 27a1 · 27b0 · 27bf · 2934 · 2935 · 2a-20e3 · 2b05 · 2b06 · 2b07 · 2b1b · 2b1c · 2b50 · 2b55 · 30-20e3 · 3030 · 303d · 31-20e3 · 32-20e3 · 3297 · 3299 · 33-20e3 · 34-20e3 · 35-20e3 · 36-20e3 · 37-20e3 · 38-20e3 · 39-20e3 · a9 · ae · e50a
Physics — curated scientific apparatus and components · 5 · CC0
asset:svg/physics/<name>.svg
effusion-reservoir · rocket-cutaway · rocket-first-stage · rocket-second-stage · rocket-upper-stage
Elevating a scene
A one-line sim already animates:
spring(sp); run(sp, 8);
That’s the minimal scene — correct, but bare. An elevated scene turns the same sim into a narrated lesson: parts revealed one at a time, each labelled, the governing law stated on screen, then the motion played across several synchronized views.
The key fact that makes this possible: a sim’s parts are ordinary manic
entities. spring(sp, …) lays out sp.wall, sp.spring, sp.mass, sp.path,
plus the tag sp.parts over all of them. Every base verb, modifier, and
annotation addresses those ids directly — there is no separate “physics mode”.
So you elevate a scene with the vocabulary you already know.
The three moves
1 · Stage — hide the parts, then reveal them in order
Build the sim, then hide each part (and any extra view) so you can bring them in
deliberately. untraced on a path keeps it in the scene at zero draw-progress so
you can draw it on later.
spring(sp, (360,300), 10, 1.4, 110);
hidden(sp.wall); hidden(sp.spring); hidden(sp.mass); hidden(sp.overlays);
untraced(sp.path);
well(sp, (1010,230), 120); hidden(sp.well); // an extra "reading", revealed later
2 · Annotate — name the parts with base entities
Point at things with text + leader arrow, mark a length or displacement with
bracelabel, drop a reference line, and state the law with a text. Create
them all now, hidden, so the reveal order is yours to choreograph.
text(coilL,(300,205),"spring, stiffness k"); color(coilL,lime); display(coilL); hidden(coilL);
bracelabel(xb,(360,352),(514,352),"x₀",22); color(xb,gold); hidden(xb); hidden(xb.label);
text(hooke,(360,150),"restoring force F = −k·x"); color(hooke,gold); display(hooke); hidden(hooke);
3 · Choreograph — chapters, emphasis, then run
Narrate with section + say, reveal with show/draw, emphasize with
flash/pulse/recolor, fade the clutter before the motion, then run.
section("Hooke's law");
say(cap, "pull it x₀ from equilibrium — it pulls straight back, F = −k·x", 0.4);
show(sp.spring, 0.4); flash(sp.spring, gold); show(xb, 0.4); show(xb.label, 0.4);
wait(0.8);
fade(xb, 0.3); fade(xb.label, 0.3); fade(hooke, 0.3); // declutter
section("Motion");
show(sp.overlays, 0.4); show(sp.well, 0.5); draw(sp.path, 0.6);
run(sp, 10); // sim, overlays, well ball, energy sweep — all animate together
The lever kit
Every one of these is base vocabulary that works on any entity — a sim part, a shape, a label — so the same kit elevates a physics sim, a geometry construction, or an algorithm trace.
| Lever | Builtins | Buys you |
|---|---|---|
| Stage the parts | hidden(id.part), untraced(id.path), then show/draw | reveal piece-by-piece instead of all at once |
| Narrate (light) | say(cap,"…",dur), a small text kicker updated with say, wait(dur) | chapter the story without covering the stage |
| Typewriter | type(id,dur) + cursor(id) (set the string with say(id,"…",0.1) first) | a lab-note / terminal feel that types itself out |
| Live data | counter(id,(x,y),start,decimals,"pre","suf") + to(id, value, target, dur) | a number that ticks up (k, period, acceleration) |
| Camera | cam((x,y),dur,ease) + zoom(factor,dur,ease) | push in on a part, then pull back — cinematography |
| Pin a HUD | sticky(id) | keep a caption / counter fixed on screen while the camera cam/zooms the world |
| Kinetic type | caption(id,"…",(x,y),size,color) + wordpop(id,dur) / karaoke(id,dur,color) | words pop in, or a highlight sweeps across them |
| Name the parts | text, leader arrow, bracelabel, reference line | say what each thing is |
| Emphasize | flash(id,color), pulse(id), recolor(id,color,dur), glow(id,amt), shake(id), spin(id,deg,dur) | move the eye to the part being discussed |
| Add ambient life | particles(id, circle_or_rect, count, radius, seed) + wander(id,dur) | contained bubbles, dust, stars, data, or molecules without hand-animating dots |
| Change a particle state | particles(...,"grid") + `arrange(id,container,“random | grid |
| Show a transfer | link(id,a,b,bend) + flow(id,dur) | a tracked curved connection and a travelling emphasis pulse for energy, signals, traffic, or attention |
| Move a real object on a path | travel(marker,path,dur,ease) | a plot marker, vehicle, token, or probe follows the authored path once and stops at its endpoint |
| Preserve identity | watermark(mark) or watermark(mark,(x,y),"text") | a persistent screen-fixed mark; use the responsive bottom-right default or move it away from platform UI / important content |
| Broadcast | flash(id.parts, lime) | hit every part of the sim at once |
| State the law | a text with the formula | tie the motion to the equation |
| Multiple readings | reveal well / phase / timegraph / energygraph, then run | one motion, several synchronized views |
| Declutter | fade(...) the static annotations | clean playback |
Careful with
section("…"). It drops a full-screen title card (an 820×240 backdrop over the whole stage) and holds it ~2.2 s. Two or three of those and the interstitials start to bury the animation you’re trying to show. Reach for it only for a genuine hard scene change; for beat-to-beat chapters prefer a lightweight persistent kicker/captionupdated withsay, or let the typewriter / camera / kinetic-type do the pacing. Vary the lens per scene — if every lesson uses the identicalsection+say+showloop, the medium looks one-note; the point of the kit above is that manic has range.
Reveal-order gotchas
show/fadeforce opacity to 1 / 0. They will override a deliberately faint entity (a dim reference line, a panel frame). To bring in something that should stay faint,drawit (traces the stroke, leaves opacity alone) or justdisplayit faint from the start — don’tshowit.drawis the nicest reveal for strokes — lines, arrows, curves, and paths sketch themselves on. Set the entityuntracedfirst, thendraw(id, dur).fadethe annotations before the motion so the swing plays against a clean stage.- Loops (
+timeline) reference only ids that exist — build every part before the script that animates it.
Worked examples
The gallery pairs each elevated lesson with its minimal reference — and each one leads with a different lens, to show the range:
- Anatomy of a Spring (
spring-annotated) — typewriter + live data: atyped lab-note with acursor, andcounters that tick k and the period up, over Hooke’s law → parabolic well → SHM. - The Atwood Machine (
pulley-annotated) — camera:cam+zoompush in on the masses for the imbalance beat, then pull back to release; the caption + counter aresticky, so they stay pinned through the zoom. - Brachistochrone race (
brachistochrone-annotated) — kinetic typography on a multi-body sim:wordpopthe question,karaokethe path names as the curves sketch on, then crown the cycloid.
Notice none of them use section — the pacing comes from the lens instead, so the
motion is never hidden behind a card.
The three-move recipe is domain-agnostic: it elevates anything with addressable parts, not just the physics kit.
Going 3D
So far every shape has lived on a flat page. manic has a second world: a real 3D space you look at through a camera. The idea is exactly the same — you name a cast of shapes and call them out in a script — but now the shapes have depth, and you can spin the camera around them.
One rule of thumb: everything 3D ends in 3 — cube3, sphere3, move3,
orbit3. That’s how you tell the two worlds apart.
Generated and derived 3D geometry
These constructors cover large scientific structures without expanding the scene into thousands of named objects:
implicit3(shell,"x*x+y*y-z*z",(-2,2),(-2,2),(-2,2),1,24);
collection3data(nodes,(0,0,0),"0 0 0; 1 0 0; 1 1 1; 0 1 0");
links3data(edges,nodes,"0 1; 1 2; 2 3; 3 0");
lsystem3(tree,(0,0,0),0.18,25,5,"F","F=F[+F][-F][^F]");
implicit3samples a bounded scalar field and extracts one isosurface.collection3dataandlinks3dataretain explicit coordinates and arbitrary edges as two batches—useful for Cayley diagrams, dependency graphs, and imported scientific datasets.lsystem3is a 3D turtle grammar:Fdraws,fmoves,+/-yaw,^/&pitch,</>roll, and brackets branch.
To show the three shadows of a moving curve, derive them from the source:
curve3(path,"2*cos(t)","2*sin(t)","t/3",(0,12.56));
projectpath3(xyShadow,path,xy);
projectpath3(xzShadow,path,xz);
projectpath3(yzShadow,path,yz);
par {
rotate3(path,(20,50,10),2,smooth);
orbit3(55,20,18,2,smooth);
}
The projected paths resample the source after its current transform, so the shadows cannot drift away from the geometry they explain.
First, a camera
A 3D scene needs a camera — an eye to look through. You say where the eye sits and which point it looks at:
camera3((8, -10, 6), (0, 0, 1), 45); // eye position, look-at point, zoom
Positions are (x, y, z), and here z is up (x and y are the ground). You
add one camera, and you can swing it around later.
The creator-first five
For most explainers, start with the relationship you want rather than camera coordinates or intermediate positions. 3D V2 adds five composition words:
| word | creator intent |
|---|---|
view3(subject, "isometric", 1, smooth, 1.3) | frame one object or tagged group, with margin |
travel3(subject, route, 2, smooth) | move along a line3, arrow3, or curve3 |
attach3(label, subject, (0,0,1)) | position-only follow (compatible default); add rigid to inherit orientation; use none to release |
become3(subject, blueprint, 1, smooth) | keep the subject id while it adopts another shape and style |
turn3(group, pivot, z, 90, 1, smooth) | rigidly turn one object or tagged group around a pivot and axis |
Here is the core pattern. The sensor and wings travel with the ship, release without snapping, then the wings deploy and the ship becomes its final design:
attach3(sensor, ship, (0,0,0.8));
attach3(leftWing, ship, (0,-1.2,0));
attach3(rightWing, ship, (0,1.2,0));
par { draw(route, 2.8, smooth); travel3(ship, route, 2.8, smooth); }
attach3(sensor, none);
attach3(leftWing, none); attach3(rightWing, none);
par {
turn3(wings, ship, z, 90, 1.1, smooth);
become3(ship, finalBlueprint, 1.1, smooth);
}
view3(spacecraft, "isometric", 1.2, smooth, 1.4);
▶ Compact five-word reference:
▶ Vertical creator story:
▶ Production lab (relationships, contours, finishes, OBJ, and variable tube):
Design details that make 3D feel professional
- Give the main subject one persistent id. Declare alternate forms as hidden
blueprints and use
become3; do not swap the whole scene. - Tag parts that should turn or frame together.
view3andturn3both accept a tag, so one spatial rig remains one authoring concept. - Attach before the shared move; release immediately before a part gets its own motion. Release preserves the resolved world position.
- Use
view3(...,"fit")to preserve the current viewing direction, or choosefront,side,top, orisometric. A margin around1.15–1.35is useful for landscape; start around1.45–1.7for vertical video with text above and a social footer below. - Put shot changes at idea boundaries, then let object motion carry the middle of the explanation. Constant camera motion makes depth harder to read.
view3uses transformed group bounds and the active canvas aspect ratio, so the same intent works in landscape and portrait without a second 3D mode. In a creator/quiz scene it also fits the actual media rectangle, not the full canvas behind the heading and social footer.travel3reads the route’s current transform every frame. A route may rotate or move in the sameparblock without pre-baking dozens of coordinates.- Use
attach3(part, body, offset, rigid)for a mechanical assembly. Its offset lives in the body’s local frame, it turns with the body, and release freezes both position and orientation exactly.
▶ See it play:
Scientific frames: 2D, textbook 3D, and spatial 3D
Use the smallest truthful dimension. A genuinely planar geometry problem should
stay in Manic’s native 2D shapes. When the relationship is spatial but the
result belongs on a calm textbook page, use a real 3D frame3 with an
orthographic camera and mode=textbook. When depth becomes part of the lesson,
restyle that same frame and orbit it:
camera3((9,-11,8), (0,0,0), 12, orthographic);
frame3(world, (0,0,0), (8,8,6),
"x=-4..4 y=-4..4 z=-3..3 planes=xy:min,xz:min,yz:min major=2 minor=1 mode=textbook");
// Later: same coordinates, geometry, labels, and identity.
par {
present3(world, spatial, 0.7, smooth);
orbit3(52, 28, 12, 2.4, smooth);
}
The options are compact and composable:
| need | option |
|---|---|
| data bounds | x=-2..4 y=0..8 z=0.1..100 |
| one or more walls | planes=xy:min,xz:min,yz:min |
| a top or central plane | planes=xy:max,xz:origin |
| an exact section | planes=xy:-0.5 |
| two coloured parallel sections | planes=xy:-0.5@cyan,xy:1.5@magenta |
| shared intervals | major=1 minor=0.5 |
| independent intervals | xmajor=0.5 ymajor=1 zmajor=2 |
| logarithmic data | zscale=log (the corresponding range must be positive) |
| page or spatial styling | mode=textbook or mode=spatial |
Every generated role is addressable through tags: world.axes, world.axis.x,
world.grids, world.grid.xy, world.grid.major, world.grid.minor,
world.ticks, and world.labels. This makes ordinary show, fade, pulse,
and recolor useful without adding graph-specific animation commands.
grid3 and axes3 remain the fastest defaults for a simple positive stage.
Choose frame3 when bounds, log scale, multiple walls, repeated sections, or
textbook/spatial continuity matter.
▶ Solve the 3 × 4 × 12 box in three honest views:
▶ Pure 3D: shortest distance from a point to a plane:
▶ Six Asymptote-inspired grid policies:
The 3D cast
| shape | write | draws |
|---|---|---|
| cube3 | cube3(box, (0,0,1), (2,2,2)); | a box (width, depth, height) |
| sphere3 | sphere3(ball, (0,0,1), 0.9); | a ball of that radius |
| point3 | point3(p, (1,1,1)); | a small marker in space |
| line3 / arrow3 | arrow3(v, (0,0,0), (0,0,2)); | a segment / a vector |
| grid3 | grid3(floor, (0,0,0), 5, 1); | a ground grid to sit things on |
| axes3 | axes3(ax, (0,0,0), 3); | labelled x, y, z arrows |
| frame3 | frame3(world,(0,0,0),(8,8,6),"planes=xy:min,xz:min mode=textbook"); | bounded scientific axes plus selected grid planes |
| randomwalk3 | randomwalk3(walk,(0,0,0),10000,21,"mode=turtle angle=60 color=turn shade=depth"); | one deterministic 3D path with a local/world direction model |
| hilbert3 | hilbert3(curve,(0,0,0),6,4); | one exact space-filling path through all cells of a 3D lattice |
Style and reveal them with words you already know — color, opacity, show,
flash:
cube3(box, (0, 0, 1), (2, 2, 2)); color(box, cyan);
show(box, 0.6);
Labelling a point in space
To put words on a 3D point, make an ordinary 2D text and pin it there with
pin3. As the camera moves, the label sticks to its point:
text(tag, (0, 0), "origin");
pin3(tag, (0, 0, 0), (18, -14)); // optional screen offset clears the marker
pin3 deliberately stays the same screen size. When the label should feel as
if it lives in the world, use label3 and give its desired world height:
text(pName, (0,0), "P");
label3(pName, pointP, 0.35); // gets smaller as P moves away
Relationships, projections, and contours
These are live construction words, not snapshots:
point3(p, (2,1,3));
project3(shadow, p, "xy");
link3(drop, p, shadow, 0.06);
surface3(bowl, "x^2+y^2", (-2,2), (-2,2), 24);
contour3(levelOne, bowl, 1);
Move p and both shadow and drop recompute every frame. contour3 accepts
a surface3 height field and extracts the requested z level. Use these words
for geometry, vectors, optimization, fields, and engineering callouts—the
engine owns the relationship while the author owns the story.
Deterministic 3D random walks
randomwalk3 keeps tens of thousands of decisions in one path, so draw-on,
camera fitting, direct seeking, and export remain predictable:
camera3((10,-12,8),(0,0,0),38,orthographic);
randomwalk3(walk,(0,0,0),12000,21,
"mode=turtle angle=60 distribution=gaussian color=turn shade=depth scale=0.12");
untraced(walk);
par {
draw(walk,2.4,smooth);
view3(walk,"fit",0.7,smooth,1.2);
}
mode=axischooses one of ±x, ±y, ±z on every step.mode=turtleadvances along a local heading, then turns that orientation frame; useangle=90or60for the classic textbook constructions.distribution=uniform|gaussianchanges how the six choices are sampled.color=direction(orturn) paints segments by the selected choice.shade=depthadds a camera-aware far/near cue without changing the path.scaleis the world-space length of every step.seedmakes comparison and rerendering exact. The bounded maximum is 50,000 steps.
See asymptote-randomwalk3-reference.manic for the recurring Asymptote models
and creator-randomwalk3-diffusion.manic for a 100 → 1,000 → 10,000-step
explanation.
A line that fills a cube
hilbert3 generates one exact 3D Hilbert path as a single seekable entity.
Order 1 visits 8 cells with 7 segments; every refinement replaces each visit
with eight smaller visits, so order 5 has 32,767 segments:
camera3((13,-15,11),(0,0,0),42);
hilbert3(curve,(0,0,0),6,1);
hilbert3(next,(0,0,0),6,2); hidden(next);
untraced(curve);
draw(curve,1.5,smooth);
become3(curve,next,1.3,smooth);
The default arc-length gradient stays continuous through become3; choose
"color=single" when an ordinary color(curve, cyan) should paint the whole
path. Orders are deliberately bounded to 1–5 so direct seeking and production
rendering remain predictable. See
How one line fills a cube for the full
7 → 32,767-segment story with a continuous camera and CTA.
Large evolving 3D stories
Four small foundations cover dependency clouds, articulated mechanisms, time-varying fields, and addressable model parts without exposing frame callbacks.
| Intent | Manic words |
|---|---|
| many stable points | collection3 |
| relationships among them | links3, child3 |
| one dependent articulated chain | chain3, trail3 |
| a changing 3D field and its motion | vectorfield3, advect3 |
| a camera that follows one member | followshot3 |
| named parts inside one OBJ | assembly3 |
| a small offline sound beat | cue |
Here is the reusable field pattern:
camera3((8,-10,6), (0,0,0), 42);
collection3(seeds, (-2,0,0), 48, 1.2, 21, 0.045);
vectorfield3(flow, (0,0,0), 4,
"-y + 0.35*sin(2*pi*p)",
"x + 0.25*cos(2*pi*p)",
"0.25*sin(x+y+2*pi*p)", 5);
child3(hero, seeds, 0, 0.08);
trail3(history, seeds, 0, 0.025);
par {
advect3(seeds, flow, 5, 0.45);
followshot3(hero);
}
followshot3(none);
p is normalized absolute time from 0 to 1. Manic precompiles the RK4 paths,
then samples those paths, the field arrows, the child proxy, the trail, and the
camera target from the same timeline time. Scrubbing backward or jumping
straight to the middle is therefore repeatable.
Use collection3 for a fixed-count repeated 3D cast. It renders as a batch,
while child3 exposes only the member that needs a label or camera. links3
can create chain, nearest, or all relationships without hundreds of
handwritten link3 calls:
collection3(cloud, (0,0,0), 80, 3, 42);
links3(neighborhood, cloud, nearest, 2);
drift3(cloud, 4, 0.6);
For a dependent mechanism, every chain3 endpoint starts where its predecessor
ended. The history is taken from the real compiled endpoint route:
collection3(arm, (0,0,0), 4, 0, 7, 0.07);
chain3(arm, "1.8 1.35 1.0", "1.0 -1.8 2.4", 6);
links3(bones, arm, chain);
ring3(firstOrbit, arm, 0, 72);
ring3(secondOrbit, arm, 1, 72);
trail3(tipHistory, arm, 3, 0.035);
ring3 does not approximate a separately animated circle. Its centre is the
previous endpoint and its radius is the current distance to the chosen child,
so it remains correct while the chain rotates, travels, seeks, or rewinds.
Turn that same member history into a truthful screen-space plot with
historyplot. Choose x, y, or z; Manic uses the complete compiled range
for stable scaling and reveals only the history reached so far:
historyplot(wave, arm, 3, y, (540,1250), (820,320));
Use historyplot3 when the trace belongs inside the 3-D world rather than the
screen overlay. Its origin is a world coordinate and its size is measured in
world units, so depth stacking, focus shots, orbiting, and follow cameras all
remain coherent:
historyplot3(wave3, arm, 3, y, (-3,-4,2), (8,2.5));
view3(wave3, "front", 1, smooth, 1.2);
Both forms are derived from the same compiled child motion. Use historyplot
for a dashboard or fixed teaching panel; use historyplot3 for layered
Fourier constructions, spatial signal galleries, and camera-led explanations.
For a procedural tree, tree3 keeps authoring and rendering bounded. Each
generation is one addressable edge batch, while leaves are one collection:
tree3(tree, (0,0,0), 2.1, 27, 0.72, 10, 42);
for i in 0..10 { untraced(tree.d{i}); }
stagger(0.12) { for i in 0..10 { draw(tree.d{i},0.5,smooth); } }
view3(tree, "isometric", 1.0, smooth, 1.35);
This is the right tradeoff for creator files: a meaningful generated recipe, normal layers/tags and camera verbs, but no recursive user callback.
For grouped assets, OBJ group names become safe part ids:
assembly3(console, "asset:models/manic-console.obj", (0,0,0), 1.4);
text(screenLabel, (0,0), "signal");
label3(screenLabel, console.screen, 0.28);
cue(chime);
This is deliberately bounded: stable-count collections, formula fields, named OBJ groups, camera-facing labels, and four local cues. GLB node/material hierarchies, arbitrary SFX files, shader callbacks, count-changing children, and true occluding 3D glyph meshes remain future layers.
▶ Living dependency cloud:
▶ Dependent chain and truthful history:
▶ Fourier chain to live derived waveform:
▶ Five depth-layered Fourier families (DefinedMotion animation2.gif test):
▶ Odd harmonics to square-wave partial sum:
▶ Batched fractal-tree growth:
▶ Time-varying field and follow shot:
▶ Addressable assembly, notation, cue, and particle punctuation:
Textbook dimension-story series
These portrait stories use the same 3D engine for a textbook-friendly purpose: start with one familiar object, preserve its identity, and let the next dimension arrive through motion. Each gallery page contains the complete source and its video card.
| Story | Dimensional journey | Teaching idea |
|---|---|---|
| The trapped light beam | 1D → 2D → 3D | Distance grows from 5 to 13 to 85 through nested right triangles. |
| How space learned to grow | point → line → plane → room | A line sweeps into a surface; the surface lifts into volume. |
| Length, area, volume | 1D → 2D → 3D | Why measured units become cm, cm², and cm³. |
| A point gets an address | x → (x,y) → (x,y,z) | Each new axis adds one coordinate to the same point. |
| The revolving semicircle | diameter → curve → sphere | A solid can be generated from a lower-dimensional rule. |
| Statistical dimensions | list → scatter → cloud | More variables require a richer coordinate world. |
| Dimension reduction | 3D → 2D → 1D | A sphere reveals a section, then the section reveals a diameter. |
| Watermelon sections | whole → halves → ¼ + ¾ | Perpendicular great-circle cuts create meaningful pieces. |
Creator pattern: keep the mathematical subject persistent, introduce one axis
or section at a time, and hold the settled frame long enough to read the new
formula. Use view3(...,"fit") at dimensional boundaries; use ordinary object
motion inside each explanation.
Curves and surfaces
Draw a wire through space from three formulas of t (a helix, here), or a
surface from a height formula z = f(x, y):
curve3(helix, "cos(t)", "sin(t)", "t*0.2", (0, 12));
surface3(wave, "sin(x)*cos(y)", (-3, 3), (-3, 3));
For shapes a plain height field can’t make — a torus, a Möbius strip —
use param3, which takes three formulas of two parameters, u and v:
param3(torus, "(3 + cos(v))*cos(u)", "(3 + cos(v))*sin(u)", "sin(v)",
(0, 6.28), (0, 6.28));
One formula rule: always put
*between names. Writepi*t, neverpit(manic readspitas one unknown word). Same forv*v, notvv.
Make the same generated object change
Use an ordinary parameter, add p to the generated formula, and connect it
with bind. Manic resamples the same object instead of replacing it:
parameter(shape, (640,620), 0, 0, 1, "shape", 2);
surface3(world, "0.22*(x*x+y*y)", (-3,3), (-3,3), 34);
bind(shape, world, formula, "0.22*x*x + 0.22*(1-2*p)*y*y");
to(shape, value, 1, 3, smooth);
The same pattern works for every stable formula family:
bind(shape, helix, formula,
"(1+p)*cos(t)", "(1+p)*sin(t)", "0.3*t");
bind(shape, torus, formula,
"(3+(0.2+p)*cos(v))*cos(u)",
"(3+(0.2+p)*cos(v))*sin(u)",
"(0.2+p)*sin(v)");
This is a useful creator distinction:
- use
bindwhen one curve/surface remains the subject and its mathematical rule changes continuously; - use
morph3when one authored shape becomes a different authored shape.
The bound object keeps its id, resolution, colour, material, transform, and
timeline identity. Manic checks the declared parameter range at build time and
rejects sampled non-finite formulas before recording. Topology and item count
stay fixed, so this is not a per-vertex scripting API. Do not combine morph3
and a generated-family binding on the same target. gradient3,
tangentplane3, and volume3 stay attached to that source and resample as it
deforms, so the measurement and the surface cannot drift apart.
▶ General three-family reference:
▶ Creator Short — solve the bowl-to-saddle transition:
Design tip: author the constructor formula to match the parameter’s initial value. Then the first reveal and the live journey share the same exact shape, with no opening jump.
Textbook sections: halves, quarters, and the remainder
param3 is also the V2 route for an exact authored section of a curved solid.
Bound one parameter to describe only the required half or quarter of the sphere,
and add a second bounded surface for the exposed cut face. This keeps the lesson
mathematical: the geometry is defined by the section, not hidden by a flat mask.
The watermelon example uses that pattern to compare horizontal and vertical great-circle cuts, then makes two perpendicular cuts and separates a quarter from its three-quarter remainder:
// A sphere quarter: longitude u spans 90°, latitude v spans the full height.
param3(quarter,
"3*cos(v)*cos(u)", "3*cos(v)*sin(u)", "3*sin(v)",
(0, pi/2), (-pi/2, pi/2));
finish3(quarter, "shading=smooth material=matte depth=0.25 shadow=0.18");
▶ Animated textbook reconstruction:
Open the complete, copyable source in the 3D scenes gallery.
Practical authoring tips:
- Keep the outside shell and each exposed section face as separate tagged entities. They can be revealed, shifted, and recoloured as one teaching unit.
- Use a light
papertemplate and restrained face colours when the goal is a textbook diagram; depth should clarify the construction, not dominate it. - Frame each new construction with
view3(tag,"fit",...)after the cast changes. The camera then follows the mathematical subject instead of fixed coordinates. - V2 supports exact authored sections through bounded
param3. A generic verb that cuts any arbitrary solid and automatically creates the resulting pieces is intentionally deferred to V3.
Solids
Build filled, shaded solids:
prism3/pyramid3— n-sided prisms and cones (use many sides for a cylinder or a smooth cone).revolve3— spin a radius profiler(t)around the upright axis (vases, spheres, lathe shapes).extrude3— lift a flat 2D shape (even a boolean cut-out) straight up into a solid.
prism3(hex, (0, 0, 1), 6, 1, 2);
revolve3(vase, (3, 0, 1.5), "0.7 + 0.4*sin(t*2)", (0, 3));
Giving lines some body
A 3D line, arrow, or curve is a thin thread by default. thick turns it into a
rounded tube (arrows grow a solid head):
arrow3(v, (0, 0, 0), (2, 2, 2)); thick(v, 0.04);
For a horn, vessel, nerve, pipe, or any path whose radius changes, use a
normalized radius profile (t=0 start, t=1 end):
curve3(spine, "4*t-2", "sin(6*t)*0.2", "0", (0,1));
tube3(horn, spine, "0.06 + 0.28*t", 14);
One optional render finish
The default is still Manic’s restrained, template-aware diagram rendering.
When an object needs a different surface treatment, finish3 keeps the choice
in one bounded string:
finish3(globe, "shading=smooth material=metal depth=0.2 shadow=0.2");
finish3(terrain, "mesh=0.25 texture=checker scale=3");
finish3(shell, "material=glass shading=smooth");
- Start with
shading=smoothfor spheres/organic surfaces and leave boxes flat. - A little
meshclarifies topology;1is intentionally strong. depthandshadoware subtle readability controls from0to1, not a replacement for authored lighting.checkerandstripesare procedural and deterministic; Manic does not load texture scripts or arbitrary shaders.
Controlled OBJ models
model3(mark, "asset:models/manic-pyramid.obj", (0,0,1), 1.4);
finish3(mark, "shading=smooth material=metal mesh=0.12");
assembly3(console, "asset:models/manic-console.obj", (3,0,0), 1.2);
show(console.screen, 0.4);
model3 reads geometry only: OBJ vertices, polygon faces (triangulated), and
lines. It ignores material/script features and enforces file/geometry limits.
An asset: URI selects a file packaged with Manic, so it works from the CLI,
Docker image, or production backend without a launch-directory assumption or
extra flag. An ordinary path such as uploads/my-model.obj still works for a
user-owned model, but the UI/backend must provision that file. Use built-in
solids when they express the same idea—they remain the lightest option.
Available bundled 3D assets
| Stable URI | What it is | Good for |
|---|---|---|
asset:models/manic-pyramid.obj | Small generic pyramid OBJ | Learning model3, a beacon, marker, monument, or placeholder model |
asset:models/manic-console.obj | Grouped console OBJ (base, screen, key) | Learning assembly3, part callouts, staged reveals, and technical product stories |
Bundled names are intentionally few and predictable. See the full
Bundled assets catalog and do not invent an asset: name that is
not listed. To add one to Manic itself, place the
geometry-only file under assets/models/, document its URI here and in
assets/README.md, and add a checked example. The release, Docker, EC2, and
playground pipelines copy the complete assets/ directory automatically, so
future catalog entries need no per-file deployment rule.
Moving in 3D
Same rhythm as the 2D verbs, with the 3 on the end:
par {
rotate3(box, (0, 0, 360), 4, linear); // spin the box
orbit3(70, 25, 12, 4, smooth); // orbit the camera around it
roll3(-20, 4, smooth); // bank around the view direction
}
move3/shift3— move to / by a pointrotate3— turn it (degrees around x, y, z)grow3— stretch a line or arrow’s tip to a new pointorbit3— swing the camera (angle around, angle up, distance)roll3— bank the camera around its viewing direction; it can run besideorbit3inpar, including through stable overhead/underside viewslook3— aim the camera at a new point
These remain the precise controls. Use them when exact coordinates or a specific orbit are part of the explanation; use the creator-first five for camera composition and relationship choreography.
Check the transitions, not only the last frame
manic check examples/three-d-v2-lab.manic --canvas portrait
The publishing audit samples camera transitions between named steps. It warns
when projected 3D bounds leave the creator media rectangle, an orbit/zoom reads
as a shock, the eye enters geometry, or a live spatial relationship has lost
its source. It also checks the settled frame of every step. The most useful
repair is usually semantic: tag the shot’s subject and call view3(tag,"fit")
after the cast changes, or give a camera beat more duration.
Morphing one shape into another
morph3 sets a shape up to become another; then to(..., morph, ...) blends
between them. It works for curves, surfaces, and solids — even a cube turning
into a sphere:
cube3(a, (0, 0, 1), (2, 2, 2));
sphere3(b, (0, 0, 0), 1.2); hidden(b);
morph3(a, b);
to(a, morph, 1, 2.5, smooth); // a cube melts into a ball
Which words work in 3D?
3D shapes speak most of the same vocabulary — color, opacity, hidden,
untraced, tag, and the verbs show, fade, draw, flash, pulse,
scale. A handful of words are 2D-only and will politely refuse on a 3D
shape (with a message that names the 3D replacement):
| if you reach for… | on a 3D shape, use… |
|---|---|
hue | color with a palette name |
stroke | thick |
move / rotate / spin | move3 / rotate3 |
cam / zoom | camera3 / orbit3 |
morph | morph3 |
Now see it all in motion in the 3D scenes gallery.
Examples gallery
Every animation in examples/, by topic — the code and the clip for each. Run any of them with manic examples/<name>.manic. Project: https://8gwifi.org/manic.
- Algorithms & data structures — 6 examples
- Graphs — 5 examples
- Grids — pathfinding & automata — 5 examples
- Calculus & functions — 28 examples
- Linear algebra & tables — 13 examples
- Statistics & probability — 14 examples
- Machine learning — 9 examples
- Physics — simulations — 58 examples
- Optics — light as geometry — 14 examples
- Vectors, fields & coordinates — 5 examples
- Geometry (olympiad) — 8 examples
- Transforms & morphing — 15 examples
- Text & UI — 8 examples
- Generative & recursive — 21 examples
- Creator formats — responsive social video — 32 examples
- Race charts — paste a table, get a race — 5 examples
- Boolean shapes — 1 example
- 3D scenes — 44 examples
- Diagrams — 24 examples
Algorithms & data structures
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
bubble_sort
Real sliding swaps; array + compare + swap.
// Bubble Sort — real sliding swaps. `array` gives fixed slot boxes and value
// cells; `compare(a, i, j)` flashes the values now in slots i and j, and
// `swap(a, i, j)` slides them past each other into the swapped slots. `swap`
// carries the array's occupancy forward, so a whole chain of swaps composes
// correctly (no `say`). We sort [3, 1, 2] -> [1, 2, 3].
//
// manic examples/bubble_sort.manic
title("Bubble Sort");
canvas("16:9");
text(head, (cx, 130), "compare neighbours, swap if out of order");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (cx, 560), ""); color(cap, dim); size(cap, 26);
array(a, "3 1 2", (cx, 360), 100, 100);
show(head, 0.5);
say(cap, "an unsorted array");
wait(0.6);
section("Pass 1");
say(cap, "compare slots 0 and 1: 3 > 1, swap");
compare(a, 0, 1);
swap(a, 0, 1);
say(cap, "compare slots 1 and 2: 3 > 2, swap");
compare(a, 1, 2);
swap(a, 1, 2);
section("Pass 2");
say(cap, "compare slots 0 and 1: 1 < 2, ok");
compare(a, 0, 1, lime);
say(cap, "compare slots 1 and 2: 2 < 3, ok");
compare(a, 1, 2, lime);
section("Sorted");
say(cap, "1 2 3 -- done");
recolor(a.cells, lime, 0.4);
par { pulse(a.c0); pulse(a.c1); pulse(a.c2); }
wait(1.4);
two_pointer
lo/hi index carets scanning inward on a sorted array.
// Two Pointers — the `pointer`/`pointat` primitive. `pointer(id, arr, slot, label)`
// drops a caret under a slot; `pointat(id, arr, slot)` slides it to another slot
// (its label follows). Pointers track slot *positions*, so they stay put as
// values move. Here `lo`/`hi` scan inward on a sorted array.
//
// manic examples/two_pointer.manic
title("Two Pointers");
canvas("16:9");
text(head, (cx, 120), "two pointers scan toward the middle");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (cx, 560), ""); color(cap, dim); size(cap, 26);
array(a, "1 3 5 7 9 11", (cx, 340), 92, 92);
pointer(lo, a, 0, "lo");
pointer(hi, a, 5, "hi");
show(head, 0.5);
say(cap, "start at both ends");
wait(0.5);
compare(a, 0, 5);
say(cap, "step both inward");
par { pointat(lo, a, 1); pointat(hi, a, 4); }
compare(a, 1, 4);
par { pointat(lo, a, 2); pointat(hi, a, 3); }
compare(a, 2, 3);
say(cap, "the pointers have met");
wait(1.2);
stack_queue
LIFO stack + FIFO queue, with action-point carets.
// Stack & Queue — dynamic structures, with carets marking WHERE each op acts.
// `push`/`pop` (stack, LIFO, grows up) and `enqueue`/`dequeue` (queue, FIFO,
// grows right) are mutating verbs: they add a cell and animate it in, tracking
// occupancy so a chain of ops composes. A `caret` marks the action point and is
// `move`d in step with each op so it rides the changing top / back.
//
// manic examples/stack_queue.manic
title("Stack & Queue");
canvas("16:9");
let sx = 300; let sy = 500; // stack anchor (bottom cell centre)
let qx = 780; let qy = 300; // queue anchor (front cell centre)
let cw = 84; let ch = 64; // cell size
let stx = sx + 62; // stack "top" caret sits right of the column
text(head, (cx, 80), "LIFO stack, FIFO queue");
display(head); color(head, cyan); size(head, 28); hidden(head);
text(cap, (cx, 660), ""); color(cap, dim); size(cap, 24);
text(sl, (sx, sy + 66), "stack: push / pop"); color(sl, lime); size(sl, 22);
text(ql, (qx + cw, qy - 118), "queue: enqueue / dequeue"); color(ql, cyan); size(ql, 22);
stack(st, (sx, sy), cw, ch);
queue(qu, (qx, qy), cw, ch);
// action-point markers
caret(top, (stx, sy), "top", left); // rides the top of the stack
caret(front, (qx, qy - 52), "front", down); // fixed: dequeue leaves here
caret(back, (qx, qy + 52), "back", up); // rides the back of the queue
show(head, 0.5);
section("Stack");
say(cap, "push 5, 3, 8 — each lands on top, the top caret rises");
push(st, "5");
par { push(st, "3"); move(top, (stx, sy - ch)); }
par { push(st, "8"); move(top, (stx, sy - 2*ch)); }
say(cap, "pop — the top value (8) leaves, the caret drops back");
par { pop(st); move(top, (stx, sy - ch)); }
wait(0.5);
section("Queue");
say(cap, "enqueue A, B, C — they join at the back caret");
enqueue(qu, "A");
par { enqueue(qu, "B"); move(back, (qx + cw, qy + 52)); }
par { enqueue(qu, "C"); move(back, (qx + 2*cw, qy + 52)); }
say(cap, "dequeue — the front leaves, the rest advance, back shifts in");
par { dequeue(qu); move(back, (qx + cw, qy + 52)); }
par { dequeue(qu); move(back, (qx, qy + 52)); }
say(cap, "stack: in and out at the top. queue: in at back, out at front.");
wait(1.2);
linked_list
Singly / doubly / circular — classic node anatomy + pointer re-threading.
// Linked List — classic anatomy, three variations. A node is a framed box split
// into compartments: singly `[ data | .next ]`, doubly `[ .prev | data | next. ]`,
// where a pointer field carries a dot its arrow starts from. `head` marks the
// entry node; the tail's `next` ends at `NULL` (singly/doubly) or curves back to
// the head (circular). `insert`/`remove` re-thread the pointers — no shifting.
//
// manic examples/linked_list.manic
title("Linked List");
canvas("16:9");
text(head, (cx, 56), "node = data + pointer field(s); head in, NULL or loop at the tail");
display(head); color(head, cyan); size(head, 24); hidden(head);
text(cap, (cx, 700), ""); color(cap, dim); size(cap, 24);
text(t1, (150, 160), "singly"); color(t1, lime); size(t1, 22);
text(t2, (150, 350), "doubly"); color(t2, lime); size(t2, 22);
text(t3, (150, 545), "circular"); color(t3, lime); size(t3, 22);
list(sa, "3 8 5", (cx, 160), singly, 64, 50);
list(da, "3 8 5", (cx, 350), doubly, 64, 50);
list(ca, "3 8 5", (cx, 545), circular, 64, 50);
show(head, 0.5);
say(cap, "three classic variations of the same idea");
wait(0.9);
section("Insert");
say(cap, "insert 7 after node 1 in the doubly list — pointers re-thread, no shift");
insert(da, 1, "7");
wait(0.6);
section("Remove");
say(cap, "remove the head of the singly list — the list re-points past it");
remove(sa, 0);
say(cap, "data + pointers: the whole family from one primitive");
wait(1.4);
textbook-doubly-list
The paper/support TEXTBOOK treatment on the algo kit — proof it’s domain-agnostic:
template("paper") + a hatched support rule turn the doubly linked list into a flat
exam-paper figure (the theme’s palette remap inks it automatically), then it inserts
and removes. Same styling that dressed the physics sims, on a data structure.
// ============================================================================
// textbook-doubly-list.manic — the paper/support treatment on the ALGO kit
// ----------------------------------------------------------------------------
// Proof the textbook look is DOMAIN-AGNOSTIC: the same `template("paper")` page,
// the same std `support` hatch primitive, and the same `color`/`say` staging —
// here on the algo kit's doubly linked list instead of a physics sim. Nothing
// about `support`/`paper` knows about physics; they compose over any kit.
// ============================================================================
title("Doubly linked list — textbook style");
canvas("16:9");
template("paper"); // the white exam-paper page (engine theme)
text(hdr, (cx, 66), "Doubly linked list");
color(hdr, fg); size(hdr, 30); bold(hdr); display(hdr);
support(rule, (cx, 102), 380, "down"); // a hatched section rule — a std primitive, in an ALGO scene
// the algo-kit structure — the paper theme's palette REMAP turns the sim's
// neon colours into ink automatically (light boxes, dark text, muted pointers),
// so no recolouring is needed. The bare id `dll` also broadcasts now, e.g.
// `hidden(dll)` / `flash(dll, cyan)` address the whole list at once.
list(dll, "3 8 5 2", (cx, 340), doubly, 80, 58);
text(cap, (cx, 628), ""); color(cap, fg); size(cap, 22); display(cap);
say(cap, "each node = [ ·prev | data | next· ] — data plus two pointers");
wait(1.1);
say(cap, "insert 7 after node 1: the prev/next pointers re-thread — nothing shifts");
insert(dll, 1, "7");
wait(0.9);
say(cap, "remove the head: the list simply re-points past it");
remove(dll, 0);
wait(1.2);
hashmap
Hash a key to a bucket; collisions chain on (separate chaining).
// Hash Map — separate chaining. `hashmap(id, n, (cx,cy))` draws n numbered
// buckets in a column; `put(id, key, val)` hashes the key to a bucket and chains
// the `key:val` entry on (collisions extend the chain); `get(id, key)` hashes,
// then scans that bucket's chain — each entry flashes until the key matches
// (lime) or the chain ends (bucket flashes magenta = miss).
//
// (Hash = sum of the key's bytes mod n. "cat" and "act" are anagrams, so they
// collide — same bucket, chained.)
//
// manic examples/hashmap.manic
title("Hash Map");
canvas("16:9");
text(head, (cx, 60), "separate chaining: hash the key, chain on collision");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (cx, 690), ""); color(cap, dim); size(cap, 24);
hashmap(ht, 5, (360, 360), 128, 46);
show(head, 0.5);
say(cap, "put cat, dog, ok — each hashes to its bucket");
put(ht, "cat", "7");
put(ht, "dog", "3");
put(ht, "ok", "1");
wait(0.5);
section("Collision");
say(cap, "put act — same bytes as cat, so it collides and chains on");
put(ht, "act", "9");
wait(0.6);
section("Lookup");
say(cap, "get act — scan the chain in bucket 2 until the key matches");
get(ht, "act");
say(cap, "get xyz — hashes to a bucket, scans, falls off the end: miss");
get(ht, "xyz");
wait(1.2);
Graphs
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
graph
Labelled nodes + edges, drawn on; reflowing links.
// Graph — the algo kit's `graph` builtin (Manim's Graph/DiGraph). Nodes are
// labelled circles; `a-b` is an undirected edge, `a>b` a directed arrow. Tag
// broadcast (`draw(g.edges)`, `flash(g.nodes, …)`) animates whole groups.
//
// manic examples/graph.manic
// manic examples/graph.manic --record out --fps 60
title("Graph");
canvas(1280, 720);
text(head, (640, 118), "a directed graph, traversed");
display(head); color(head, cyan); size(head, 34); hidden(head);
text(cap, (640, 664), ""); color(cap, dim); size(cap, 22);
// six vertices in a circle; directed edges (a>b)
graph(g, "a b c d e f",
"a>b b>c c>d d>e e>f f>a a>d b>e",
circular, (640, 384), 210);
// nodes fade in (hidden→show); edges trace on (untraced→draw)
hidden(g.nodes);
untraced(g.edges);
show(head, 0.5);
say(cap, "drop in the vertices");
show(g.nodes, 0.4); // broadcasts over every node
say(cap, "connect the directed edges");
draw(g.edges, 0.6); // broadcasts over every edge
section("Traversal");
say(cap, "walk a > b > c > d");
seq {
flash(g.a, magenta);
flash(g.b, magenta);
flash(g.c, magenta);
flash(g.d, magenta);
}
say(cap, "highlight the visited path");
par {
recolor(g.a, lime, 0.4);
recolor(g.b, lime, 0.4);
recolor(g.c, lime, 0.4);
recolor(g.d, lime, 0.4);
}
wait(1.2);
graph_moving
Drag a vertex and its incident edges follow.
// Moving Graph — vertices move and the edges reflow to follow them
// (Manim's MovingVertices / MovingDiGraph). Also exercises layout reveal,
// per-node moves, tag-broadcast recolour, and a highlight sweep.
//
// manic examples/graph_moving.manic
// manic examples/graph_moving.manic --record out --fps 60
//
// Node ids are g.1 … g.4 ; edge ids g.1-2 etc ; tags g.nodes / g.edges.
title("Moving Graph");
canvas(1280, 720);
text(head, (640, 118), "edges follow their vertices");
display(head); color(head, cyan); size(head, 34); hidden(head);
text(cap, (640, 664), ""); color(cap, dim); size(cap, 22);
// four vertices, five undirected edges, circular to start
graph(g, "1 2 3 4", "1-2 2-3 3-4 1-3 1-4", circular, (640, 384), 150);
hidden(g.nodes);
untraced(g.edges);
// --- reveal ---
show(head, 0.5);
say(cap, "a small graph");
show(g.nodes, 0.4);
draw(g.edges, 0.6);
wait(0.5);
// --- case 1: fling the vertices to the corners; edges reflow live ---
section("Moving vertices");
say(cap, "move each vertex — the edges stretch to follow");
par {
move(g.1, (360, 250), 1.2, overshoot);
move(g.2, (920, 250), 1.2, overshoot);
move(g.3, (920, 520), 1.2, overshoot);
move(g.4, (360, 520), 1.2, overshoot);
}
wait(0.8);
// --- case 2: orbit two vertices past each other ---
say(cap, "swap two vertices");
par {
move(g.2, (360, 520), 1.0, smooth);
move(g.4, (920, 250), 1.0, smooth);
}
wait(0.8);
// --- case 3: pull one vertex around; incident edges track it ---
say(cap, "drag one vertex around");
seq {
move(g.1, (640, 150), 0.7, smooth);
move(g.1, (1080, 384), 0.7, smooth);
move(g.1, (640, 620), 0.7, smooth);
move(g.1, (360, 250), 0.7, smooth);
}
wait(0.6);
// --- case 4: recolour the whole graph via tag broadcast, then highlight ---
section("Styling");
say(cap, "recolour every edge, then highlight a node");
recolor(g.edges, cyan, 0.5);
flash(g.1, magenta);
par {
recolor(g.1, lime, 0.4);
pulse(g.1);
}
wait(1.5);
bfs_dfs
The same graph, queue vs stack, with live frontier readouts.
// Graph Traversal — BFS vs DFS, the classic side-by-side. They're the SAME
// algorithm; only the frontier differs: BFS uses a QUEUE (explore level by
// level), DFS uses a STACK (dive deep first). `bfs(g, start)` / `dfs(g, start)`
// read the graph's adjacency, run the traversal, and animate the textbook
// states — discovered (cyan) -> current (magenta) -> done (lime) — with tree
// edges lighting up and live `queue:` / `stack:` + `visited:` readouts.
//
// manic examples/bfs_dfs.manic
title("Graph Traversal");
canvas("16:9");
text(head, (cx, 56), "BFS vs DFS: same graph, queue vs stack");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (cx, 690), ""); color(cap, dim); size(cap, 24);
graph(gr, "a b c d e f g", "a-b a-c b-d b-e c-f c-g", circular, (cx, 320), 200, 30);
show(head, 0.5);
wait(0.4);
section("BFS");
say(cap, "BFS explores level by level, using a QUEUE");
bfs(gr, a);
say(cap, "queue order: a, then b c, then d e f g");
wait(0.8);
section("DFS");
par { recolor(gr.nodes, panel, 0.4); recolor(gr.edges, dim, 0.4); }
say(gr.frontier, "stack:");
say(gr.visited, "visited:");
say(cap, "DFS dives deep down one branch, using a STACK");
dfs(gr, a);
say(cap, "the stack drove it depth-first before backtracking");
wait(1.2);
dijkstra
Weighted edges, settling distances, a shortest-path tree.
// Dijkstra — single-source shortest paths on a WEIGHTED graph. Give edges a
// weight with `a-b:w` (drawn as a midpoint label). `dijkstra(g, start)` reads the
// weights, then runs the classic loop: repeatedly settle the nearest unsettled
// node (magenta -> lime), relaxing its edges and lowering neighbours' distances.
// Each node shows its best-known distance (inf -> the final shortest distance),
// and the shortest-path-tree edges stay lit at the end.
//
// manic examples/dijkstra.manic
title("Dijkstra");
canvas("16:9");
text(head, (cx, 58), "shortest paths: settle the nearest node, relax its edges");
display(head); color(head, cyan); size(head, 25); hidden(head);
text(cap, (cx, 690), ""); color(cap, dim); size(cap, 24);
graph(g, "a b c d e f",
"a-b:2 a-c:5 b-c:1 b-d:4 c-e:3 d-e:1 d-f:2 e-f:6",
circular, (cx, 350), 210, 30);
show(head, 0.5);
say(cap, "each node shows its best distance: start 0, the rest inf");
wait(0.7);
section("Relax");
say(cap, "settle the nearest node, then relax every edge leaving it");
dijkstra(g, a);
say(cap, "settled distances are final; the lime edges form the shortest-path tree");
wait(1.4);
cubic-graphs
A hand-composed graph-theory plate: five 3-connected simple cubic graphs
(including the 6-spoke wheel from a for loop) drawn on in sequence, with
LaTeX node labels ($u_i$, $v_i$) and typeset names $G_1..G_5$ — a paper-style
figure straight from a theorem statement.
title("Five Exceptional Cubic Graphs");
canvas("16:9");
template("paper");
text(titleA, (cx, 34), "Five 3-connected simple cubic graphs");
text(titleB, (cx, 68), "cycle-cover exceptions: at most $\lceil n/6\rceil$ cycles is not guaranteed");
display(titleA);
bold(titleA);
size(titleA, 30);
size(titleB, 22);
color(titleB, dim);
hidden(titleA);
hidden(titleB);
line(frameTop, (42, 104), (w - 42, 104));
line(frameBottom, (42, h - 26), (w - 42, h - 26));
line(frameLeft, (42, 104), (42, h - 26));
line(frameRight, (w - 42, 104), (w - 42, h - 26));
tag(frameTop, frame);
tag(frameBottom, frame);
tag(frameLeft, frame);
tag(frameRight, frame);
stroke(frame, 3);
color(frame, dim);
opacity(frame, 0.45);
untraced(frame);
dot(g1u1, (180, 310), 5);
dot(g1u2, (320, 310), 5);
dot(g1u3, (370, 210), 5);
dot(g1u4, (250, 125), 5);
dot(g1u5, (130, 210), 5);
dot(g1v1, (220, 265), 5);
dot(g1v2, (280, 265), 5);
dot(g1v3, (305, 225), 5);
dot(g1v4, (250, 185), 5);
dot(g1v5, (195, 225), 5);
tag(g1u1, g1Nodes);
tag(g1u2, g1Nodes);
tag(g1u3, g1Nodes);
tag(g1u4, g1Nodes);
tag(g1u5, g1Nodes);
tag(g1v1, g1Nodes);
tag(g1v2, g1Nodes);
tag(g1v3, g1Nodes);
tag(g1v4, g1Nodes);
tag(g1v5, g1Nodes);
line(g1e1, (180, 310), (320, 310));
line(g1e2, (320, 310), (370, 210));
line(g1e3, (370, 210), (250, 125));
line(g1e4, (250, 125), (130, 210));
line(g1e5, (130, 210), (180, 310));
line(g1e6, (220, 265), (280, 265));
line(g1e7, (280, 265), (305, 225));
line(g1e8, (305, 225), (250, 185));
line(g1e9, (250, 185), (195, 225));
line(g1e10, (195, 225), (220, 265));
line(g1e11, (180, 310), (220, 265));
line(g1e12, (320, 310), (280, 265));
line(g1e13, (370, 210), (305, 225));
line(g1e14, (250, 125), (250, 185));
line(g1e15, (130, 210), (195, 225));
tag(g1e1, g1Edges);
tag(g1e2, g1Edges);
tag(g1e3, g1Edges);
tag(g1e4, g1Edges);
tag(g1e5, g1Edges);
tag(g1e6, g1Edges);
tag(g1e7, g1Edges);
tag(g1e8, g1Edges);
tag(g1e9, g1Edges);
tag(g1e10, g1Edges);
tag(g1e11, g1Edges);
tag(g1e12, g1Edges);
tag(g1e13, g1Edges);
tag(g1e14, g1Edges);
tag(g1e15, g1Edges);
text(g1lu1, (165, 336), "$u_1$");
text(g1lu2, (333, 336), "$u_2$");
text(g1lu3, (393, 214), "$u_3$");
text(g1lu4, (270, 124), "$u_4$");
text(g1lu5, (105, 215), "$u_5$");
text(g1lv1, (198, 262), "$v_1$");
text(g1lv2, (301, 262), "$v_2$");
text(g1lv3, (326, 228), "$v_3$");
text(g1lv4, (272, 181), "$v_4$");
text(g1lv5, (174, 227), "$v_5$");
tag(g1lu1, nodeLabels);
tag(g1lu2, nodeLabels);
tag(g1lu3, nodeLabels);
tag(g1lu4, nodeLabels);
tag(g1lu5, nodeLabels);
tag(g1lv1, nodeLabels);
tag(g1lv2, nodeLabels);
tag(g1lv3, nodeLabels);
tag(g1lv4, nodeLabels);
tag(g1lv5, nodeLabels);
equation(g1Name, (250, 366), "G_1", 30);
tag(g1Name, graphNames);
dot(g2u1, (1030, 120), 5);
dot(g2u2, (1160, 210), 5);
dot(g2u3, (1120, 320), 5);
dot(g2u4, (940, 320), 5);
dot(g2u5, (900, 210), 5);
dot(g2u6, (1030, 185), 5);
dot(g2u7, (1095, 225), 5);
dot(g2u8, (1075, 275), 5);
dot(g2u9, (985, 275), 5);
dot(g2u10, (965, 225), 5);
tag(g2u1, g2Nodes);
tag(g2u2, g2Nodes);
tag(g2u3, g2Nodes);
tag(g2u4, g2Nodes);
tag(g2u5, g2Nodes);
tag(g2u6, g2Nodes);
tag(g2u7, g2Nodes);
tag(g2u8, g2Nodes);
tag(g2u9, g2Nodes);
tag(g2u10, g2Nodes);
line(g2e1, (1030, 120), (1160, 210));
line(g2e2, (1160, 210), (1120, 320));
line(g2e3, (1120, 320), (940, 320));
line(g2e4, (940, 320), (900, 210));
line(g2e5, (900, 210), (1030, 120));
line(g2e6, (1030, 120), (1030, 185));
line(g2e7, (1160, 210), (1095, 225));
line(g2e8, (1120, 320), (1075, 275));
line(g2e9, (940, 320), (985, 275));
line(g2e10, (900, 210), (965, 225));
line(g2e11, (1030, 185), (1075, 275));
line(g2e12, (1075, 275), (965, 225));
line(g2e13, (965, 225), (1095, 225));
line(g2e14, (1095, 225), (985, 275));
line(g2e15, (985, 275), (1030, 185));
tag(g2e1, g2Edges);
tag(g2e2, g2Edges);
tag(g2e3, g2Edges);
tag(g2e4, g2Edges);
tag(g2e5, g2Edges);
tag(g2e6, g2Edges);
tag(g2e7, g2Edges);
tag(g2e8, g2Edges);
tag(g2e9, g2Edges);
tag(g2e10, g2Edges);
tag(g2e11, g2Edges);
tag(g2e12, g2Edges);
tag(g2e13, g2Edges);
tag(g2e14, g2Edges);
tag(g2e15, g2Edges);
text(g2lu1, (1010, 114), "$u_1$");
text(g2lu2, (1184, 214), "$u_2$");
text(g2lu3, (1138, 344), "$u_3$");
text(g2lu4, (918, 344), "$u_4$");
text(g2lu5, (874, 214), "$u_5$");
text(g2lu6, (1051, 181), "$u_6$");
text(g2lu7, (1122, 224), "$u_7$");
text(g2lu8, (1095, 280), "$u_8$");
text(g2lu9, (959, 280), "$u_9$");
text(g2lu10, (934, 224), "$u_{10}$");
tag(g2lu1, nodeLabels);
tag(g2lu2, nodeLabels);
tag(g2lu3, nodeLabels);
tag(g2lu4, nodeLabels);
tag(g2lu5, nodeLabels);
tag(g2lu6, nodeLabels);
tag(g2lu7, nodeLabels);
tag(g2lu8, nodeLabels);
tag(g2lu9, nodeLabels);
tag(g2lu10, nodeLabels);
equation(g2Name, (1030, 366), "G_2", 30);
tag(g2Name, graphNames);
let g3cx = cx;
let g3cy = 398;
let g3r = 105;
dot(g3c, (g3cx, g3cy), 5);
tag(g3c, g3Nodes);
for i in 0..12 {
line(g3rim{i}, (g3cx + g3r * cos(-pi/2 + tau * i/12), g3cy + g3r * sin(-pi/2 + tau * i/12)), (g3cx + g3r * cos(-pi/2 + tau * (i + 1)/12), g3cy + g3r * sin(-pi/2 + tau * (i + 1)/12)));
line(g3spoke{i}, (g3cx, g3cy), (g3cx + g3r * cos(-pi/2 + tau * i/12), g3cy + g3r * sin(-pi/2 + tau * i/12)));
dot(g3u{i}, (g3cx + g3r * cos(-pi/2 + tau * i/12), g3cy + g3r * sin(-pi/2 + tau * i/12)), 5);
tag(g3rim{i}, g3Edges);
tag(g3spoke{i}, g3Edges);
tag(g3u{i}, g3Nodes);
}
text(g3l1, (640, 264), "$u_1$");
text(g3l2, (710, 284), "$u_2$");
text(g3l3, (762, 334), "$u_3$");
text(g3l4, (778, 402), "$u_4$");
text(g3l5, (762, 470), "$u_5$");
text(g3l6, (710, 520), "$u_6$");
text(g3l7, (640, 540), "$u_7$");
text(g3l8, (570, 520), "$u_8$");
text(g3l9, (518, 470), "$u_9$");
text(g3l10, (500, 402), "$u_{10}$");
text(g3l11, (518, 334), "$u_{11}$");
text(g3l12, (570, 284), "$u_{12}$");
tag(g3l1, nodeLabels);
tag(g3l2, nodeLabels);
tag(g3l3, nodeLabels);
tag(g3l4, nodeLabels);
tag(g3l5, nodeLabels);
tag(g3l6, nodeLabels);
tag(g3l7, nodeLabels);
tag(g3l8, nodeLabels);
tag(g3l9, nodeLabels);
tag(g3l10, nodeLabels);
tag(g3l11, nodeLabels);
tag(g3l12, nodeLabels);
equation(g3Name, (640, 612), "G_3", 30);
tag(g3Name, graphNames);
dot(g4u1, (210, 625), 5);
dot(g4u2, (350, 625), 5);
dot(g4u3, (410, 510), 5);
dot(g4u4, (290, 435), 5);
dot(g4v1, (255, 560), 5);
dot(g4v2, (320, 560), 5);
dot(g4v3, (340, 510), 5);
dot(g4v4, (290, 485), 5);
dot(g4v5, (220, 520), 5);
dot(g4w1, (170, 500), 5);
dot(g4w2, (145, 450), 5);
dot(g4w3, (95, 565), 5);
tag(g4u1, g4Nodes);
tag(g4u2, g4Nodes);
tag(g4u3, g4Nodes);
tag(g4u4, g4Nodes);
tag(g4v1, g4Nodes);
tag(g4v2, g4Nodes);
tag(g4v3, g4Nodes);
tag(g4v4, g4Nodes);
tag(g4v5, g4Nodes);
tag(g4w1, g4Nodes);
tag(g4w2, g4Nodes);
tag(g4w3, g4Nodes);
line(g4e1, (95, 565), (210, 625));
line(g4e2, (210, 625), (350, 625));
line(g4e3, (350, 625), (410, 510));
line(g4e4, (410, 510), (290, 435));
line(g4e5, (290, 435), (145, 450));
line(g4e6, (145, 450), (95, 565));
line(g4e7, (145, 450), (170, 500));
line(g4e8, (95, 565), (170, 500));
line(g4e9, (170, 500), (220, 520));
line(g4e10, (255, 560), (320, 560));
line(g4e11, (320, 560), (340, 510));
line(g4e12, (340, 510), (290, 485));
line(g4e13, (290, 485), (220, 520));
line(g4e14, (220, 520), (255, 560));
line(g4e15, (210, 625), (255, 560));
line(g4e16, (350, 625), (320, 560));
line(g4e17, (410, 510), (340, 510));
line(g4e18, (290, 435), (290, 485));
tag(g4e1, g4Edges);
tag(g4e2, g4Edges);
tag(g4e3, g4Edges);
tag(g4e4, g4Edges);
tag(g4e5, g4Edges);
tag(g4e6, g4Edges);
tag(g4e7, g4Edges);
tag(g4e8, g4Edges);
tag(g4e9, g4Edges);
tag(g4e10, g4Edges);
tag(g4e11, g4Edges);
tag(g4e12, g4Edges);
tag(g4e13, g4Edges);
tag(g4e14, g4Edges);
tag(g4e15, g4Edges);
tag(g4e16, g4Edges);
tag(g4e17, g4Edges);
tag(g4e18, g4Edges);
text(g4lu1, (190, 650), "$u_1$");
text(g4lu2, (365, 650), "$u_2$");
text(g4lu3, (435, 514), "$u_3$");
text(g4lu4, (294, 414), "$u_4$");
text(g4lv1, (255, 540), "$v_1$");
text(g4lv2, (335, 544), "$v_2$");
text(g4lv3, (360, 505), "$v_3$");
text(g4lv4, (312, 485), "$v_4$");
text(g4lv5, (224, 500), "$v_5$");
text(g4lw1, (176, 480), "$w_1$");
text(g4lw2, (123, 444), "$w_2$");
text(g4lw3, (72, 568), "$w_3$");
tag(g4lu1, nodeLabels);
tag(g4lu2, nodeLabels);
tag(g4lu3, nodeLabels);
tag(g4lu4, nodeLabels);
tag(g4lv1, nodeLabels);
tag(g4lv2, nodeLabels);
tag(g4lv3, nodeLabels);
tag(g4lv4, nodeLabels);
tag(g4lv5, nodeLabels);
tag(g4lw1, nodeLabels);
tag(g4lw2, nodeLabels);
tag(g4lw3, nodeLabels);
equation(g4Name, (275, 690), "G_4", 30);
tag(g4Name, graphNames);
dot(g5a, (1020, 440), 5);
dot(g5b, (1160, 470), 5);
dot(g5c, (1135, 535), 5);
dot(g5d, (1195, 575), 5);
dot(g5e, (1080, 635), 5);
dot(g5f, (915, 625), 5);
dot(g5g, (880, 525), 5);
dot(g5h, (1020, 505), 5);
dot(g5i, (960, 535), 5);
dot(g5j, (1080, 535), 5);
dot(g5k, (980, 590), 5);
dot(g5l, (1060, 590), 5);
tag(g5a, g5Nodes);
tag(g5b, g5Nodes);
tag(g5c, g5Nodes);
tag(g5d, g5Nodes);
tag(g5e, g5Nodes);
tag(g5f, g5Nodes);
tag(g5g, g5Nodes);
tag(g5h, g5Nodes);
tag(g5i, g5Nodes);
tag(g5j, g5Nodes);
tag(g5k, g5Nodes);
tag(g5l, g5Nodes);
line(g5e1, (1020, 440), (1160, 470));
line(g5e2, (1160, 470), (1195, 575));
line(g5e3, (1195, 575), (1080, 635));
line(g5e4, (1080, 635), (915, 625));
line(g5e5, (915, 625), (880, 525));
line(g5e6, (880, 525), (1020, 440));
line(g5e7, (1160, 470), (1135, 535));
line(g5e8, (1135, 535), (1195, 575));
line(g5e9, (1135, 535), (1080, 535));
line(g5e10, (1020, 440), (1020, 505));
line(g5e11, (880, 525), (960, 535));
line(g5e12, (915, 625), (980, 590));
line(g5e13, (1080, 635), (1060, 590));
line(g5e14, (1020, 505), (980, 590));
line(g5e15, (1020, 505), (1060, 590));
line(g5e16, (960, 535), (1080, 535));
line(g5e17, (960, 535), (1060, 590));
line(g5e18, (1080, 535), (980, 590));
tag(g5e1, g5Edges);
tag(g5e2, g5Edges);
tag(g5e3, g5Edges);
tag(g5e4, g5Edges);
tag(g5e5, g5Edges);
tag(g5e6, g5Edges);
tag(g5e7, g5Edges);
tag(g5e8, g5Edges);
tag(g5e9, g5Edges);
tag(g5e10, g5Edges);
tag(g5e11, g5Edges);
tag(g5e12, g5Edges);
tag(g5e13, g5Edges);
tag(g5e14, g5Edges);
tag(g5e15, g5Edges);
tag(g5e16, g5Edges);
tag(g5e17, g5Edges);
tag(g5e18, g5Edges);
equation(g5Name, (1020, 690), "G_5", 30);
tag(g5Name, graphNames);
tag(g1Edges, allEdges);
tag(g2Edges, allEdges);
tag(g3Edges, allEdges);
tag(g4Edges, allEdges);
tag(g5Edges, allEdges);
tag(g1Nodes, allNodes);
tag(g2Nodes, allNodes);
tag(g3Nodes, allNodes);
tag(g4Nodes, allNodes);
tag(g5Nodes, allNodes);
stroke(allEdges, 3);
color(allEdges, fg);
untraced(allEdges);
color(allNodes, fg);
hidden(allNodes);
size(nodeLabels, 19);
color(nodeLabels, dim);
hidden(nodeLabels);
hidden(graphNames);
show(titleA, 0.5);
show(titleB, 0.5);
draw(frame, 0.7);
par {
seq {
draw(g1Edges, 1.0);
show(g1Nodes, 0.25);
show(g1Name, 0.25);
}
seq {
wait(0.25);
draw(g2Edges, 1.0);
show(g2Nodes, 0.25);
show(g2Name, 0.25);
}
seq {
wait(0.5);
draw(g3Edges, 1.2);
show(g3Nodes, 0.25);
show(g3Name, 0.25);
}
seq {
wait(0.75);
draw(g4Edges, 1.1);
show(g4Nodes, 0.25);
show(g4Name, 0.25);
}
seq {
wait(1.0);
draw(g5Edges, 1.0);
show(g5Nodes, 0.25);
show(g5Name, 0.25);
}
}
show(nodeLabels, 0.7);
recolor(graphNames, gold, 0.5);
pulse(graphNames, 0.8);
wait(1.0);
Grids — pathfinding & automata
A first-class 2-D cell grid — the one primitive under tilemaps, spatial pathfinding (space, not graph’s topology), cellular automata and Wave Function Collapse. Cells address like matrix/table ({id}.r{i}c{j}); seed a maze from a compact # . @ * ASCII string; the pathfinders reuse the algo kit’s exact colour grammar (discovered cyan → current magenta → done lime), and generation pre-simulates at build time then replays with run.
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
grid-astar
A* over a seeded ASCII maze: open cells flood by f-score (frontier cyan → current magenta), a live frontier/visited readout counts up, and the shortest route traces out in gold as {id}.path. neighbors picks 4- or 8-connectivity.
title("A* Pathfinding — Search Over Space");
canvas("16:9");
template("neon");
watermark(mark, (w*0.15, h*0.05), "Made With Manic");
// A cell grid seeded from a compact ASCII maze: `#` wall, `.` open, `@` start,
// `*` goal. A* explores the open cells (frontier cyan → current magenta), then
// the shortest route is traced in gold — the spatial sibling of the algo kit's
// graph search, sharing its exact colour grammar.
grid(g, "@ . . . . . . . ; # # # # # # . # ; . . . . . . . . ; . # # # # # # # ; . . . . . . . . ; # # # # # # # *", (cx, cy+20), 8, 6, 78);
neighbors(g, "4");
gridastar(g, (0,0), (7,5), manhattan);
draw(g.path, 1.4);
grid-life
Conway’s Game of Life: a glider seeded with setcell, then evolve pre-simulates six generations at build time (alive = a filled cell, Conway’s B3/S23) and run replays them — the glider walks diagonally across the grid.
title("Conway's Life — A Glider");
canvas("16:9");
template("neon");
watermark(mark, (w*0.15, h*0.05), "Made With Manic");
// A cellular automaton on the same cell grid. `step` pre-simulates one Conway
// generation at build time (alive = a filled `wall` cell, 8-neighbourhood);
// `run` then replays the stored generations. This glider walks diagonally.
grid(life, (cx, cy+10), 14, 12, 42);
setcell(life, 1, 2, wall);
setcell(life, 2, 3, wall);
setcell(life, 3, 1, wall);
setcell(life, 3, 2, wall);
setcell(life, 3, 3, wall);
evolve(life, "life"); evolve(life, "life"); evolve(life, "life");
evolve(life, "life"); evolve(life, "life"); evolve(life, "life");
run(life, 6, 5.0);
grid-life-soup
Game of Life at scale: a 40x40 grid seeded with a pseudo-random soup (a shader-style hash, since manic has no rand()), 30 generations evolved then run — the chaos settles into gliders, blinkers and still lifes.
// grid-life-soup — Conway's Game of Life from a random soup, on the grid kit.
//
// A 40x40 cellular automaton seeded with a pseudo-random field (manic has no
// rand(), so a shader-style hash stands in), then evolved: each
// `evolve(life,"life")` pre-simulates one Conway generation at build time
// (alive = a filled `wall` cell, B3/S23, 8-neighbourhood), and `run` replays
// the 30 stored generations — the soup settles into gliders, blinkers and still lifes.
//
// manic examples/grid-life-soup.manic
title("Conway's Life — a random soup");
canvas(1080, 1080);
template("paper");
let cols = 40;
let rows = 40;
let cell = 25;
grid(life, (540, 540), cols, rows, cell);
// seed ~45% of cells at random (the hash keeps it reproducible)
for r in 0..rows {
for c in 0..cols {
if sin((r * 12.9898 + c * 78.233) * 43758.5453) > 0.1 {
setcell(life, r, c, wall);
}
}
}
// pre-simulate 30 generations, then replay them over 8s
for g in 0..30 { evolve(life, "life"); }
run(life, 30, 8);
grid-wfc
Wave Function Collapse: collapse pre-simulates a seeded, neighbour-constrained settling row by row, then run replays the grid resolving from empty into a finished maze — deterministic, so the same seed always settles the same way.
title("Wave Function Collapse — A Maze, Settling");
canvas("16:9");
template("neon");
watermark(mark, (w*0.15, h*0.05), "Made With Manic");
// `collapse` pre-simulates a seeded Wave-Function-Collapse-style settling at
// build time — each cell is decided under its neighbours' constraints, one row
// at a time — then `run` replays the grid resolving from empty to a finished
// maze. Seeded, so it is fully deterministic (same seed ⇒ same maze).
grid(mz, (cx, cy+10), 20, 14, 30);
collapse(mz, "maze", 7);
run(mz, 14, 5.0);
grid-life-zoo
Conway’s Life’s whole taxonomy — a still life (Block), an oscillator (Blinker) and a
spaceship (Glider) — three grids seeded with setcell, evolved eight generations and
run in parallel: the Block holds, the Blinker flips, the Glider walks. The famous
pattern zoo, entirely in the grid kit.
// Conway's Life — the Zoo. img_4.png's taxonomy (still lifes / oscillators /
// spaceships) is nothing more than Conway's Life on a grid — which the grid kit
// already does: seed cells, `evolve` a few generations at build time, then `run`
// to replay them. Three grids run in parallel: a Block that never moves, a Blinker
// that oscillates, and a Glider that walks across its grid. No new kit needed.
title("Conway's Life — the Zoo");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
text(kicker, (cx, h*0.10), "STILL LIFE · OSCILLATOR · SPACESHIP — all just Conway's Life");
size(kicker, 22*u); color(kicker, dim); bold(kicker); wrap(kicker, w*0.9);
// --- Still life: Block (never changes) ---
grid(blk, (w*0.22, cy), 6, 6, 40);
setcell(blk, 2, 2, wall); setcell(blk, 2, 3, wall);
setcell(blk, 3, 2, wall); setcell(blk, 3, 3, wall);
text(blkL, (w*0.22, cy+180*u), "Block — still life"); size(blkL, 20*u); color(blkL, cyan);
// --- Oscillator: Blinker (period 2) ---
grid(bln, (w*0.5, cy), 6, 6, 40);
setcell(bln, 2, 1, wall); setcell(bln, 2, 2, wall); setcell(bln, 2, 3, wall);
text(blnL, (w*0.5, cy+180*u), "Blinker — oscillator (p2)"); size(blnL, 20*u); color(blnL, cyan);
// --- Spaceship: Glider (translates) ---
grid(gld, (w*0.78, cy), 8, 8, 34);
setcell(gld, 1, 2, wall);
setcell(gld, 2, 3, wall);
setcell(gld, 3, 1, wall); setcell(gld, 3, 2, wall); setcell(gld, 3, 3, wall);
text(gldL, (w*0.78, cy+180*u), "Glider — spaceship"); size(gldL, 20*u); color(gldL, cyan);
// Pre-simulate 8 generations of each (build time), same count so they replay in sync.
evolve(blk, "life"); evolve(blk, "life"); evolve(blk, "life"); evolve(blk, "life");
evolve(blk, "life"); evolve(blk, "life"); evolve(blk, "life"); evolve(blk, "life");
evolve(bln, "life"); evolve(bln, "life"); evolve(bln, "life"); evolve(bln, "life");
evolve(bln, "life"); evolve(bln, "life"); evolve(bln, "life"); evolve(bln, "life");
evolve(gld, "life"); evolve(gld, "life"); evolve(gld, "life"); evolve(gld, "life");
evolve(gld, "life"); evolve(gld, "life"); evolve(gld, "life"); evolve(gld, "life");
// Replay all three at once — the whole zoo, self-animating.
par {
run(blk, 8, 5.0);
run(bln, 8, 5.0);
run(gld, 8, 5.0);
}
wait(0.6);
Calculus & functions
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
calculus-demo
The flagship: two big ideas on one curve. A tangent slides along a bell curve with a live slope readout (flat at the peak), then the area sweeps open while the integral climbs to its true value — on properly numbered, scaled axes.
title("Calculus, visualized — the derivative and the integral");
canvas("16:9");
// screen anchor for the plot: math (0,0) maps here. Placing it at cx-450 with
// sx=150 centres the domain 0..6 under the canvas centre.
let ox = cx - 450;
let oy = cy + 120;
let sx = 150; // screen px per unit of x
let sy = 170; // screen px per unit of y
// ---------------- header ----------------
text(hdr, (cx, 54), "Two big ideas, one curve");
size(hdr, 32); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
text(flabel, (cx, 94), "f(x) = a smooth bell-shaped curve");
size(flabel, 18); color(flabel, dim); display(flabel); hidden(flabel);
// ---------------- the curve ----------------
axes(ax, (ox, oy), 930, 205, 1);
color(ax, dim); untraced(ax);
// axis labels, at the arrow tips
text(xlab, (ox + 958, oy + 6), "x");
size(xlab, 24); color(xlab, dim); display(xlab); hidden(xlab);
text(ylab, (ox, oy - 232), "f(x)");
size(ylab, 24); color(ylab, dim); display(ylab); hidden(ylab);
// numbered scale — each label sits at the plot's real coordinate:
// x = k -> (ox + k*sx, oy) y = v -> (ox, oy - v*sy)
line(tx1, (ox + 1*sx, oy - 7), (ox + 1*sx, oy + 7)); color(tx1, dim); tag(tx1, scale); hidden(tx1);
line(tx2, (ox + 2*sx, oy - 7), (ox + 2*sx, oy + 7)); color(tx2, dim); tag(tx2, scale); hidden(tx2);
line(tx3, (ox + 3*sx, oy - 7), (ox + 3*sx, oy + 7)); color(tx3, dim); tag(tx3, scale); hidden(tx3);
line(tx4, (ox + 4*sx, oy - 7), (ox + 4*sx, oy + 7)); color(tx4, dim); tag(tx4, scale); hidden(tx4);
line(tx5, (ox + 5*sx, oy - 7), (ox + 5*sx, oy + 7)); color(tx5, dim); tag(tx5, scale); hidden(tx5);
line(tx6, (ox + 6*sx, oy - 7), (ox + 6*sx, oy + 7)); color(tx6, dim); tag(tx6, scale); hidden(tx6);
text(nx1, (ox + 1*sx, oy + 28), "1"); size(nx1, 16); color(nx1, dim); tag(nx1, scale); hidden(nx1);
text(nx2, (ox + 2*sx, oy + 28), "2"); size(nx2, 16); color(nx2, dim); tag(nx2, scale); hidden(nx2);
text(nx3, (ox + 3*sx, oy + 28), "3"); size(nx3, 16); color(nx3, dim); tag(nx3, scale); hidden(nx3);
text(nx4, (ox + 4*sx, oy + 28), "4"); size(nx4, 16); color(nx4, dim); tag(nx4, scale); hidden(nx4);
text(nx5, (ox + 5*sx, oy + 28), "5"); size(nx5, 16); color(nx5, dim); tag(nx5, scale); hidden(nx5);
text(nx6, (ox + 6*sx, oy + 28), "6"); size(nx6, 16); color(nx6, dim); tag(nx6, scale); hidden(nx6);
line(tyh, (ox - 7, oy - 0.5*sy), (ox + 7, oy - 0.5*sy)); color(tyh, dim); tag(tyh, scale); hidden(tyh);
line(ty1, (ox - 7, oy - 1*sy), (ox + 7, oy - 1*sy)); color(ty1, dim); tag(ty1, scale); hidden(ty1);
text(nyh, (ox - 36, oy - 0.5*sy), "0.5"); size(nyh, 16); color(nyh, dim); tag(nyh, scale); hidden(nyh);
text(ny1, (ox - 24, oy - 1*sy), "1"); size(ny1, 16); color(ny1, dim); tag(ny1, scale); hidden(ny1);
plot(f, (ox, oy), sx, sy, "exp(-0.5*(x-3)*(x-3))", (0, 6));
color(f, cyan); stroke(f, 4); untraced(f);
// ---------------- captions (swap per act) ----------------
text(capd, (cx, h - 46), "THE DERIVATIVE — the slope at every point");
size(capd, 22); color(capd, gold); bold(capd); display(capd); hidden(capd);
text(capi, (cx, h - 46), "THE INTEGRAL — the area underneath");
size(capi, 22); color(capi, lime); bold(capi); display(capi); hidden(capi);
text(endcap, (cx, h - 46), "two views of one curve");
size(endcap, 22); color(endcap, cyan); bold(endcap); display(endcap); hidden(endcap);
// ---------------- derivative pieces ----------------
tangent(tan, f, 0.4, 220);
color(tan, gold); hidden(tan);
slope(sl, f, 0.4);
color(sl, gold); hidden(sl);
// ---------------- integral pieces ----------------
area(reg, f, 0, 0);
color(reg, lime);
text(ivlabel, (cx + 330, cy - 150), "area so far");
size(ivlabel, 16); color(ivlabel, dim); display(ivlabel); hidden(ivlabel);
integral(iv, f, 0, 0, (cx + 330, cy - 110));
size(iv, 30); color(iv, lime); hidden(iv);
// ================= timeline =================
show(hdr, 0.6);
show(flabel, 0.4);
draw(ax, 0.8);
par {
show(xlab, 0.4);
show(ylab, 0.4);
show(scale, 0.4);
}
draw(f, 1.6);
wait(0.4);
// --- Act 1: the derivative ---
show(capd, 0.5);
show(tan, 0.5);
show(sl, 0.4);
wait(0.3);
par { // climb to the peak — slope goes to zero
to(tan, x, 3, 2.2);
to(sl, x, 3, 2.2);
}
pulse(sl, 0.5);
wait(0.3);
par { // over the top — slope turns negative
to(tan, x, 5.6, 2.6);
to(sl, x, 5.6, 2.6);
}
wait(0.4);
// --- Act 2: the integral ---
par {
fade(capd, 0.4);
fade(tan, 0.4);
fade(sl, 0.4);
}
show(capi, 0.5);
show(ivlabel, 0.3);
show(iv, 0.3);
par { // sweep the area open, number climbs
to(reg, x, 6, 4);
to(iv, x, 6, 4);
}
wait(0.4);
flash(iv, lime);
pulse(iv, 0.5);
wait(0.4);
// --- close ---
fade(capi, 0.4);
show(endcap, 0.6);
calculus-one
Calculus I end to end in one continuous scene: limits (a curve with a hole), the derivative (secants collapsing onto the tangent), curve shape, and the integral + Fundamental Theorem — chapter wipes between.
title("Calculus I, End to End");
canvas("16:9");
template("black");
// ================= HUD (sticky: survives every camera move) =================
text(head, (cx, 60), "Calculus I, End to End"); display(head); cursor(head); sticky(head);
text(h1, (cx, 148), "1. Limits - getting close without arriving"); size(h1, 28); color(h1, cyan); hidden(h1); sticky(h1);
text(h2, (cx, 148), "2. The derivative - a slope caught in the act"); size(h2, 28); color(h2, magenta); hidden(h2); sticky(h2);
text(h3, (cx, 148), "3. What f' knows about the shape of f"); size(h3, 28); color(h3, lime); hidden(h3); sticky(h3);
text(h4, (cx, 148), "4. The integral - area, one strip at a time"); size(h4, 28); color(h4, gold); hidden(h4); sticky(h4);
text(h5, (cx, 148), "5. The Fundamental Theorem - the bridge"); size(h5, 28); hidden(h5); sticky(h5);
text(cap, (cx, h - 42), ""); size(cap, 26); sticky(cap);
caption(menu, "limits - slopes - shapes - areas - the bridge", (cx, cy), 34, gold); hidden(menu);
// chapter wipes, tinted to the incoming chapter
line(w2, (-40, 140), (-40, h - 90)); stroke(w2, 4); color(w2, magenta); glow(w2, 14);
line(w3, (-40, 140), (-40, h - 90)); stroke(w3, 4); color(w3, lime); glow(w3, 14);
line(w4, (-40, 140), (-40, h - 90)); stroke(w4, 4); color(w4, gold); glow(w4, 14);
line(w5, (-40, 140), (-40, h - 90)); stroke(w5, 4); glow(w5, 14);
// ================= ch 1 cast: LIMITS =================
// example A: f(x) = (x^3 - x)/(x - 1) = x(x+1) away from the hole at x = 1
// example B: a limit at infinity - the sigmoid hugging its asymptote
let gx1 = cx + 34;
let gy = 560;
arrow(x1a, (cx - 380, gy), (cx + 390, gy)); untraced(x1a); stroke(x1a, 3); tag(x1a, c1);
arrow(y1a, (gx1, gy), (gx1, 235)); untraced(y1a); stroke(y1a, 3); tag(y1a, c1);
plot(p1, (gx1, gy), 170, 60, "x^2+x", (-2.2, 1.8));
untraced(p1); stroke(p1, 4); gradient(p1, blue, cyan, gold, 270); tag(p1, c1);
dot(rider, (gx1, gy), 7); color(rider, gold); glow(rider, 8); hidden(rider); tag(rider, c1);
limit(lm, p1, 1, gold); hidden(lm); tag(lm, c1);
plot(p1b, (gx1, gy), 170, 60, "4/(1+exp(-2.2*x))", (-2.2, 1.8));
untraced(p1b); stroke(p1b, 4); color(p1b, teal); tag(p1b, c1);
limit(lmb, p1b, inf, cyan); hidden(lmb); tag(lmb, c1);
equation(eq1, (cx, 230), `\lim_{x\to 1}\frac{x^3-x}{x-1}`, 40); hidden(eq1); sticky(eq1);
// ================= ch 2 cast: THE DERIVATIVE =================
// example A: f(x) = x^2/2, secants through (1, 0.5) collapse onto the tangent
// example B: sine's recorded slope is cosine
let gx2 = cx - 170;
arrow(x2a, (cx - 312, gy), (cx + 322, gy)); untraced(x2a); stroke(x2a, 3); tag(x2a, c2);
arrow(y2a, (gx2, gy), (gx2, 240)); untraced(y2a); stroke(y2a, 3); tag(y2a, c2);
plot(p2, (gx2, gy), 170, 80, "0.5*x^2", (-0.6, 2.6));
untraced(p2); stroke(p2, 4); gradient(p2, blue, magenta, 270); tag(p2, c2);
dot(anch, (gx2 + 170, gy - 40), 7); color(anch, gold); glow(anch, 8); hidden(anch); tag(anch, c2);
for k in 0..3 {
let hh = 1.5 - 0.6*k;
let mm = 1 + hh/2;
let ya = 0.5 - 0.9*mm;
let yb = 0.5 + 1.4*mm;
line(sec{k}, (gx2 + 17, gy - ya*80), (gx2 + 408, gy - yb*80));
stroke(sec{k}, 3); color(sec{k}, teal); opacity(sec{k}, 0.45 + 0.2*k); hidden(sec{k}); tag(sec{k}, c2);
dot(sd{k}, (gx2 + (1 + hh)*170, gy - ((1 + hh)^2/2)*80), 6);
color(sd{k}, teal); hidden(sd{k}); tag(sd{k}, c2);
}
tangent(tg2, p2, 1, 280); hidden(tg2); tag(tg2, c2);
slope(sl2, p2, 1); hidden(sl2); tag(sl2, c2);
deriv(dv2, p2, magenta); untraced(dv2); tag(dv2, c2);
line(bl2, (gx2 - 90, 445), (gx2 + 445, 445)); dashed(bl2); stroke(bl2, 2); color(bl2, dim); hidden(bl2); tag(bl2, c2);
plot(p2s, (gx2, 445), 170, 80, "1.1*sin(1.8*x)", (-0.5, 2.6));
untraced(p2s); stroke(p2s, 4); color(p2s, cyan); tag(p2s, c2);
deriv(dvs, p2s, magenta); untraced(dvs); dashed(dvs, 12, 9); tag(dvs, c2);
equation(eq2, (cx, 230), `f'(1) \;=\; \lim_{h\to 0}\frac{f(1+h)-f(1)}{h}`, 38); hidden(eq2); sticky(eq2);
// ================= ch 3 cast: SHAPE =================
// f(x) = x^3/3 - x : turning points at x = -1, 1; concavity flips at 0
// (one horizontal axis only - no vertical line slicing through the curve)
let gy3 = 420;
arrow(x3a, (cx - 436, gy3), (cx + 446, gy3)); untraced(x3a); stroke(x3a, 3); tag(x3a, c3);
plot(p3, (cx, gy3), 180, 150, "x^3/3-x", (-2.2, 2.2));
untraced(p3); stroke(p3, 4); gradient(p3, blue, lime, gold, 270); tag(p3, c3);
tangent(tg3, p3, -2.1, 300); hidden(tg3); tag(tg3, c3);
slope(sl3, p3, -2.1); hidden(sl3); tag(sl3, c3);
extrema(ex3, p3, gold); hidden(ex3); tag(ex3, c3);
inflections(if3, p3, magenta); hidden(if3); tag(if3, c3);
equation(eq3, (cx, 230), `f'(x)=x^2-1`, 38); hidden(eq3); sticky(eq3);
// ================= ch 4 + 5 cast: INTEGRAL and the FTC =================
// example A: f(x) = 0.3 x (4 - x), area 0..4 is exactly 3.2
// example B: a sine arch whose signed area cancels to zero
let gx4 = cx - 340;
arrow(x4a, (gx4 - 40, gy), (gx4 + 730, gy)); untraced(x4a); stroke(x4a, 3); tag(x4a, c4);
arrow(y4a, (gx4, gy), (gx4, 240)); untraced(y4a); stroke(y4a, 3); tag(y4a, c4);
plot(p4, (gx4, gy), 170, 80, "0.3*x*(4-x)", (0, 4));
untraced(p4); stroke(p4, 4); gradient(p4, blue, gold, 270); tag(p4, c4);
for i in 0..8 {
let xm = (i + 0.5)*0.5;
let hgt = 0.3*xm*(4 - xm);
rect(rs{i}, (gx4 + xm*170, gy - hgt*80/2 + 24), 0.5*170 - 3, hgt*80);
color(rs{i}, gold); opacity(rs{i}, 0.55); tag(rs{i}, rstrips);
}
hidden(rstrips);
area(ar4, p4, 0, 0); color(ar4, gold); opacity(ar4, 0.3);
integral(int4, p4, 0, 0, (gx4 + 640, 300)); hidden(int4);
plot(p4b, (gx4, gy), 170, 80, "1.2*sin(1.5708*x)", (0, 4));
untraced(p4b); stroke(p4b, 4); color(p4b, teal);
area(arb, p4b, 0, 0); color(arb, teal); opacity(arb, 0.3);
integral(intb, p4b, 0, 0, (gx4 + 640, 350)); hidden(intb);
equation(eq4, (cx, 230), `A \;\approx\; \sum_i \textcolor{gold}{f(x_i)}\,\Delta x`, 38); hidden(eq4); sticky(eq4);
// FTC: the accumulation A(x) drawn as its own curve, then ITS derivative
accum(acF, p4, 0, gold); untraced(acF);
deriv(dfF, acF, magenta); untraced(dfF); dashed(dfF, 12, 9);
text(alab, (gx4 + 700, 296), "A(x) - area so far"); size(alab, 24); color(alab, gold); hidden(alab);
text(flab, (cx, 430), "f(x)"); size(flab, 24); color(flab, cyan); hidden(flab);
equation(eqF, (cx, 230), `A(x) \;=\; \int_0^x f(t)\,dt`, 40); hidden(eqF); sticky(eqF); z(eqF, 10);
// finale
circle(confbox, (cx, 230), 80); hidden(confbox);
particles(conf, confbox, 22, 4, 9, "ring"); color(conf, gold); hidden(conf);
caption(r1, "zoom in: the derivative is the slope of a moment", (cx, 400), 28, magenta); hidden(r1);
caption(r2, "add up: the integral is the area of a journey", (cx, 455), 28, gold); hidden(r2);
caption(r3, "the Fundamental Theorem: each undoes the other", (cx, 510), 28); hidden(r3);
// ================= timeline =================
// ---- cold open
type(head, 1.2);
wordpop(menu, 0.07);
wait(1.0);
fade(menu, 0.5);
// ---- ch 1: LIMITS ------------------------------------------------------
mark("limits");
cue(whoosh);
show(h1, 0.5);
par { draw(x1a, 0.6); draw(y1a, 0.6); }
show(rider, 0.2);
par { draw(p1, 1.4); travel(rider, p1, 1.4, smooth); }
fade(rider, 0.3);
show(eq1, 0.6);
say(cap, "At x = 1 this formula divides zero by zero - the curve has a HOLE", 0.5);
show(lm, 0.6);
cue(tick);
rewrite(eq1, `\lim_{x\to 1}\frac{x^3-x}{x-1} \;=\; \lim_{x\to 1}\,x(x+1)`, 0.9);
par { cam((gx1 + 170, 470), 1.2, smooth); zoom(1.8, 1.2, smooth); }
say(cap, "The limit asks: where were you HEADING? Watch the dot commit", 0.5);
to(lm, x, 1, 2.6, smooth);
cue(tick);
rewrite(eq1, `\lim_{x\to 1}\frac{x^3-x}{x-1} \;=\; 2`, 0.9);
flash(lm, cyan);
wait(0.8);
par { cam((cx, cy), 1.0, smooth); zoom(1, 1.0, smooth); }
// second example: chasing x to infinity
say(cap, "Limits also ask where the ROAD ends: chase x toward infinity", 0.5);
par { fade(p1, 0.5); fade(lm, 0.5); }
draw(p1b, 1.2);
show(lmb, 0.5);
to(lmb, x, 1.8, 2.0, smooth);
cue(tick);
rewrite(eq1, `\lim_{x\to\infty}\frac{4}{1+e^{-2.2x}} \;=\; 4`, 0.9);
wait(0.7);
par { shift(w2, (w + 80, 0), 0.7, linear); fade(c1, 0.6); disintegrate(eq1, 0.8); fade(h1, 0.5); }
// ---- ch 2: THE DERIVATIVE ----------------------------------------------
mark("derivative");
cue(whoosh);
show(h2, 0.5);
par { draw(x2a, 0.6); draw(y2a, 0.6); }
draw(p2, 1.2);
show(anch, 0.4);
show(eq2, 0.6);
say(cap, "Pin one point. A SECANT through a neighbour measures average slope", 0.5);
par { cam((gx2 + 200, 470), 1.0, smooth); zoom(1.35, 1.0, smooth); }
par { show(sec0, 0.4); show(sd0, 0.4); }
cue(tick);
rewrite(eq2, `\frac{\Delta y}{\Delta x} \;=\; 1+\tfrac{h}{2} \;=\; 1.75`, 0.8);
par { fade(sec0, 0.4); fade(sd0, 0.4); show(sec1, 0.4); show(sd1, 0.4); }
cue(tick);
rewrite(eq2, `\frac{\Delta y}{\Delta x} \;=\; 1+\tfrac{h}{2} \;=\; 1.45`, 0.8);
par { fade(sec1, 0.4); fade(sd1, 0.4); show(sec2, 0.4); show(sd2, 0.4); }
cue(tick);
rewrite(eq2, `\frac{\Delta y}{\Delta x} \;=\; 1+\tfrac{h}{2} \;=\; 1.15`, 0.8);
say(cap, "Slide the neighbour home: the secants collapse onto ONE line", 0.5);
par { fade(sec2, 0.5); fade(sd2, 0.5); show(tg2, 0.5); show(sl2, 0.5); }
flash(tg2, gold);
cue(pop);
cue(tick);
rewrite(eq2, `f'(1) \;=\; 1`, 0.9);
wait(0.6);
par { cam((cx, cy), 1.0, smooth); zoom(1, 1.0, smooth); }
// now do it EVERYWHERE: the tangent glides, recording f' as a new curve
say(cap, "Do it at every point - and record the answer as a NEW curve", 0.5);
par { to(tg2, x, -0.4, 0.7, smooth); to(sl2, x, -0.4, 0.7, smooth); }
par { to(tg2, x, 2.3, 2.6, smooth); to(sl2, x, 2.3, 2.6, smooth); draw(dv2, 2.6); }
cue(tick);
rewrite(eq2, `f'(x) \;=\; x`, 0.9);
wait(0.6);
// second example: every curve carries its shadow
say(cap, "It works for EVERY curve: sine's recorded slope is cosine", 0.5);
par { fade(p2, 0.5); fade(dv2, 0.5); fade(tg2, 0.5); fade(sl2, 0.5); fade(anch, 0.4); }
par { show(bl2, 0.4); draw(p2s, 1.2); }
draw(dvs, 1.6);
cue(tick);
rewrite(eq2, `\big(\sin x\big)' \;=\; \cos x`, 0.9);
wait(0.7);
par { shift(w3, (w + 80, 0), 0.7, linear); fade(c2, 0.6); disintegrate(eq2, 0.8); fade(h2, 0.5); }
// ---- ch 3: SHAPE -------------------------------------------------------
mark("shape");
cue(whoosh);
show(h3, 0.5);
draw(x3a, 0.7);
draw(p3, 1.4);
show(eq3, 0.6);
say(cap, "The derivative is a story about SHAPE. Ride the tangent and listen", 0.5);
par { show(tg3, 0.5); show(sl3, 0.5); }
par { to(tg3, x, -1, 1.6, smooth); to(sl3, x, -1, 1.6, smooth); }
cue(pop);
show(ex3, 0.4); pulse(ex3, 0.8);
say(cap, "Slope zero: the curve turns. A peak - then a valley", 0.5);
par { to(tg3, x, 0, 1.2, smooth); to(sl3, x, 0, 1.2, smooth); }
show(if3, 0.4); flash(if3, magenta);
say(cap, "Here the BEND flips: concave down becomes concave up - an inflection", 0.5);
par { to(tg3, x, 1, 1.2, smooth); to(sl3, x, 1, 1.2, smooth); }
pulse(ex3, 0.8);
par { to(tg3, x, 2.1, 1.2, smooth); to(sl3, x, 2.1, 1.2, smooth); }
cue(tick);
rewrite(eq3, `f'=0:\ \text{turning}\qquad f''=0:\ \text{bending flips}`, 0.9);
wait(1.0);
par { shift(w4, (w + 80, 0), 0.7, linear); fade(c3, 0.6); disintegrate(eq3, 0.8); fade(h3, 0.5); }
// ---- ch 4: THE INTEGRAL ------------------------------------------------
mark("integral");
cue(whoosh);
show(h4, 0.5);
par { draw(x4a, 0.6); draw(y4a, 0.6); }
draw(p4, 1.3);
show(eq4, 0.6);
say(cap, "The other question: how much accumulates? Slice it into strips", 0.5);
stagger(0.08) { for i in 0..8 { par { show(rs{i}, 0.3); shift(rs{i}, (0, -24), 0.4, out); } } }
wait(0.8);
say(cap, "Thinner and thinner strips: the sum becomes the exact area", 0.5);
show(int4, 0.3);
par { fade(rstrips, 1.1); to(ar4, x, 4, 2.2, smooth); to(int4, x, 4, 2.2, smooth); }
flash(int4, gold);
cue(tick);
rewrite(eq4, `A \;=\; \int_0^4 f\,dx \;=\; 3.2`, 0.9);
wait(0.8);
// second example: signed area - the hill cancels the valley
say(cap, "One warning: below the axis, area counts NEGATIVE", 0.5);
par { fade(p4, 0.5); fade(ar4, 0.5); fade(int4, 0.5); }
draw(p4b, 1.2);
show(intb, 0.3);
par { to(arb, x, 4, 2.0, smooth); to(intb, x, 4, 2.0, smooth); }
flash(intb, teal);
cue(tick);
rewrite(eq4, `\int_0^4 \sin\!\Big(\frac{\pi x}{2}\Big)dx \;=\; 0`, 0.9);
say(cap, "The hill above cancels the valley below - signed area", 0.5);
wait(0.6);
par { fade(p4b, 0.5); fade(arb, 0.5); fade(intb, 0.4); show(p4, 0.5); }
par { shift(w5, (w + 80, 0), 0.7, linear); disintegrate(eq4, 0.8); fade(h4, 0.5); }
// ---- ch 5: THE FUNDAMENTAL THEOREM --------------------------------------
mark("ftc");
cue(whoosh);
show(h5, 0.5);
say(cap, "Rewind. This time, GRAPH the running total as you sweep", 0.5);
show(ar4, 0.4);
to(ar4, x, 0, 0.7, smooth);
show(eqF, 0.7);
par { to(ar4, x, 4, 2.8, smooth); draw(acF, 2.8); }
show(alab, 0.5); show(flab, 0.5);
say(cap, "Now take the DERIVATIVE of that gold curve - the slope of the total", 0.5);
cue(tick);
rewrite(eqF, `\frac{d}{dx}A(x) \;=\; \;?`, 0.8);
draw(dfF, 2.2);
flash(p4, magenta);
cue(chime);
say(cap, "It lands EXACTLY on f. Accumulating, then differentiating, is a round trip", 0.5);
cue(tick);
rewrite(eqF, `\boxed{\;\frac{d}{dx}\int_0^x f(t)\,dt \;=\; f(x)\;}`, 1.0);
wait(1.2);
// ---- finale
par { fade(c4, 0.7); fade(ar4, 0.7); fade(acF, 0.7); fade(dfF, 0.7); fade(alab, 0.5); fade(flab, 0.5); fade(h5, 0.5); }
seq { show(conf, 0.1); burst(conf, 1.4); }
par {
breathe(eqF, 3, 0.05, 0, 7);
seq { stagger(0.4) { wordpop(r1, 0.05); wordpop(r2, 0.05); wordpop(r3, 0.05); } }
}
say(cap, "Calculus I: zoom in, add up - and the bridge between", 0.6);
wait(2.5);
integrals
Three views of the integral on one bump: coarse Riemann strips rise into place with the sample dots f(x*) riding the curve, refine to 32, then the true area sweeps open — glowing act-wipes between.
title("Ways to Integrate");
canvas(1000, 1000);
template("black");
// ---------- layout constants ----------
let gx = cx - 340; // graph origin (math x = 0)
let gy = h - 160; // graph baseline (math y = 0)
let sx = 170; // px per x-unit
let sy = 100; // px per y-unit
// ---------- HUD: headings & captions (sticky = survive camera moves) ----------
text(head, (cx, 60), "Ways to Integrate"); display(head); cursor(head); sticky(head);
text(t1, (cx, 122), "1. Riemann - slice the domain"); size(t1, 30); color(t1, cyan); hidden(t1); sticky(t1);
text(t2, (cx, 122), "2. Lebesgue - slice the range"); size(t2, 30); color(t2, gold); hidden(t2); sticky(t2);
text(t3, (cx, 122), "3. Henstock-Kurzweil - slice with a gauge"); size(t3, 30); color(t3, magenta); hidden(t3); sticky(t3);
caption(in1, "Riemann - vertical strips of equal width", (cx, cy - 70), 32, cyan); hidden(in1);
caption(in2, "Lebesgue - horizontal layers weighed by measure", (cx, cy), 32, gold); hidden(in2);
caption(in3, "Henstock-Kurzweil - strip widths set by a gauge", (cx, cy + 70), 32, magenta); hidden(in3);
text(cap, (cx, h - 42), ""); size(cap, 26); sticky(cap);
// ---------- act wipes: glowing verticals that sweep the frame between acts ----------
line(wipe1, (-40, 140), (-40, h - 90)); stroke(wipe1, 4); color(wipe1, gold); glow(wipe1, 14);
line(wipe2, (-40, 140), (-40, h - 90)); stroke(wipe2, 4); color(wipe2, magenta); glow(wipe2, 14);
line(wipe3, (-40, 140), (-40, h - 90)); stroke(wipe3, 4); glow(wipe3, 14);
// ---------- axes ----------
arrow(xax, (gx - 30, gy), (gx + 4*sx + 50, gy)); untraced(xax); stroke(xax, 3);
arrow(yax, (gx, gy + 30), (gx, 175)); untraced(yax); stroke(yax, 3);
text(xl0, (gx, gy + 26), "0"); size(xl0, 22); color(xl0, dim); hidden(xl0);
text(xl4, (gx + 4*sx, gy + 26), "4"); size(xl4, 22); color(xl4, dim); hidden(xl4);
// ---------- the smooth bump (acts 1 & 2) ----------
plot(fplot, (gx, gy), sx, sy, "3*exp(-((x-2)^2))", (0, 4));
untraced(fplot); stroke(fplot, 4); gradient(fplot, blue, cyan, gold, 270);
dot(rider, (gx, gy), 7); color(rider, gold); glow(rider, 8); hidden(rider);
// Riemann: 8 coarse strips (declared 26px low, they RISE into place)
// + the SAMPLE DOTS f(x_i*) riding the curve top - the star of the definition
for i in 0..8 {
let xm = (i + 0.5)*0.5;
let hgt = 3*exp(-((xm - 2)^2));
rect(rc{i}, (gx + xm*sx, gy - hgt*sy/2 + 26), 0.5*sx - 3, hgt*sy);
color(rc{i}, cyan); opacity(rc{i}, 0.55); tag(rc{i}, coarse);
dot(sm{i}, (gx + xm*sx, gy - hgt*sy), 5);
color(sm{i}, cyan); glow(sm{i}, 6); tag(sm{i}, samples);
}
hidden(coarse); hidden(samples);
// Riemann: 32 fine strips
for i in 0..32 {
let xm = (i + 0.5)*0.125;
let hgt = 3*exp(-((xm - 2)^2));
rect(rf{i}, (gx + xm*sx, gy - hgt*sy/2), 0.125*sx - 2, hgt*sy);
color(rf{i}, cyan); opacity(rf{i}, 0.7); tag(rf{i}, fine);
}
hidden(fine);
// the limit: swept exact area + live integral readout
area(ar, fplot, 0, 0); color(ar, cyan); opacity(ar, 0.35);
integral(intg, fplot, 0, 0, (gx + 4*sx - 40, 265)); hidden(intg);
// the pivot strip: one vertical slice that ROTATES into a horizontal one
rect(pv, (gx + 2*sx, gy - 1.5*sy), 42, 3*sy); color(pv, cyan); opacity(pv, 0.7); hidden(pv);
// Lebesgue: 10 horizontal layers (declared offset sideways, they SLIDE in alternating)
for j in 0..10 {
let ym = (j + 0.5)*0.3;
let hf = sqrt(ln(3/ym));
let jm = j - 2*floor(j/2);
let xoff = 180*jm - 90;
rect(ls{j}, (gx + 2*sx - xoff, gy - ym*sy), 2*hf*sx, 0.3*sy - 2);
color(ls{j}, gold); opacity(ls{j}, 0.55); tag(ls{j}, layers);
}
hidden(layers);
// the level scanner: a dashed line that sweeps UP through the layers
line(lev, (gx, gy - 0.15*sy), (gx + 4*sx, gy - 0.15*sy));
dashed(lev); color(lev, gold); stroke(lev, 2); hidden(lev);
// ---------- the spiky function (act 3) ----------
plot(gplot, (gx, gy), sx, sy, "1.1+1.7*exp(-40*((x-3)^2))", (0, 4));
untraced(gplot); stroke(gplot, 4); gradient(gplot, teal, magenta, 270);
dot(rider2, (gx, gy), 7); color(rider2, magenta); glow(rider2, 8); hidden(rider2);
// the gauge ITSELF, drawn as a real function: delta(x) dips where f spikes
plot(dplot, (gx, gy), sx, 60, "0.55-0.45*exp(-20*((x-3)^2))", (0, 4));
untraced(dplot); dashed(dplot); stroke(dplot, 2); color(dplot, magenta); opacity(dplot, 0.7);
text(dlab, (gx + 4*sx + 34, gy - 33), `$\delta(x)$`); size(dlab, 24); color(dlab, magenta); hidden(dlab);
// uniform cells first (they rise in) - two of them will FAIL at the spike
for i in 0..8 {
let xm = (i + 0.5)*0.5;
let hgt = 1.1 + 1.7*exp(-40*((xm - 3)^2));
rect(hku{i}, (gx + xm*sx, gy - hgt*sy/2 + 26), 0.5*sx - 3, hgt*sy);
color(hku{i}, teal); opacity(hku{i}, 0.5); tag(hku{i}, hk);
}
hidden(hk);
// gauge-fine cells + their TAG POINTS t_i pinned on the curve
for i in 0..10 {
let xm = 2.5 + (i + 0.5)*0.1;
let hgt = 1.1 + 1.7*exp(-40*((xm - 3)^2));
rect(nk{i}, (gx + xm*sx, gy - hgt*sy/2), 0.1*sx - 2, hgt*sy);
color(nk{i}, magenta); opacity(nk{i}, 0.75); tag(nk{i}, gauge); tag(nk{i}, hk);
dot(tg{i}, (gx + xm*sx, gy - hgt*sy), 3);
color(tg{i}, magenta); glow(tg{i}, 5); tag(tg{i}, tgd);
}
hidden(gauge); hidden(tgd);
bracelabel(bnar, (gx + 2.5*sx, gy + 16), (gx + 3.5*sx, gy + 16), `gauge $\delta(x)$ shrinks here`, 14);
color(bnar, magenta); hidden(bnar);
// ---------- equations (each one LIVES: it rewrites, then turns to dust) ----------
equation(eqR, (cx, 190), `S_{8} \;=\; \sum_{i=1}^{8} f(x_i^{*})\,\textcolor{cyan}{\Delta x}`, 40); hidden(eqR); sticky(eqR);
equation(eqL, (cx, 190), `\text{layer}_j \;=\; \textcolor{gold}{\mu\{\,f > t_j\,\}}\cdot\Delta t`, 40); hidden(eqL); sticky(eqL);
equation(eqH, (cx, 190), `\sum_i f(\textcolor{magenta}{t_i})\,\Delta x_i`, 38); hidden(eqH); sticky(eqH);
equation(eqS, (cx, 250), `\textcolor{cyan}{\text{Riemann}} \;\subsetneq\; \textcolor{gold}{\text{Lebesgue}} \;\subsetneq\; \textcolor{magenta}{\text{Henstock-Kurzweil}}`, 44); hidden(eqS); sticky(eqS); z(eqS, 10);
// ---------- finale confetti + triptych reprise of the three motifs ----------
circle(confbox, (cx, 290), 90); hidden(confbox);
particles(conf, confbox, 24, 4, 11, "ring"); color(conf, gold); hidden(conf);
let mxr = cx - 380;
let mxh = cx + 380;
let myb = 490;
// mini Riemann: a tiny staircase of vertical strips
for i in 0..6 {
let xm = (i + 0.5)/3;
let hgt = 76*exp(-2*((xm - 1)^2));
rect(mr{i}, (mxr - 110 + xm*110, myb - hgt/2), 34, hgt);
color(mr{i}, cyan); opacity(mr{i}, 0.6); tag(mr{i}, minir);
}
// mini Lebesgue: a tiny layer cake
for j in 0..5 {
let wd = 200 - 34*j;
rect(ml{j}, (cx, myb - 9 - j*17), wd, 14);
color(ml{j}, gold); opacity(ml{j}, 0.6); tag(ml{j}, minil);
}
// mini Henstock-Kurzweil: coarse cells, then a burst of fine ones at the spike
for i in 0..3 {
rect(mw{i}, (mxh - 90 + i*42, myb - 19), 40, 38);
color(mw{i}, teal); opacity(mw{i}, 0.6); tag(mw{i}, minih);
}
for k in 0..5 {
let hgt = 38 + 52*exp(-0.7*((k - 2)^2));
rect(mn{k}, (mxh + 10 + (k + 0.5)*11, myb - hgt/2), 9, hgt);
color(mn{k}, magenta); opacity(mn{k}, 0.75); tag(mn{k}, minih);
}
rect(mt, (mxh + 88, myb - 19), 40, 38); color(mt, teal); opacity(mt, 0.6); tag(mt, minih);
hidden(minir); hidden(minil); hidden(minih);
caption(ms1, "slice the domain", (mxr, 560), 26, cyan); hidden(ms1);
caption(ms2, "slice the range", (cx, 560), 26, gold); hidden(ms2);
caption(ms3, "slice by a gauge", (mxh, 560), 26, magenta); hidden(ms3);
// ================= timeline =================
// ---- cold open: title types on, the three names POP word by word
type(head, 1.2);
stagger(0.5) { wordpop(in1, 0.05); wordpop(in2, 0.05); wordpop(in3, 0.05); }
wait(1.2);
par { fade(in1, 0.4); fade(in2, 0.4); fade(in3, 0.4); }
// ---- act 1: RIEMANN ----------------------------------------------------
cue(whoosh);
show(t1, 0.5);
par { draw(xax, 0.6); draw(yax, 0.6); show(xl0, 0.6); show(xl4, 0.6); }
show(rider, 0.2);
par { draw(fplot, 1.5); travel(rider, fplot, 1.5, smooth); }
fade(rider, 0.3);
say(cap, "Slice the domain into equal strips; each SAMPLE DOT sets its strip's height", 0.5);
stagger(0.08) { for i in 0..8 { par { show(rc{i}, 0.3); shift(rc{i}, (0, -26), 0.4, out); show(sm{i}, 0.3); } } }
show(eqR, 0.6);
wait(1.2);
// push IN while the strips refine, equation rewrites underneath
say(cap, "Refine: quadruple the strips - the staircase hugs the curve", 0.5);
par { cam((cx, gy - 1.2*sy), 1.1, smooth); zoom(1.45, 1.1, smooth); }
par { fade(coarse, 0.5); fade(samples, 0.5); stagger(0.02) { for i in 0..32 { show(rf{i}, 0.22); } } }
cue(tick);
rewrite(eqR, `S_{32} \;=\; \sum_{i=1}^{32} f(x_i^{*})\,\textcolor{cyan}{\Delta x}`, 0.8);
wait(0.9);
par { cam((cx, cy), 1.0, smooth); zoom(1, 1.0, smooth); }
// the limit: strips dissolve into the EXACT swept area, the number climbs
cue(tick);
rewrite(eqR, `\int_0^{4} f\,dx \;=\; \lim_{n\to\infty} S_n`, 0.9);
say(cap, "In the limit, the sum becomes the exact area", 0.5);
show(intg, 0.3);
par { fade(fine, 1.2); to(ar, x, 4, 2.2, smooth); to(intg, x, 4, 2.2, smooth); }
flash(intg, gold);
wait(1.0);
par { shift(wipe1, (w + 80, 0), 0.7, linear); fade(ar, 0.5); fade(intg, 0.5); disintegrate(eqR, 0.8); fade(t1, 0.5); }
// ---- act 2: LEBESGUE ---------------------------------------------------
cue(whoosh);
show(t2, 0.5);
say(cap, "Turn the slicing sideways: cut the range, not the domain", 0.5);
show(pv, 0.3);
par { rotate(pv, 90, 0.9, smooth); recolor(pv, gold, 0.9); }
fade(pv, 0.4);
show(eqL, 0.6);
show(lev, 0.3);
par {
stagger(0.1) { for j in 0..10 { par { show(ls{j}, 0.35); shift(ls{j}, (180*(j - 2*floor(j/2)) - 90, 0), 0.5, out); } } }
shift(lev, (0, -2.7*sy), 1.9, smooth);
zoom(1.06, 2.2, smooth);
}
// the stack breathes: a travelling wave rolls up through the layers
par { for j in 0..10 { oscillate(ls{j}, y, 2.4, 3, j/10, 3.2); } pulse(ls4, 0.9); }
say(cap, "A layer at height t sits over the set where f > t; its width is that set's measure", 0.5);
cue(tick);
rewrite(eqL, `\int f\,d\mu \;=\; \int_0^{\infty}\textcolor{gold}{\mu\{\,f > t\,\}}\,dt`, 0.9);
wait(1.2);
fade(lev, 0.4);
par { shift(wipe2, (w + 80, 0), 0.7, linear); fade(layers, 0.5); disintegrate(eqL, 0.8); fade(t2, 0.5); fade(fplot, 0.5); zoom(1, 0.8, smooth); }
// ---- act 3: HENSTOCK-KURZWEIL ------------------------------------------
cue(whoosh);
show(t3, 0.5);
show(rider2, 0.2);
par { draw(gplot, 1.4); travel(rider2, gplot, 1.4, smooth); }
fade(rider2, 0.3);
say(cap, "New function, old recipe: equal strips again...", 0.5);
stagger(0.07) { for i in 0..8 { par { show(hku{i}, 0.3); shift(hku{i}, (0, -26), 0.4, out); } } }
show(eqH, 0.6);
wait(0.5);
// push in on the spike: the uniform cells FAIL, flash red, shake, shatter
par { cam((gx + 3*sx, gy - 1.3*sy), 1.1, smooth); zoom(1.6, 1.1, smooth); }
say(cap, "The spike slips between samples - equal widths are too coarse here", 0.5);
par { recolor(hku5, red, 0.3); recolor(hku6, red, 0.3); shake(hku5, 0.8); shake(hku6, 0.8); }
cue(pop);
par { disintegrate(hku5, 0.8); disintegrate(hku6, 0.8); }
// the gauge appears AS A FUNCTION: it dips exactly where f is dangerous
say(cap, `The gauge $\delta(x)$ is itself a function - watch it dip where f spikes`, 0.5);
par { draw(dplot, 1.0); show(dlab, 0.6); }
say(cap, `Tiny slices where f is wild - and every slice carries its tag point $t_i$`, 0.5);
stagger(0.05) { for i in 0..10 { par { show(nk{i}, 0.22); show(tg{i}, 0.22); } } }
show(bnar, 0.5);
wait(0.8);
par { cam((cx, cy), 1.0, smooth); zoom(1, 1.0, smooth); }
cue(tick);
rewrite(eqH, `\Delta x_i < \textcolor{magenta}{\delta(t_i)} \;\Longrightarrow\; \Big|\sum_i f(t_i)\,\Delta x_i - A\Big| < \varepsilon`, 0.9);
flow(gplot, 1.5, forward, once);
wait(1.2);
// ---- finale: wipe to black, hierarchy + confetti + triptych reprise ------
par {
shift(wipe3, (w + 80, 0), 0.7, linear);
fade(hk, 0.5); fade(tgd, 0.5); fade(gplot, 0.5); fade(dplot, 0.5); fade(dlab, 0.5);
fade(xax, 0.5); fade(yax, 0.5); fade(xl0, 0.5); fade(xl4, 0.5);
disintegrate(eqH, 0.8); fade(bnar, 0.5); fade(t3, 0.5);
}
cue(chime);
show(eqS, 0.8);
par {
breathe(eqS, 3, 0.05, 0, 7);
seq { show(conf, 0.1); burst(conf, 1.4); }
seq {
stagger(0.35) {
stagger(0.05) { for i in 0..6 { show(mr{i}, 0.25); } }
stagger(0.06) { for j in 0..5 { show(ml{j}, 0.25); } }
seq { stagger(0.04) { for i in 0..3 { show(mw{i}, 0.2); } } stagger(0.04) { for k in 0..5 { show(mn{k}, 0.2); } } show(mt, 0.2); }
}
stagger(0.3) { wordpop(ms1, 0.06); wordpop(ms2, 0.06); wordpop(ms3, 0.06); }
}
}
say(cap, "One area - three philosophies of slicing", 0.6);
wait(2.5);
disc-integration
A flat 2D region spins up into a 3D solid of revolution: one living equation rewrites from the profile r=√z to the volume ∫πr² dz, a single strip lifts into a disc, and a live volume readout climbs.
title("Disc Integration");
canvas(1000, 1000);
template("black");
// ================= HUD (2D always draws above the 3D world) =================
text(head, (cx, 60), "Disc Integration"); display(head); cursor(head);
text(t1, (cx, 122), "1. A region under a curve"); size(t1, 30); color(t1, gold); hidden(t1);
text(t2, (cx, 122), "2. Spin it - every strip sweeps a disc"); size(t2, 30); color(t2, cyan); hidden(t2);
text(t3, (cx, 122), "3. Stack the discs - integrate"); size(t3, 30); color(t3, magenta); hidden(t3);
text(cap, (cx, h - 42), ""); size(cap, 26);
// one living equation: it rewrites its way from the curve to the volume
equation(eqM, (cx, 190), `y=\sqrt{x}\quad(0\le x\le 4)`, 40); hidden(eqM);
// live volume readout
counter(vol, (w - 185, 300), 0, 2, "V = ", ""); hidden(vol);
// ================= act 1 cast: the flat 2D region =================
let gx = cx - 300;
let gy = 520;
let s2 = 140;
arrow(xax2, (gx - 30, gy), (gx + 4*s2 + 50, gy)); untraced(xax2); stroke(xax2, 3); tag(xax2, d2);
arrow(yax2, (gx, gy + 30), (gx, gy - 2.4*s2)); untraced(yax2); stroke(yax2, 3); tag(yax2, d2);
text(x0b, (gx, gy + 26), "0"); size(x0b, 22); color(x0b, dim); hidden(x0b); tag(x0b, d2);
text(x4b, (gx + 4*s2, gy + 26), "4"); size(x4b, 22); color(x4b, dim); hidden(x4b); tag(x4b, d2);
plot(rplot, (gx, gy), s2, s2, "sqrt(x)", (0, 4));
untraced(rplot); stroke(rplot, 4); gradient(rplot, blue, cyan, gold, 270); tag(rplot, d2);
dot(rider, (gx, gy), 7); color(rider, gold); glow(rider, 8); hidden(rider); tag(rider, d2);
area(ar2, rplot, 0, 0); color(ar2, cyan); opacity(ar2, 0.3); tag(ar2, d2);
// the one strip we will follow into 3D (x = 2.25 - remember it)
rect(strip, (gx + 2.25*s2, gy - sqrt(2.25)*s2/2), 66, sqrt(2.25)*s2);
color(strip, cyan); opacity(strip, 0.75); hidden(strip); tag(strip, d2);
// ================= act 2-3 cast: the 3D world (right-handed, Z-up) ==========
camera3((8.2, -9.5, 5.0), (0, 0, 1.9), 42);
axes3(ax3, (0, 0, 0), 4.6, 1); hidden(ax3);
// the spin axis, called out explicitly
line3(spinax, (0, 0, -0.3), (0, 0, 4.6)); thick(spinax, 0.015); color(spinax, dim); hidden(spinax);
text(zlab, (cx, cy), "spin axis"); size(zlab, 22); color(zlab, dim); hidden(zlab);
pin3(zlab, (0, 0, 4.55), (0, -16));
// the profile curve, stood upright: r = sqrt(z)
curve3(prof, "sqrt(t)", "0", "t", (0, 4)); untraced(prof); thick(prof, 0.04); color(prof, gold);
// ghost of the swept surface: bright wireframe revolve
revolve3(ghost, (0, 0, 0), "sqrt(t)", (0, 4), 64);
finish3(ghost, "wire=1"); color(ghost, teal); opacity(ghost, 0.4); untraced(ghost);
// 8 coarse discs (thin cylinders = many-sided prisms), declared low so they RISE
for i in 0..8 {
let zc = (i + 0.5)*0.5;
prism3(dc{i}, (0, 0, zc - 0.45), 48, sqrt(zc), 0.48);
color(dc{i}, cyan); opacity(dc{i}, 0.85); tag(dc{i}, coarse3);
}
hidden(coarse3);
// 20 fine discs
for i in 0..20 {
let zc = (i + 0.5)*0.2;
prism3(df{i}, (0, 0, zc - 0.3), 48, sqrt(zc), 0.19);
color(df{i}, cyan); opacity(df{i}, 0.9); tag(df{i}, fine3);
}
hidden(fine3);
// radius callout for the pulled-out disc (z = 2.25, r = 1.5, pulled to x + 3)
arrow3(rarr, (3.0, 0, 2.62), (4.5, 0, 2.62)); thick(rarr, 0.03); color(rarr, gold); untraced(rarr);
text(rlab, (cx, cy), `$r = \sqrt{z}$`); size(rlab, 26); color(rlab, gold); hidden(rlab);
pin3(rlab, (3.75, 0, 2.62), (0, -30));
// the exact solid, and the vase it will morph into ("any profile, same recipe")
revolve3(solid, (0, 0, 0), "sqrt(t)", (0, 4), 64);
finish3(solid, "shading=smooth depth=0.2 shadow=0.2"); color(solid, cyan); hidden(solid);
revolve3(vase, (0, 0, 0), "1.1+0.55*sin(1.7*t-0.9)", (0, 4), 64); hidden(vase);
morph3(solid, vase);
// ================= timeline =================
// ---- cold open
type(head, 1.1);
// ---- act 1: the flat region
cue(whoosh);
show(t1, 0.5);
par { draw(xax2, 0.6); draw(yax2, 0.6); show(x0b, 0.6); show(x4b, 0.6); }
show(rider, 0.2);
par { draw(rplot, 1.4); travel(rider, rplot, 1.4, smooth); }
fade(rider, 0.3);
show(eqM, 0.6);
say(cap, "Take the region under the curve...", 0.5);
to(ar2, x, 4, 1.6, smooth);
say(cap, "...and keep your eye on this one strip", 0.5);
show(strip, 0.4);
pulse(strip, 0.9);
wait(0.8);
// ---- the pivot: leave flatland
say(cap, "Now stand the region upright - and SPIN it around the axis", 0.5);
cue(whoosh);
fade(d2, 0.8);
show(t2, 0.5); fade(t1, 0.3);
par { show(ax3, 0.8); show(spinax, 0.8); show(zlab, 0.8); }
draw(prof, 1.2);
flash(spinax, magenta);
// THE MONEY SHOT: the profile curve physically sweeps 360 degrees
// around the axis while the wireframe surface traces on beneath it
par {
turn3(prof, (0, 0, 2), z, 360, 2.6, smooth);
draw(ghost, 2.6);
orbit3(-20, 18, 12.0, 2.6, smooth);
}
cue(tick);
rewrite(eqM, `dV \;=\; \pi\,\textcolor{cyan}{r^2}\,dz`, 0.9);
say(cap, "Every strip of area sweeps a thin DISC of volume", 0.5);
// ---- act 3: the disc stack rises
show(t3, 0.5); fade(t2, 0.3);
stagger(0.09) { for i in 0..8 { par { show(dc{i}, 0.3); shift3(dc{i}, (0, 0, 0.45), 0.4, out); } } }
wait(0.4);
// pull ONE disc out of the stack - the same strip from act 1
say(cap, "That same strip from before - now a cylinder: radius sqrt(z), thickness dz", 0.5);
cue(pop);
shift3(dc4, (3.0, 0, 0), 0.9, smooth);
par { draw(rarr, 0.5); show(rlab, 0.5); }
wait(1.2);
par { fade(rarr, 0.4); fade(rlab, 0.4); shift3(dc4, (-3.0, 0, 0), 0.9, smooth); }
cue(tick);
rewrite(eqM, `V \;\approx\; \sum_i \pi\,r(z_i)^2\,\Delta z`, 0.9);
wait(0.8);
// refine: thinner discs, camera drifts around the stack
say(cap, "Slice thinner - the stack hugs the surface", 0.5);
par {
fade(coarse3, 0.6);
stagger(0.04) { for i in 0..20 { show(df{i}, 0.22); } }
orbit3(35, 24, 11.0, 2.2, smooth);
}
cue(tick);
rewrite(eqM, `V \;=\; \pi\!\int_0^{4}(\sqrt{z}\,)^2\,dz \;=\; \pi\!\int_0^{4} z\,dz`, 0.9);
wait(0.8);
// the limit: discs dissolve into the exact smooth solid, the number climbs
say(cap, "In the limit: the exact solid of revolution", 0.5);
show(vol, 0.3);
par { fade(fine3, 1.2); fade(ghost, 1.2); show(solid, 1.4); to(vol, value, 25.13, 2.2); }
flash(vol, gold);
cue(tick);
rewrite(eqM, `V \;=\; \pi\cdot\tfrac{16}{2} \;=\; 8\pi \;\approx\; 25.13`, 0.9);
par { orbit3(90, 20, 10.5, 2.6, smooth); }
wait(0.6);
// ---- flourish: ANY profile, same recipe - the solid becomes a vase
say(cap, "Any profile, same recipe - just change r(z)", 0.5);
cue(whoosh);
par {
to(solid, morph, 1, 2.6, smooth);
fade(vol, 0.8);
rewrite(eqM, `V \;=\; \pi\int_a^b r(z)^2\,dz`, 1.2);
orbit3(160, 16, 10.5, 2.6, smooth);
}
cue(chime);
par {
breathe(eqM, 3, 0.05, 0, 6);
orbit3(200, 14, 11.0, 6, smooth);
}
say(cap, "Area swept in a circle - volume, one disc at a time", 0.6);
wait(2.0);
reactive-integral
The reactive-math gold example: integration by parts stays one continuous equation,
then a plot verifies that the antiderivative’s slope is the integrand and +C is a
family of vertical translations. Uses rewrite, not hand-positioned formula pieces.
// ============================================================================
// reactive-integral.manic — one calculation, one continuous visual world
// ----------------------------------------------------------------------------
// Integration by parts uses one structured equation. `rewrite` keeps unchanged
// RaTeX parts alive while only the mathematical difference moves or enters.
// The graph then verifies the result: the slope of F is the height of f, and
// +C becomes a visible family of vertical translations.
// ============================================================================
title("A Reactive Integral");
canvas("16:9");
template("plain");
watermark(manicMark, (1135, 30), "Made With Manic");
// Shared colour roles follow the same mathematical objects through every act.
// x / u is magenta; cos, sin and their antiderivative relation are cyan.
text(kicker, (285, 38), "ONE INTEGRAL · ONE CONTINUOUS STORY");
size(kicker, 18); color(kicker, dim); bold(kicker); hidden(kicker);
text(headline, (430, 72), "Watch only the mathematics that changes");
size(headline, 28); color(headline, fg); bold(headline); hidden(headline);
// ---------------------------------------------------------------------------
// ONE PERSISTENT EQUATION
// ---------------------------------------------------------------------------
equation(
work,
(cx, 145),
`\int \textcolor{magenta}{x}\,\textcolor{cyan}{\cos(x)}\,dx`,
46
);
hidden(work);
// The by-parts roles remain visible while the expression changes above them.
equation(uChoice, (330, 225), `u=x`, 28); color(uChoice, magenta); hidden(uChoice);
equation(dvChoice, (525, 225), `dv=\cos(x)\,dx`, 28); color(dvChoice, cyan); hidden(dvChoice);
equation(duChoice, (760, 225), `du=dx`, 28); color(duChoice, magenta); hidden(duChoice);
equation(vChoice, (950, 225), `v=\sin(x)`, 28); color(vChoice, cyan); hidden(vChoice);
text(method, (cx, 278), "INTEGRATION BY PARTS");
size(method, 16); color(method, dim); bold(method); hidden(method);
// ---------------------------------------------------------------------------
// ONE GRAPH, ASKED THREE CONNECTED QUESTIONS
// ---------------------------------------------------------------------------
let ox = cx;
let oy = 515;
let sx = 108;
let sy = 28;
axes(ax, (ox, oy), 550, 165, 1);
color(ax, dim); opacity(ax, 0.50); untraced(ax);
// Integrand and one representative antiderivative (C=0).
plot(f, (ox, oy), sx, sy, "x*cos(x)", (-5, 5));
color(f, cyan); stroke(f, 5); glow(f, 0.8); untraced(f);
plot(bigF, (ox, oy), sx, sy, "x*sin(x)+cos(x)", (-5, 5));
color(bigF, lime); stroke(bigF, 5); dashed(bigF, 18, 11);
glow(bigF, 0.8); untraced(bigF);
// Numerical derivative of F: drawing it lands directly on f.
deriv(dF, bigF);
color(dF, gold); stroke(dF, 3); glow(dF, 0.5); untraced(dF);
// A moving tangent makes “F' = f” a local statement at every x.
tangent(tan, bigF, -3.8, 180); color(tan, gold); hidden(tan);
slope(slopeValue, bigF, -3.8, (18, -24)); color(slopeValue, gold); hidden(slopeValue);
equation(fLabel, (250, 338), `f(x)=x\cos(x)`, 28); color(fLabel, cyan); hidden(fLabel);
equation(FLabel, (1010, 338), `F(x)=x\sin(x)+\cos(x)`, 28); color(FLabel, lime); hidden(FLabel);
equation(proofLabel, (cx, 280), `F'(x)=f(x)=x\cos(x)`, 34); color(proofLabel, gold); hidden(proofLabel);
// +C does not change the derivative: it translates the same curve vertically.
plot(Fup, (ox, oy), sx, sy, "x*sin(x)+cos(x)+1.5", (-5, 5));
plot(Fdown, (ox, oy), sx, sy, "x*sin(x)+cos(x)-1.5", (-5, 5));
color(Fup, magenta); color(Fdown, magenta);
stroke(Fup, 3); stroke(Fdown, 3); opacity(Fup, 0.55); opacity(Fdown, 0.55);
dashed(Fup, 14, 9); dashed(Fdown, 14, 9);
untraced(Fup); untraced(Fdown);
equation(familyLabel, (cx, 280), `F(x)+C\quad\text{changes height, not slope}`, 32);
color(familyLabel, magenta); hidden(familyLabel);
text(caption, (cx, 690), "");
size(caption, 19); color(caption, dim); bold(caption);
// Axes and curves are `untraced`, so their draw progress starts at zero without
// also zeroing opacity. That lets `draw(...)` reveal their geometry later.
// ---------------------------------------------------------------------------
// TIMELINE
// ---------------------------------------------------------------------------
show(kicker, 0.45);
show(headline, 0.55);
say(caption, "Start with the question. Keep the stage — change only its state.");
show(work, 0.65);
wait(1.60);
// Choose u and dv by reusing the same semantic colours.
show(method, 0.35);
say(caption, "Choose the two roles. Colour preserves their identity.");
par { show(uChoice, 0.50); show(dvChoice, 0.65); }
wait(1.20);
par { show(duChoice, 0.50); show(vChoice, 0.50); }
wait(1.50);
// Build x sin(x) - integral sin(x) dx. Reusable glyphs travel to their new
// jobs while the obsolete cosine is the only original factor that leaves.
say(caption, "Apply integration by parts: persistent pieces move; new pieces arrive.");
rewrite(
work,
`\textcolor{magenta}{x}\textcolor{cyan}{\sin(x)}-\int\textcolor{cyan}{\sin(x)}\,dx`,
1.05,
smooth
);
wait(2.00);
// Only the remaining integral changes. x sin(x) stays untouched.
say(caption, "Resolve the remaining integral. The stable product never blinks.");
rewrite(
work,
`\textcolor{magenta}{x}\textcolor{cyan}{\sin(x)}+\textcolor{cyan}{\cos(x)}+\textcolor{gold}{C}`,
1.00,
smooth
);
wait(2.00);
// Move attention from symbolic manipulation into the same idea as geometry.
say(caption, "Now verify the answer visually — without leaving the scene.");
par {
fade(method, 0.40);
fade(uChoice, 0.40); fade(dvChoice, 0.40);
fade(duChoice, 0.40); fade(vChoice, 0.40);
draw(ax, 0.85);
}
draw(f, 1.80);
show(fLabel, 0.45);
wait(1.00);
draw(bigF, 1.80);
show(FLabel, 0.45);
wait(1.00);
// The tangent and its numeric slope glide continuously across F.
say(caption, "At every x, the slope of the green curve equals the blue height.");
par { show(tan, 0.45); show(slopeValue, 0.45); }
par {
to(tan, x, 3.8, 4.80, smooth);
to(slopeValue, x, 3.8, 4.80, smooth);
}
wait(0.60);
// The derivative trace lands directly over the original integrand.
show(proofLabel, 0.50);
draw(dF, 2.20);
flash(f, cyan);
wait(1.40);
// Finish with the geometric meaning of the integration constant.
say(caption, "+C creates a family of answers — same shape, same slope.");
par {
fade(tan, 0.45); fade(slopeValue, 0.45); fade(dF, 0.45);
fade(proofLabel, 0.45); fade(fLabel, 0.45); fade(FLabel, 0.45);
show(familyLabel, 0.55);
pulse(work, 0.70);
}
par { draw(Fup, 1.50); draw(Fdown, 1.50); }
wait(2.80);
rewrite-integration
A full manual-integration walkthrough, animated: one equation object is rewrite-morphed
through all 21 steps — every substitution, partial-fraction split and back-substitution,
each captioned with its rule — down to the closed form. Display-quality LaTeX throughout.
title("Rewrite Integration Steps");
canvas("16:9");
template("mono");
text(head, (cx, 58), "Integrating by rewriting the denominator");
display(head);
bold(head);
size(head, 32);
hidden(head);
text(sub, (cx, 94), "Manual integration — partial fractions by substitution");
size(sub, 20);
color(sub, dim);
hidden(sub);
rect(panelBox, (cx, cy + 14), w - 150, 380);
outline(panelBox, dim);
opacity(panelBox, 0.35);
hidden(panelBox);
text(stepTitle, (cx, 150), "");
display(stepTitle);
bold(stepTitle);
size(stepTitle, 26);
color(stepTitle, cyan);
text(ruleTag, (cx, 182), "");
size(ruleTag, 18);
color(ruleTag, dim);
text(watermark, (w - 96, h - 12), "Made with Manic");
size(watermark, 15);
color(watermark, dim);
hidden(watermark);
equation(work, (cx, cy + 20), `\int \frac{1}{(x - 1)(x + 2)}\,dx`, 34);
hidden(work);
equation(finalLine, (cx, h - 64), `\boxed{\,\frac{\log{\left(x - 1 \right)}}{3} - \frac{\log{\left(x + 2 \right)}}{3} + C\,}`, 34);
color(finalLine, lime);
hidden(finalLine);
show(head, 0.45);
show(sub, 0.45);
show(panelBox, 0.45);
show(watermark, 0.45);
show(work, 0.5);
wait(0.4);
par {
say(stepTitle, "1. u-Substitution", 0.25);
say(ruleTag, "Rule: URule", 0.25);
rewrite(work, `Let\ u = x - 1,\quad \frac{du}{dx} = 1`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "2. u-Substitution", 0.25);
say(ruleTag, "Rule: URule", 0.25);
rewrite(work, `Let\ u = u + \frac{3}{2},\quad \frac{du}{d_u} = 1`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "3. Rewrite", 0.25);
say(ruleTag, "Rule: RewriteRule", 0.25);
rewrite(work, `\int \frac{1}{3 u + \left(u - \frac{3}{2}\right)^{2} - \frac{9}{2}} \,d_u = \int - \frac{2}{3 \left(2 u + 3\right)} + \frac{2}{3 \left(2 u - 3\right)} \,d_u`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "4. Sum rule", 0.25);
say(ruleTag, "Rule: AddRule", 0.25);
rewrite(work, `\int (f+g)\,d_u = \int f\,d_u + \int g\,d_u`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "5. Constant factor", 0.25);
say(ruleTag, "Rule: ConstantTimesRule", 0.25);
rewrite(work, `\int - \frac{2}{3 \left(2 u + 3\right)} \,d_u = - \frac{2}{3} \int \frac{1}{2 u + 3} \,d_u`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "6. u-Substitution", 0.25);
say(ruleTag, "Rule: URule", 0.25);
rewrite(work, `Let\ u = 2 u + 3,\quad \frac{du}{d_u} = 2`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "7. Constant factor", 0.25);
say(ruleTag, "Rule: ConstantTimesRule", 0.25);
rewrite(work, `\int \frac{1}{2 u} \,d_u = \frac{1}{2} \int \frac{1}{u} \,d_u`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "8. Reciprocal", 0.25);
say(ruleTag, "Rule: ReciprocalRule", 0.25);
rewrite(work, `\int \frac{1}{u} \,d_u = \ln|u| + C = \log{\left(u \right)}`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "9. Simplify", 0.25);
say(ruleTag, "Rule: ConstantTimesRule", 0.25);
rewrite(work, `= \frac{\log{\left(u \right)}}{2} + C`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "10. Back substitute", 0.25);
say(ruleTag, "Rule: URule", 0.25);
rewrite(work, `= \frac{\log{\left(2 u + 3 \right)}}{2} + C`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "11. Simplify", 0.25);
say(ruleTag, "Rule: ConstantTimesRule", 0.25);
rewrite(work, `= - \frac{\log{\left(2 u + 3 \right)}}{3} + C`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "12. Constant factor", 0.25);
say(ruleTag, "Rule: ConstantTimesRule", 0.25);
rewrite(work, `\int \frac{2}{3 \left(2 u - 3\right)} \,d_u = \frac{2}{3} \int \frac{1}{2 u - 3} \,d_u`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "13. u-Substitution", 0.25);
say(ruleTag, "Rule: URule", 0.25);
rewrite(work, `Let\ u = 2 u - 3,\quad \frac{du}{d_u} = 2`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "14. Constant factor", 0.25);
say(ruleTag, "Rule: ConstantTimesRule", 0.25);
rewrite(work, `\int \frac{1}{2 u} \,d_u = \frac{1}{2} \int \frac{1}{u} \,d_u`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "15. Reciprocal", 0.25);
say(ruleTag, "Rule: ReciprocalRule", 0.25);
rewrite(work, `\int \frac{1}{u} \,d_u = \ln|u| + C = \log{\left(u \right)}`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "16. Simplify", 0.25);
say(ruleTag, "Rule: ConstantTimesRule", 0.25);
rewrite(work, `= \frac{\log{\left(u \right)}}{2} + C`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "17. Back substitute", 0.25);
say(ruleTag, "Rule: URule", 0.25);
rewrite(work, `= \frac{\log{\left(2 u - 3 \right)}}{2} + C`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "18. Simplify", 0.25);
say(ruleTag, "Rule: ConstantTimesRule", 0.25);
rewrite(work, `= \frac{\log{\left(2 u - 3 \right)}}{3} + C`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "19. Combine", 0.25);
say(ruleTag, "Rule: AddRule", 0.25);
rewrite(work, `= \frac{\log{\left(2 u - 3 \right)}}{3} - \frac{\log{\left(2 u + 3 \right)}}{3} + C`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "20. Back substitute", 0.25);
say(ruleTag, "Rule: URule", 0.25);
rewrite(work, `= \frac{\log{\left(2 u \right)}}{3} - \frac{\log{\left(2 u + 6 \right)}}{3} + C`, 0.7, smooth);
}
wait(0.3);
par {
say(stepTitle, "21. Back substitute", 0.25);
say(ruleTag, "Rule: URule", 0.25);
rewrite(work, `= \frac{\log{\left(2 x - 2 \right)}}{3} - \frac{\log{\left(2 x + 4 \right)}}{3} + C`, 0.7, smooth);
}
wait(0.3);
show(finalLine, 0.8);
pulse(finalLine, 0.8);
wait(1.0);
reactive-world
The foundation for creator-reactive stories: five named step blocks keep one curve,
equation, tangent, live slope, derivative plot and caption synchronized. Each step is
also exported as a marker for seeking and future multi-format publishing.
// ============================================================================
// reactive-world.manic — one world, named state transitions
// ----------------------------------------------------------------------------
// Each `step` changes several existing representations together. The equation,
// tangent, live slope, derivative curve, caption and camera share one continuous
// stage; anything a step does not mention simply persists.
// ============================================================================
title("Reactive World — The Derivative Lives on the Curve");
canvas("9:16");
template("terminal");
watermark(manicMark, (865, 165), "Made With Manic");
creator(me, "@anish2good name=Calculus_World tagline=One_idea_many_views yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social cta=See_the_change safe=reels");
socials(me);
text(kicker, (540, 130), "ONE WORLD · FIVE NAMED STEPS");
size(kicker, 23); color(kicker, dim); bold(kicker); hidden(kicker);
text(headline, (540, 215), "The derivative lives on the curve");
size(headline, 39); color(headline, fg); bold(headline); hidden(headline);
rect(stage, (540, 855), 930, 1050);
color(stage, panel); outline(stage, dim); opacity(stage, 0.72);
equation(work, (540, 390), `f(x)=0.35x^2`, 51);
color(work, cyan); hidden(work);
let ox = 540;
let oy = 1050;
let sx = 115;
let sy = 62;
axes(ax, (ox, oy), 410, 420, 1);
color(ax, dim); opacity(ax, 0.55); untraced(ax);
plot(f, (ox, oy), sx, sy, "0.35*x*x", (-3.5, 3.5));
color(f, cyan); stroke(f, 6); glow(f, 0.8); untraced(f);
deriv(df, f);
color(df, magenta); stroke(df, 5); dashed(df, 18, 11); untraced(df);
tangent(tan, f, -2.8, 235);
color(tan, gold); stroke(tan, 4); hidden(tan);
slope(rate, f, -2.8, (24, -30));
color(rate, gold); hidden(rate);
dot(vertex, (ox, oy), 10);
color(vertex, lime); glow(vertex, 1.8); hidden(vertex);
equation(curveLabel, (790, 650), `f(x)`, 31);
color(curveLabel, cyan); hidden(curveLabel);
equation(derivLabel, (790, 1320), `f'(x)`, 31);
color(derivLabel, magenta); hidden(derivLabel);
text(caption, (540, 1450), "Start with one curve and one moving question: how steep is it?");
size(caption, 26); color(caption, dim); wrap(caption, 820); hidden(caption);
// ---------------------------------------------------------------------------
// NAMED REACTIVE STEPS
// ---------------------------------------------------------------------------
step("question") {
show(kicker, 0.40);
show(headline, 0.50);
show(work, 0.55);
draw(ax, 0.85);
draw(f, 1.40);
show(curveLabel, 0.45);
show(caption, 0.45);
}
wait(0.75);
step("measure-slope") {
rewrite(work, `f'(x)=0.70x`, 0.90, smooth);
show(tan, 0.45);
show(rate, 0.45);
to(tan, x, 2.8, 3.20, smooth);
to(rate, x, 2.8, 3.20, smooth);
say(caption, "Move the point: the tangent and its slope update together.", 0.40);
}
wait(0.60);
step("find-the-flat-point") {
rewrite(work, `f'(0)=\textcolor{lime}{0}`, 0.85, smooth);
to(tan, x, 0, 1.80, smooth);
to(rate, x, 0, 1.80, smooth);
show(vertex, 0.40);
say(caption, "At the vertex the tangent becomes flat, so the derivative is zero.", 0.40);
}
wait(0.65);
step("see-the-derivative") {
rewrite(work, `f'(x)=\textcolor{magenta}{0.70x}`, 0.90, smooth);
fade(tan, 0.40);
fade(rate, 0.40);
draw(df, 1.70);
show(derivLabel, 0.45);
say(caption, "Collect every local slope and a second curve appears: the derivative.", 0.40);
}
wait(0.70);
step("takeaway") {
rewrite(work, `\text{slope of }f=\textcolor{magenta}{f'}`, 0.95, smooth);
pulse(f, 0.70);
pulse(df, 0.70);
say(caption, "One idea, synchronized across symbols, geometry and motion.", 0.40);
}
wait(1.80);
sine_wave
axes + plot, a curve traced on, then vectors.
// The Sine Wave — a first taste of the manic math kit.
// manic examples/sine_wave.manic
// manic examples/sine_wave.manic --still 2.6 --scale 1.5 --crt
title("The Sine Wave");
canvas(1280, 720);
// --- cast: the world at t = 0 ---
// a coordinate frame centred on the stage
axes(ax, (640, 380), 520, 240);
text(xlab, (1180, 410), "x"); color(xlab, dim); size(xlab, 22);
text(ylab, (665, 152), "y"); color(ylab, dim); size(ylab, 22);
// the curve: visible but not yet drawn, so we can trace it on
plot(wave, (640, 380), 78, 120, sin, 6.6);
untraced(wave);
// a vector to point at, revealed later
vector(v1, (640, 380), (122, 108));
hidden(v1);
// headline + caption
text(head, (640, 118), "y = sin(x)");
display(head); color(head, cyan); size(head, 40); hidden(head);
text(cap, (640, 662), ""); color(cap, dim); size(cap, 22);
// --- script: beats, top to bottom ---
show(head, 0.5);
say(cap, "a coordinate frame on the void");
draw(wave, 1.7);
say(cap, "y = sin(x), traced on");
wait(0.6);
section("Vectors");
say(cap, "a vector from the origin");
par {
show(v1, 0.4);
pulse(v1);
}
wait(1.2);
creator-trigonometry-one-angle
One reactive angle drives three synchronized unit circles, degree/radian readouts, projection measurements, graph markers and progressive sine/cosine/tangent traces. Tangent is split into truthful branches, so its marker disappears at π/2 instead of drawing a false line through the asymptote.
// One live angle drives three unit-circle constructions, graph traces,
// markers, projections, and numerical readouts. Tangent is deliberately split
// into branches so the animation never lies across an asymptote.
title("One Angle Creates Three Functions");
canvas("9:16");
template("black");
creator(me,"@anish2good name=Manic_Geometry tagline=Relationships_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Animate_the_relationship safe=clean");
socials(me);
watermark(mark,(w*0.18,h*0.065),"Made With Manic");
endcard(me,"title=Make_Math_Move cta=8gwifi.org/manic");
let u=(w+h-abs(w-h))/1080;
text(kicker,(cx,h*0.095),"MANIC · LIVE TRIGONOMETRY");
text(head,(cx,h*0.145),"One angle. Three different measurements.");
equation(identity,(cx,h*0.205),
`\textcolor{cyan}{y=\sin\alpha}\qquad
\textcolor{magenta}{y=\cos\alpha}\qquad
\textcolor{lime}{y=\tan\alpha}`,22*u);
counter(degrees,(w*0.33,h*0.255),20,0,"α = ","°");
counter(radians,(w*0.69,h*0.255),0.35,2,"α = "," rad");
text(caption,(cx,h*0.835),"Rotate one radius. Watch three graphs respond.");
size(kicker,19*u); bold(kicker); color(kicker,cyan);
size(head,29*u); bold(head); wrap(head,w*0.84);
size(degrees,20*u); bold(degrees); color(degrees,gold);
size(radians,20*u); bold(radians); color(radians,gold);
size(caption,20*u); bold(caption); color(caption,dim); wrap(caption,w*0.80);
// One authored angle is the only changing input in the entire construction.
// Two rotations are available: the first teaches, the second becomes a
// continuous performance pass. Circle formulas are naturally periodic; graph
// markers and public readouts wrap back into [0,2pi).
parameter(alpha,(cx,h*0.885),0.35,0.02,12.54,"ANGLE α",2);
color(alpha,cyan);
bind(alpha,degrees,value,"(p-6.283185*floor(p/6.283185))*180/pi");
bind(alpha,radians,value,"p-6.283185*floor(p/6.283185)");
// Shared portrait geometry.
let circleX=w*0.265;
let graphX=w*0.535;
let radius=w*0.092;
let graphSX=w*0.050;
let graphSY=radius;
let graphRight=graphX+graphSX*6.283;
let row1=h*0.385;
let row2=h*0.565;
let row3=h*0.745;
// ---------------------------------------------------------------------------
// SIN — the rotating point's height.
// ---------------------------------------------------------------------------
circle(sinCircle,(circleX,row1),radius);
outlined(sinCircle); outline(sinCircle,fg);
line(sinCX,(circleX-radius*1.22,row1),(circleX+radius*1.22,row1));
line(sinCY,(circleX,row1-radius*1.22),(circleX,row1+radius*1.22));
point(sinO,(circleX,row1));
point(sinP,(circleX+radius*cos(0.35),row1-radius*sin(0.35)));
point(sinHeight,(circleX,row1-radius*sin(0.35)));
segment(sinRay,sinO,sinP);
segment(sinGuide,sinP,sinHeight);
anglemark(sinAngle,sinHeight,sinO,sinP);
bind(alpha,sinP,x,"286.2+99.4*cos(p)");
bind(alpha,sinP,y,"739.2-99.4*sin(p)");
bind(alpha,sinHeight,y,"739.2-99.4*sin(p)");
line(sinGX,(graphX,row1),(graphRight,row1));
line(sinGY,(graphX,row1-graphSY*1.28),(graphX,row1+graphSY*1.28));
plot(sinCurve,(graphX,row1),graphSX,graphSY,"sin(x)",(0,6.283));
point(sinDot,(graphX+graphSX*0.35,row1-graphSY*sin(0.35)));
point(sinFoot,(graphX+graphSX*0.35,row1));
segment(sinDrop,sinDot,sinFoot);
bind(alpha,sinCurve,trace,"p/6.283185");
bind(alpha,sinDot,x,"577.8+54*(p-6.283185*floor(p/6.283185))");
bind(alpha,sinDot,y,"739.2-99.4*sin(p)");
bind(alpha,sinFoot,x,"577.8+54*(p-6.283185*floor(p/6.283185))");
text(sinLabel,(graphX+graphSX*2.2,row1-radius*1.45),"y = sin(α) · height");
color(sinCircle,fg); color(sinCX,dim); color(sinCY,dim);
color(sinRay,cyan); color(sinGuide,cyan); color(sinAngle,cyan);
color(sinP,cyan); color(sinHeight,cyan); color(sinO,fg);
color(sinGX,dim); color(sinGY,dim); color(sinCurve,cyan);
color(sinDot,fg); color(sinFoot,fg); color(sinDrop,cyan); color(sinLabel,cyan);
dashed(sinGuide,8,6); dashed(sinDrop,8,6);
stroke(sinCircle,3); stroke(sinRay,4); stroke(sinGuide,3); stroke(sinCurve,5);
size(sinLabel,18*u); bold(sinLabel);
// ---------------------------------------------------------------------------
// COS — the rotating point's horizontal reach.
// ---------------------------------------------------------------------------
circle(cosCircle,(circleX,row2),radius);
outlined(cosCircle); outline(cosCircle,fg);
line(cosCX,(circleX-radius*1.22,row2),(circleX+radius*1.22,row2));
line(cosCY,(circleX,row2-radius*1.22),(circleX,row2+radius*1.22));
point(cosO,(circleX,row2));
point(cosP,(circleX+radius*cos(0.35),row2-radius*sin(0.35)));
point(cosReach,(circleX+radius*cos(0.35),row2));
segment(cosRay,cosO,cosP);
segment(cosGuide,cosP,cosReach);
bind(alpha,cosP,x,"286.2+99.4*cos(p)");
bind(alpha,cosP,y,"1084.8-99.4*sin(p)");
bind(alpha,cosReach,x,"286.2+99.4*cos(p)");
line(cosGX,(graphX,row2),(graphRight,row2));
line(cosGY,(graphX,row2-graphSY*1.28),(graphX,row2+graphSY*1.28));
plot(cosCurve,(graphX,row2),graphSX,graphSY,"cos(x)",(0,6.283));
point(cosDot,(graphX+graphSX*0.35,row2-graphSY*cos(0.35)));
point(cosFoot,(graphX+graphSX*0.35,row2));
segment(cosDrop,cosDot,cosFoot);
bind(alpha,cosCurve,trace,"p/6.283185");
bind(alpha,cosDot,x,"577.8+54*(p-6.283185*floor(p/6.283185))");
bind(alpha,cosDot,y,"1084.8-99.4*cos(p)");
bind(alpha,cosFoot,x,"577.8+54*(p-6.283185*floor(p/6.283185))");
text(cosLabel,(graphX+graphSX*2.2,row2-radius*1.45),"y = cos(α) · reach");
color(cosCircle,fg); color(cosCX,dim); color(cosCY,dim);
color(cosRay,magenta); color(cosGuide,magenta);
color(cosP,magenta); color(cosReach,magenta); color(cosO,fg);
color(cosGX,dim); color(cosGY,dim); color(cosCurve,magenta);
color(cosDot,fg); color(cosFoot,fg); color(cosDrop,magenta); color(cosLabel,magenta);
dashed(cosGuide,8,6); dashed(cosDrop,8,6);
stroke(cosCircle,3); stroke(cosRay,4); stroke(cosGuide,3); stroke(cosCurve,5);
size(cosLabel,18*u); bold(cosLabel);
// ---------------------------------------------------------------------------
// TAN — where the radius meets the tangent x=1. Its graph uses three separate
// branches; no path is ever drawn through pi/2 or 3pi/2.
// ---------------------------------------------------------------------------
circle(tanCircle,(circleX,row3),radius);
outlined(tanCircle); outline(tanCircle,fg);
line(tanCX,(circleX-radius*1.22,row3),(circleX+radius*1.38,row3));
line(tanCY,(circleX,row3-radius*1.22),(circleX,row3+radius*1.22));
line(tangentWall,(circleX+radius,row3-radius*1.28),(circleX+radius,row3+radius*1.28));
point(tanO,(circleX,row3));
point(tanP,(circleX+radius*cos(0.35),row3-radius*sin(0.35)));
point(tanHit,(circleX+radius,row3-radius*tan(0.35)));
point(tanBase,(circleX+radius,row3));
segment(tanRay,tanO,tanHit);
segment(tanLength,tanBase,tanHit);
bind(alpha,tanP,x,"286.2+99.4*cos(p)");
bind(alpha,tanP,y,"1430.4-99.4*sin(p)");
bind(alpha,tanHit,y,"1430.4-99.4*min(1.28,max(-1.28,tan(p)))");
bind(alpha,tanHit,opacity,"min(1,abs(cos(p))*7)");
bind(alpha,tanRay,opacity,"min(1,abs(cos(p))*7)");
bind(alpha,tanLength,opacity,"min(1,abs(cos(p))*7)");
line(tanGX,(graphX,row3),(graphRight,row3));
line(tanGY,(graphX,row3-graphSY*1.28),(graphX,row3+graphSY*1.28));
// Stop each sampled branch exactly at the visible y-range. The mathematical
// branch continues toward infinity; the empty gaps remain truthful.
plot(tanA,(graphX,row3),graphSX,graphSY,"tan(x)",(0.02,0.907));
plot(tanB,(graphX,row3),graphSX,graphSY,"tan(x)",(2.235,4.049));
plot(tanC,(graphX,row3),graphSX,graphSY,"tan(x)",(5.377,6.26));
point(tanDot,(graphX+graphSX*0.35,row3-graphSY*tan(0.35)));
point(tanFoot,(graphX+graphSX*0.35,row3));
segment(tanDrop,tanDot,tanFoot);
bind(alpha,tanA,trace,"min(1,p/0.907)");
bind(alpha,tanB,trace,"min(1,max(0,(p-2.235)/1.814))");
bind(alpha,tanC,trace,"min(1,max(0,(p-5.377)/0.883))");
bind(alpha,tanDot,x,"577.8+54*(p-6.283185*floor(p/6.283185))");
bind(alpha,tanDot,y,"1430.4-99.4*min(1.28,max(-1.28,tan(p)))");
bind(alpha,tanDot,opacity,"min(1,abs(cos(p))*7)");
bind(alpha,tanFoot,x,"577.8+54*(p-6.283185*floor(p/6.283185))");
bind(alpha,tanDrop,opacity,"min(1,abs(cos(p))*7)");
text(tanLabel,(graphX+graphSX*2.2,row3-radius*1.45),"y = tan(α) · tangent length");
color(tanCircle,fg); color(tanCX,dim); color(tanCY,dim); color(tangentWall,lime);
color(tanRay,lime); color(tanLength,lime); color(tanP,lime);
color(tanHit,fg); color(tanBase,fg); color(tanO,fg);
color(tanGX,dim); color(tanGY,dim);
color(tanA,lime); color(tanB,lime); color(tanC,lime);
color(tanDot,fg); color(tanFoot,fg); color(tanDrop,lime); color(tanLabel,lime);
dashed(tanDrop,8,6);
stroke(tanCircle,3); stroke(tanRay,4); stroke(tanLength,5);
stroke(tanA,5); stroke(tanB,5); stroke(tanC,5);
size(tanLabel,18*u); bold(tanLabel);
tag(sinCircle,trigWorld); tag(sinCX,trigWorld); tag(sinCY,trigWorld);
tag(sinO,trigWorld); tag(sinP,trigWorld); tag(sinHeight,trigWorld);
tag(sinRay,trigWorld); tag(sinGuide,trigWorld); tag(sinAngle,trigWorld);
tag(sinGX,trigWorld); tag(sinGY,trigWorld); tag(sinCurve,trigWorld);
tag(sinDot,trigWorld); tag(sinFoot,trigWorld); tag(sinDrop,trigWorld); tag(sinLabel,trigWorld);
tag(cosCircle,trigWorld); tag(cosCX,trigWorld); tag(cosCY,trigWorld);
tag(cosO,trigWorld); tag(cosP,trigWorld); tag(cosReach,trigWorld);
tag(cosRay,trigWorld); tag(cosGuide,trigWorld);
tag(cosGX,trigWorld); tag(cosGY,trigWorld); tag(cosCurve,trigWorld);
tag(cosDot,trigWorld); tag(cosFoot,trigWorld); tag(cosDrop,trigWorld); tag(cosLabel,trigWorld);
tag(tanCircle,trigWorld); tag(tanCX,trigWorld); tag(tanCY,trigWorld); tag(tangentWall,trigWorld);
tag(tanO,trigWorld); tag(tanP,trigWorld); tag(tanHit,trigWorld); tag(tanBase,trigWorld);
tag(tanRay,trigWorld); tag(tanLength,trigWorld);
tag(tanGX,trigWorld); tag(tanGY,trigWorld); tag(tanA,trigWorld); tag(tanB,trigWorld); tag(tanC,trigWorld);
tag(tanDot,trigWorld); tag(tanFoot,trigWorld); tag(tanDrop,trigWorld); tag(tanLabel,trigWorld);
hidden(kicker); hidden(head); hidden(identity); hidden(degrees); hidden(radians);
hidden(caption); hidden(alpha); hidden(trigWorld);
untraced(sinCurve); untraced(cosCurve); untraced(tanA); untraced(tanB); untraced(tanC);
step("one angle enters three worlds") {
par {
show(kicker,0.30); show(head,0.45); show(identity,0.45);
show(degrees,0.30); show(radians,0.30); show(caption,0.40);
show(alpha,0.35);
}
stagger(0.04) {
show(trigWorld,0.30);
}
}
wait(0.60);
step("height becomes sine") {
par {
to(alpha,value,1.05,2.20,smooth);
say(head,"Sine records the rotating point's height.",0.45,smooth);
say(caption,"The cyan projection and cyan graph are the same number.",0.45,smooth);
pulse(sinGuide,0.70); pulse(sinCurve,0.70);
}
}
wait(0.55);
step("reach becomes cosine") {
par {
to(alpha,value,2.35,2.30,smooth);
say(head,"Cosine records its horizontal reach.",0.45,smooth);
say(caption,"Past 90°, the point moves left—so cosine becomes negative.",0.48,smooth);
pulse(cosGuide,0.70); pulse(cosCurve,0.70);
}
}
wait(0.55);
step("tangent breaks at the asymptote") {
par {
to(alpha,value,1.56,1.70,smooth);
say(head,"Tangent races toward infinity.",0.40,smooth);
say(caption,"At π/2 the radius is parallel to the tangent line. There is no intersection.",0.50,smooth);
pulse(tangentWall,0.70);
}
cue(whoosh);
par {
to(alpha,value,1.82,1.10,smooth);
say(head,"It does not cross. A new branch begins.",0.40,smooth);
say(caption,"Manic leaves the discontinuity empty instead of drawing a false connection.",0.50,smooth);
}
}
wait(0.65);
step("one rotation reveals every sign") {
par {
to(alpha,value,3.75,3.10,smooth);
say(head,"One angle controls every sign and every value.",0.45,smooth);
say(caption,"At 215°: sine and cosine are negative, while tangent is positive.",0.50,smooth);
}
}
wait(0.70);
step("complete the cycle") {
par {
to(alpha,value,6.25,4.00,smooth);
say(head,"Three graphs. One rotating radius.",0.45,smooth);
say(caption,"The unit circle is not a mnemonic—it is the machine generating all three functions.",0.52,smooth);
}
}
step("keep the mathematics running") {
par {
// No hold between cycles: the angle keeps moving while the camera turns
// three stacked diagrams into one continuous cinematic inspection.
to(alpha,value,12.53,7.60,linear);
fade(alpha,0.30);
say(head,"Now let the mathematics keep running.",0.45,smooth);
say(caption,"The camera can move because every relationship remains alive.",0.48,smooth);
seq {
par {
cam((cx,row1),1.00,smooth);
zoom(1.28,1.00,smooth);
}
wait(1.00);
cam((cx,row2),1.00,smooth);
wait(1.00);
par {
// Aim slightly above the tangent row so its graph, explanation, and
// creator identity share the frame instead of leaving a dead lower half.
cam((cx,h*0.50),1.00,smooth);
zoom(1.24,1.00,smooth);
}
wait(1.40);
par {
cam((cx,cy),1.20,smooth);
zoom(1.00,1.20,smooth);
}
}
}
}
step("creator call to action") {
par {
fade(kicker,0.30); fade(head,0.30); fade(identity,0.30);
fade(degrees,0.30); fade(radians,0.30); fade(caption,0.30);
fade(alpha,0.30); fade(trigWorld,0.45); fade(me.footer,0.30);
show(me.endcard,0.60);
}
}
wait(2.00);
function_graph
Plot an expression straight from a formula string.
// Function Graphs — plot ANY formula, not just a named curve. manic's answer to
// Manim's FunctionGraph(lambda t: ...): pass a formula string in x (alias t) and
// plot() samples it. This reproduces Manim's ExampleFunctionGraph — two
// Fourier-style packets and a domain-clipped, lifted copy of the second.
//
// manic examples/function_graph.manic
// manic examples/function_graph.manic --record out --fps 60
title("Function Graphs");
canvas(1280, 720);
text(head, (640, 92), "plot any formula — y = f(x)");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (640, 656), ""); color(cap, dim); size(cap, 22);
// a faint frame to read the curves against (unit = 70 px)
plane(pl, (640, 384), 620, 300, 70);
hidden(pl.grid); untraced(pl.x); untraced(pl.y);
// a cosine packet: cos t + 1/2 cos 7t + 1/7 cos 14t, over x in [-7, 7]
plot(cosf, (640, 384), 70, 70, "cos(x) + 0.5*cos(7*x) + (1/7)*cos(14*x)", 7);
color(cosf, magenta); untraced(cosf);
// the sine version of the same packet
plot(sinf, (640, 384), 70, 70, "sin(x) + 0.5*sin(7*x) + (1/7)*sin(14*x)", 7);
color(sinf, cyan); untraced(sinf);
// same formula, clipped to x in [-4, 4] and lifted one unit (centre y - 70)
plot(sinf2, (640, 314), 70, 70, "sin(x) + 0.5*sin(7*x) + (1/7)*sin(14*x)", 4);
color(sinf2, lime); untraced(sinf2);
// --- reveal ---
show(head, 0.5);
section("The plane");
say(cap, "a grid to read against — arrows on the axes");
show(pl.grid, 0.6);
par { draw(pl.x, 0.5); draw(pl.y, 0.5); }
wait(0.3);
section("A cosine packet");
say(cap, "y = cos t + 1/2 cos 7t + 1/7 cos 14t");
draw(cosf, 1.3);
wait(0.6);
section("A sine packet");
say(cap, "same shape, sin for cos");
draw(sinf, 1.3);
wait(0.6);
section("Clip the domain");
say(cap, "same formula, but only x in [-4, 4], lifted one unit");
draw(sinf2, 1.1);
par { pulse(cosf); pulse(sinf); pulse(sinf2); }
wait(1.4);
area_under_curve
Riemann rectangles sweeping to the integral.
// Area Under a Curve — a Riemann sum sweeping n = 5, 10, 20, 40 to show the
// rectangles converging to the exact integral of x^2 on [0, 2.5] = 125/24.
//
// This is the FIRST example to use manic's loop layer: `let` variables,
// arithmetic in arguments, a `for` range loop, and id interpolation (`s5{i}`).
// The four bar-sets differ only in n / prefix / colour — a future `def` macro
// would collapse them to one call; loops already do the per-bar work.
//
// manic examples/area_under_curve.manic
// manic examples/area_under_curve.manic --record out --fps 60
title("Area Under a Curve");
canvas(1280, 720);
// --- parameters (edit freely) ---
let ox = 360; let oy = 590; // origin, in screen px
let ux = 200; let uy = 52; // px per unit on each axis
let a = 0; let b = 2.5; // integrate x^2 over [a, b]
text(head, (640, 96), "a Riemann sum becomes an integral");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (640, 656), ""); color(cap, dim); size(cap, 24);
// axes
arrow(xax, (ox - 40, oy), (920, oy)); color(xax, dim); untraced(xax);
arrow(yax, (ox, oy + 20), (ox, 250)); color(yax, dim); untraced(yax);
text(t1, (ox + 1*ux, oy + 24), "1"); color(t1, dim); size(t1, 18);
text(t2, (ox + 2*ux, oy + 24), "2"); color(t2, dim); size(t2, 18);
text(tb, (ox + b*ux, oy + 24), "2.5"); color(tb, dim); size(tb, 18);
// the curve y = x^2 over [0, 2.5]
plot(curve, (ox, oy), ux, uy, "x*x", (a, b)); color(curve, cyan); z(curve, 3); untraced(curve);
text(clab, (ox + b*ux + 30, oy - b*b*uy), "y = x^2"); color(clab, cyan); size(clab, 22); hidden(clab);
// --- midpoint rectangles, one loop per count ---
let n = 5; let dx = (b - a) / n;
for i in 0..n {
let mid = a + (i + 0.5) * dx; let h = mid * mid;
rect(s5{i}, (ox + mid*ux, oy - h*uy/2), dx*ux, h*uy);
filled(s5{i}); color(s5{i}, magenta); opacity(s5{i}, 0.4); tag(s5{i}, r5);
}
let n = 10; let dx = (b - a) / n;
for i in 0..n {
let mid = a + (i + 0.5) * dx; let h = mid * mid;
rect(s10{i}, (ox + mid*ux, oy - h*uy/2), dx*ux, h*uy);
filled(s10{i}); color(s10{i}, magenta); opacity(s10{i}, 0.4); tag(s10{i}, r10);
}
let n = 20; let dx = (b - a) / n;
for i in 0..n {
let mid = a + (i + 0.5) * dx; let h = mid * mid;
rect(s20{i}, (ox + mid*ux, oy - h*uy/2), dx*ux, h*uy);
filled(s20{i}); color(s20{i}, magenta); opacity(s20{i}, 0.4); tag(s20{i}, r20);
}
let n = 40; let dx = (b - a) / n;
for i in 0..n {
let mid = a + (i + 0.5) * dx; let h = mid * mid;
rect(s40{i}, (ox + mid*ux, oy - h*uy/2), dx*ux, h*uy);
filled(s40{i}); color(s40{i}, magenta); opacity(s40{i}, 0.4); tag(s40{i}, r40);
}
hidden(r5); hidden(r10); hidden(r20); hidden(r40);
// --- script ---
show(head, 0.5);
say(cap, "the shaded area under y = x^2 from 0 to 2.5");
par { draw(xax, 0.5); draw(yax, 0.5); }
draw(curve, 0.9);
show(clab, 0.3);
wait(0.4);
section("Rectangles");
say(cap, "n = 5 rectangles -> area ~ 5.16");
show(r5, 0.6);
wait(0.7);
fade(r5, 0.3);
say(cap, "n = 10 -> area ~ 5.20");
show(r10, 0.5);
wait(0.6);
fade(r10, 0.3);
say(cap, "n = 20 -> area ~ 5.20");
show(r20, 0.5);
wait(0.6);
fade(r20, 0.3);
say(cap, "n = 40 -> area ~ 5.21 (hugging the curve)");
show(r40, 0.5);
wait(0.8);
section("The integral");
say(cap, "as n grows without bound, the sum IS the integral");
fade(r40, 0.4);
text(ans, (640, 300), "exact area = 125/24 = 5.208"); display(ans); color(ans, lime); size(ans, 30); hidden(ans);
show(ans, 0.5);
pulse(ans);
wait(1.6);
riemann_rainbow
Coloured Riemann rectangles revealed one by one.
// Riemann Rainbow — the area under y = sin(x) on [0, pi], sliced into rectangles
// that each get their own neon hue and rise into place one by one, left to right.
//
// A showcase for the loop layer: one `for` builds all the bars (each `hue`d by
// its index), and a `stagger` block sweeps them in. Exact area = 2.
//
// manic examples/riemann_rainbow.manic
// manic examples/riemann_rainbow.manic --record out --fps 60
title("Riemann Rainbow");
canvas(1280, 720);
// --- parameters ---
let ox = 190; let oy = 560; // origin (screen px)
let ux = 300; let uy = 340; // px per unit
let a = 0; let b = pi; // y = sin(x) over [0, pi]
let n = 28; let dx = (b - a) / n;
text(head, (640, 96), "area under y = sin(x), one slice at a time");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (640, 640), ""); color(cap, dim); size(cap, 24);
// axes
arrow(xax, (ox - 40, oy), (1180, oy)); color(xax, dim); untraced(xax);
arrow(yax, (ox, oy + 20), (ox, 180)); color(yax, dim); untraced(yax);
text(l0, (ox, oy + 26), "0"); color(l0, dim); size(l0, 18);
text(lp, (ox + b*ux, oy + 26), "pi"); color(lp, dim); size(lp, 18);
// the curve
plot(curve, (ox, oy), ux, uy, "sin(x)", (a, b)); color(curve, fg); z(curve, 5); untraced(curve);
// --- one rainbow bar per slice (midpoint heights) ---
for i in 0..n {
let mid = a + (i + 0.5) * dx;
let h = sin(mid);
rect(bar{i}, (ox + mid*ux, oy - h*uy/2), dx*ux, h*uy);
filled(bar{i});
hue(bar{i}, 360 * i / n); // each slice its own colour
opacity(bar{i}, 0.9);
tag(bar{i}, bars);
}
hidden(bars);
// --- script ---
show(head, 0.5);
say(cap, "y = sin(x) from 0 to pi");
par { draw(xax, 0.5); draw(yax, 0.5); }
draw(curve, 1.0);
wait(0.3);
section("Slice by slice");
say(cap, "28 rectangles rise in, left to right");
stagger(0.05) {
for i in 0..n { show(bar{i}, 0.35); }
}
wait(0.6);
section("The area");
say(cap, "together they fill the area under the curve = 2");
par { pulse(curve); }
wait(1.6);
riemann_readout
Running sums shown as a live computed number.
// Riemann + Live Total — the midpoint area under y = x^2 on [0, 2.5] is
// COMPUTED in-language with a `sum(...)` reduction, and a `counter` readout
// tweens from 0 up to that total while the bars fill in. The number you see
// counting is the reduction's value.
//
// Showcases reductions + animated numeric readouts (`counter` + `to(_, value)`).
//
// manic examples/riemann_readout.manic
// manic examples/riemann_readout.manic --record out --fps 60
title("Riemann + Live Total");
canvas(1280, 720);
let ox = 300; let oy = 560;
let ux = 190; let uy = 52;
let a = 0; let b = 2.5; let n = 40; let dx = (b - a) / n;
// the midpoint Riemann sum, computed at build time
let area = sum(i in 0..n : (a + (i + 0.5)*dx)^2 * dx);
text(head, (640, 90), "area under y = x^2, summed as the bars fill");
display(head); color(head, cyan); size(head, 26); hidden(head);
counter(total, (950, 210), 0, 3, "area = ", "");
display(total); color(total, lime); size(total, 36); hidden(total);
text(exact, (950, 260), "exact 125/24 = 5.208"); color(exact, dim); size(exact, 20); hidden(exact);
// axes
arrow(xax, (ox - 40, oy), (900, oy)); color(xax, dim); untraced(xax);
arrow(yax, (ox, oy + 20), (ox, 210)); color(yax, dim); untraced(yax);
text(t1, (ox + 1*ux, oy + 24), "1"); color(t1, dim); size(t1, 18);
text(t2, (ox + 2*ux, oy + 24), "2"); color(t2, dim); size(t2, 18);
// curve
plot(curve, (ox, oy), ux, uy, "x*x", (a, b)); color(curve, cyan); z(curve, 4); untraced(curve);
// midpoint rectangles
for i in 0..n {
let mid = a + (i + 0.5) * dx;
let h = mid * mid;
rect(bar{i}, (ox + mid*ux, oy - h*uy/2), dx*ux, h*uy);
filled(bar{i}); color(bar{i}, magenta); opacity(bar{i}, 0.45); tag(bar{i}, bars);
}
hidden(bars);
// --- script ---
show(head, 0.5);
par { draw(xax, 0.5); draw(yax, 0.5); }
draw(curve, 0.9);
show(total, 0.3);
show(exact, 0.3);
wait(0.3);
// bars sweep in while the total counts up to the reduction's value
par {
stagger(0.03) { for i in 0..n { show(bar{i}, 0.25); } }
to(total, value, area, 1.6, linear);
}
wait(1.4);
pulse(total);
wait(1.0);
tangent
The tangent line to a curve, sliding along it — its tilt is read from the function itself, so it’s always the true slope (flat at the peaks).
title("The Tangent Line — a curve's slope, made visible");
canvas("16:9");
text(hdr, (cx, 60), "The slope of sin x, everywhere at once");
size(hdr, 30); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// the curve — plot remembers its function, so tangent can ask it questions.
// center the domain 0..6.3 across the canvas (midpoint x=3.15 sits at cx)
plot(f, (cx - 440, cy + 40), 140, 120, "sin(x)", (0, 6.3));
color(f, cyan); stroke(f, 3); untraced(f);
// a tangent that touches f where x = 0.5 (line + contact dot, one call)
tangent(tan, f, 0.5, 260);
color(tan, gold); untraced(tan);
text(cap, (cx, h - 60), "watch the tilt follow the curve");
size(cap, 20); color(cap, dim); display(cap); hidden(cap);
// ---- timeline ----
show(hdr, 0.5);
draw(f, 1.4);
show(tan, 0.6);
show(cap, 0.4);
wait(0.4);
// slide the touch point along the whole curve — the slope is derived live
to(tan, x, 5.8, 5);
wait(0.3);
to(tan, x, 1.6, 2.5); // back toward the first peak, where the slope is 0
analysis
Ask one curve everything at once — tangent, a live slope number, the normal, the area sweeping open beneath it, and the integral climbing to its true value.
title("Reading a curve: slope, tangent, normal, area");
canvas("16:9");
text(hdr, (cx, 58), "Asking a curve questions");
size(hdr, 30); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// one curve — everything below asks IT, by name
plot(f, (cx - 440, cy + 30), 140, 110, "sin(x)", (0, 6.3));
color(f, cyan); stroke(f, 3); untraced(f);
// the area under it — starts collapsed at x=0.6, sweeps open
area(reg, f, 0.6, 0.6);
color(reg, lime);
// a live integral readout that climbs as the area sweeps
integral(area_val, f, 0.6, 0.6, (cx + 340, cy - 210));
color(area_val, lime);
// tangent + its live slope number, both riding the same point
tangent(tan, f, 0.6, 240);
color(tan, gold);
slope(sl, f, 0.6);
color(sl, gold);
// the normal, perpendicular at the same place
normal(nrm, f, 0.6, 150);
color(nrm, magenta); hidden(nrm);
// ---- timeline ----
show(hdr, 0.5);
draw(f, 1.4);
show(tan, 0.5);
show(sl, 0.4);
wait(0.4);
// sweep the area open while the tangent + slope slide together and the
// integral readout climbs
par {
to(reg, x, 5.7, 4);
to(area_val, x, 5.7, 4);
to(tan, x, 5.7, 4);
to(sl, x, 5.7, 4);
}
wait(0.4);
// reveal the normal and glide everything back to the first peak (slope 0)
show(nrm, 0.4);
par {
to(tan, x, 1.571, 2.5);
to(sl, x, 1.571, 2.5);
to(nrm, x, 1.571, 2.5);
}
newton
Newton’s method, drawn as a zig-zag: from a first guess, slide down each tangent to the axis, back up to the curve, and watch the guesses walk to the root.
title("Newton's method: chasing a root");
canvas("16:9");
text(hdr, (cx, 56), "Newton's method finds where x squared = 2");
size(hdr, 28); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// a reference frame so the x-axis (where the tangents land) is visible
axes(ax, (cx - 250, cy + 170), 540, 360, 1);
color(ax, dim); untraced(ax);
// f(x) = x^2 - 2, whose positive root is sqrt(2)
plot(f, (cx - 250, cy + 170), 180, 70, "x*x - 2", (-0.4, 3));
color(f, cyan); stroke(f, 3); untraced(f);
// the true zero-crossing, marked
roots(zero, f);
color(zero, lime); hidden(zero);
// the Newton zig-zag from a first guess of x = 3
newton(walk, f, 3);
color(walk, gold); stroke(walk, 3); untraced(walk);
text(cap, (cx, h - 60), "each tangent slides the guess closer");
size(cap, 20); color(cap, dim); display(cap); hidden(cap);
// ---- timeline ----
show(hdr, 0.5);
draw(ax, 0.8);
draw(f, 1.4);
wait(0.3);
show(cap, 0.4);
// walk the iterates in to the root
draw(walk, 3.5);
wait(0.3);
// reveal the exact root it converged on
show(zero, 0.5);
pulse(zero, 0.6);
inverse-derivatives
Why a function and its inverse have reciprocal slopes: e^x and ln x mirrored
across y = x, with the slopes at matching points multiplying to 1.
title("Inverse-function derivatives — why the pairs mirror");
canvas("16:9");
// uniform scale (sx == sy) so a function and its inverse reflect correctly
let ox = cx - 470;
let oy = cy + 110;
let s = 72;
text(hdr, (cx, 52), "A function and its inverse: mirror curves, reciprocal slopes");
size(hdr, 26); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// ---------------- the mirror plane ----------------
axes(ax, (ox, oy), 470, 300, 1);
color(ax, dim); untraced(ax);
// y = x, the mirror line
line(diag, (ox - 2.6*s, oy + 2.6*s), (ox + 3.8*s, oy - 3.8*s));
color(diag, dim); untraced(diag);
text(diaglab, (ox + 3.4*s, oy - 3.4*s - 22), "y = x");
size(diaglab, 16); color(diaglab, dim); display(diaglab); hidden(diaglab);
// e^x and its inverse ln x — reflections of each other
plot(ef, (ox, oy), s, s, "exp(x)", (-2.6, 1.45));
color(ef, cyan); stroke(ef, 3); untraced(ef);
text(eflab, (ox + 1.45*s + 8, oy - 4.1*s), "e^x");
size(eflab, 18); color(eflab, cyan); display(eflab); hidden(eflab);
plot(lf, (ox, oy), s, s, "ln(x)", (0.075, 4.3));
color(lf, magenta); stroke(lf, 3); untraced(lf);
text(lflab, (ox + 4.2*s, oy - 1.45*s), "ln x");
size(lflab, 18); color(lflab, magenta); display(lflab); hidden(lflab);
// tangent + live slope at a point on e^x, and at its mirror point on ln x
tangent(te, ef, 0.5, 150);
color(te, cyan); hidden(te);
slope(se, ef, 0.5);
color(se, cyan); hidden(se);
tangent(tl, lf, 1.65, 150);
color(tl, magenta); hidden(tl);
slope(sl, lf, 1.65);
color(sl, magenta); hidden(sl);
text(rec, (cx + 340, cy + 60), "slope x slope = 1");
size(rec, 22); color(rec, lime); bold(rec); display(rec); hidden(rec);
text(rec2, (cx + 340, cy + 96), "(1.65) x (0.61)");
size(rec2, 18); color(rec2, dim); display(rec2); hidden(rec2);
// ---------------- the four identity pairs ----------------
text(t1, (cx + 340, cy - 190), "(e^x)' = e^x (ln x)' = 1/x");
size(t1, 17); color(t1, fg); display(t1); hidden(t1);
text(t2, (cx + 340, cy - 150), "(sin x)' = sqrt(1-sin^2 x) (arcsin x)' = 1/sqrt(1-x^2)");
size(t2, 15); color(t2, fg); display(t2); hidden(t2);
text(t3, (cx + 340, cy - 118), "(cos x)' = -sqrt(1-cos^2 x) (arccos x)' = -1/sqrt(1-x^2)");
size(t3, 15); color(t3, fg); display(t3); hidden(t3);
text(t4, (cx + 340, cy - 86), "(tan x)' = 1+tan^2 x (arctan x)' = 1/(1+x^2)");
size(t4, 15); color(t4, fg); display(t4); hidden(t4);
// ================= timeline =================
show(hdr, 0.6);
draw(ax, 0.7);
draw(diag, 0.5);
show(diaglab, 0.3);
par {
draw(ef, 1.4);
draw(lf, 1.4);
}
par {
show(eflab, 0.4);
show(lflab, 0.4);
}
wait(0.3);
// the reciprocal-slope check
par {
show(te, 0.4);
show(se, 0.4);
}
par {
show(tl, 0.4);
show(sl, 0.4);
}
show(rec, 0.5);
show(rec2, 0.4);
flash(rec, lime);
wait(0.4);
// the four pairs it generates
stagger(0.4) {
show(t1, 0.4);
show(t2, 0.4);
show(t3, 0.4);
show(t4, 0.4);
}
derivative-of-ln-x
A visual proof of (ln x)' = 1/x: one continuous 3D coordinate plane turns over,
x/y exchange screen roles, and unchanged algebra terms retain their identity.
// ============================================================================
// derivative-of-ln-x.manic — inverse-function derivatives on a turning plane
// ----------------------------------------------------------------------------
// The curve is first read as y=e^x. A true 3-D camera turn exchanges the screen
// roles of world x/y, so the same relationship reads x=e^y. While the graph is
// hidden for the first plane turn, morph3 exchanges its coordinates; the same
// geometry can then remain visible through the return turn into y=ln(x).
// ============================================================================
title("Why the derivative of ln x is 1/x");
canvas("16:9");
template("plain");
// Landscape proof: the upper-right stays clear while the algebra owns the
// bottom and the vertical axis label owns the top-centre.
watermark(manicMark, (1135, 30), "Made With Manic");
// Orthographic overhead camera: the continuous pole-safe orbit frame keeps the
// whole turn smooth, and roll3 controls which world direction is screen-up.
camera3((0, 0.5, 12), (0, 0.5, 0), 8.5, orthographic);
// ---------------------------------------------------------------------------
// ONE WORLD PLANE · axes, grid, exponential, tangent and exact slope triangle
// ---------------------------------------------------------------------------
grid3(grid, (0, 0, 0), 4, 1);
color(grid, dim); opacity(grid, 0.30); tag(grid, world);
arrow3(xaxis, (-4.05, 0, 0), (4.05, 0, 0));
arrow3(yaxis, (0, -3.4, 0), (0, 4.05, 0));
color(xaxis, fg); color(yaxis, fg); thick(xaxis, 0.012); thick(yaxis, 0.012);
tag(xaxis, world); tag(yaxis, world);
for i in -4..5 {
if i != 0 {
line3(xtick{i}, (i, -0.07, 0), (i, 0.07, 0));
line3(ytick{i}, (-0.07, i, 0), (0.07, i, 0));
color(xtick{i}, dim); color(ytick{i}, dim);
tag(xtick{i}, world); tag(ytick{i}, world);
}
}
equation(xlab, (0,0), `x`, 31); pin3(xlab, (4.10, -0.18, 0)); tag(xlab, world);
equation(ylab, (0,0), `y`, 31); pin3(ylab, (-0.18, 4.10, 0)); tag(ylab, world);
hidden(world);
// Source curve γ(t)=(t,e^t), and its inverse parameterisation γ⁻¹(t)=(e^t,t).
curve3(curve, "t", "exp(t)", "0", (-3.6, 1.42));
curve3(curveTarget, "exp(t)", "t", "0", (-3.6, 1.42));
color(curve, lime); color(curveTarget, lime); thick(curve, 0.024); thick(curveTarget, 0.024);
untraced(curve); hidden(curveTarget); morph3(curve, curveTarget);
equation(expName, (0,0), `y=e^x`, 31); color(expName, lime); pin3(expName, (-1.45, 2.20, 0)); hidden(expName);
equation(swappedName, (0,0), `x=e^y`, 31); color(swappedName, lime); pin3(swappedName, (2.65, -1.20, 0)); hidden(swappedName);
equation(logName, (0,0), `y=\ln(x)`, 31); color(logName, lime); pin3(logName, (2.65, 0.90, 0)); hidden(logName);
// At t=0.6: P=(0.6,e^0.6), and e^0.6 ≈ 1.822. The tangent reaches y=0
// exactly one unit of run before P, so its slope triangle is rise/run = y/1.
curve3(tangent, "-0.65+2*t", "-0.455529+3.644238*t", "0", (0,1));
curve3(tangentTarget, "-0.455529+3.644238*t", "-0.65+2*t", "0", (0,1));
color(tangent, magenta); color(tangentTarget, magenta); thick(tangent, 0.018); thick(tangentTarget, 0.018);
untraced(tangent); hidden(tangentTarget); morph3(tangent, tangentTarget);
curve3(runSide, "-0.4+t", "0", "0", (0,1));
curve3(runTarget, "0", "-0.4+t", "0", (0,1));
color(runSide, gold); color(runTarget, gold); thick(runSide, 0.024); thick(runTarget, 0.024);
untraced(runSide); hidden(runTarget); morph3(runSide, runTarget);
curve3(riseSide, "0.6", "1.822119*t", "0", (0,1));
curve3(riseTarget, "1.822119*t", "0.6", "0", (0,1));
color(riseSide, cyan); color(riseTarget, cyan); thick(riseSide, 0.024); thick(riseTarget, 0.024);
untraced(riseSide); hidden(riseTarget); morph3(riseSide, riseTarget);
point3(contact, (0.6, 1.822119, 0), 0.075); color(contact, fg); hidden(contact);
point3(contactTarget, (1.822119, 0.6, 0), 0.075); color(contactTarget, fg); hidden(contactTarget);
equation(riseY, (0,0), `y`, 28); color(riseY, cyan); pin3(riseY, (0.82, 0.91, 0)); hidden(riseY);
equation(runOne, (0,0), `1`, 28); color(runOne, gold); pin3(runOne, (0.10, -0.22, 0)); hidden(runOne);
equation(targetX, (0,0), `x`, 28); color(targetX, cyan); pin3(targetX, (0.91, 0.82, 0)); hidden(targetX);
equation(targetOne, (0,0), `1`, 28); color(targetOne, gold); pin3(targetOne, (-0.22, 0.10, 0)); hidden(targetOne);
// The diagonal is the hinge / mirror relation during the plane turns.
for i in -9..10 {
line3(diag{i}, (i*0.42, i*0.42, 0.02), (i*0.42+0.24, i*0.42+0.24, 0.02));
color(diag{i}, magenta); thick(diag{i}, 0.018); untraced(diag{i}); tag(diag{i}, mirror);
}
equation(diagName, (0,0), `y=x`, 27); color(diagName, magenta); pin3(diagName, (2.55, 2.55, 0.02)); hidden(diagName);
// Screen-space algebra. Separate equations keep the camera/geometry generic.
equation(slopeExp, (cx, 655), `\textcolor{magenta}{\mathrm{slope}}=\textcolor{cyan}{e^x}`, 40); hidden(slopeExp);
equation(slopeY, (cx, 655), `\textcolor{magenta}{\mathrm{slope}}=\textcolor{cyan}{y}`, 40); hidden(slopeY);
equation(slopeFracY, (cx, 655), `\textcolor{magenta}{\mathrm{slope}}=\frac{\textcolor{cyan}{y}}{\textcolor{gold}{1}}=\frac{\textcolor{cyan}{\mathrm{rise}}}{\textcolor{gold}{\mathrm{run}}}`, 40); hidden(slopeFracY);
// Persistent algebra pieces. The unchanged prefix/equality never disappear:
// only the term being justified moves, fades, or arrives. This is the same
// object-continuity principle used by matching-transform systems, expressed
// with ordinary Manic entities rather than replacing a whole equation image.
equation(slopeWord, (500, 655), `\textcolor{magenta}{\mathrm{slope}}`, 40); hidden(slopeWord); tag(slopeWord, inverseAlgebra);
equation(mainEquals, (575, 655), `=`, 40); hidden(mainEquals); tag(mainEquals, inverseAlgebra);
equation(heightTerm, (630, 655), `\textcolor{cyan}{\mathrm{height}}`, 40); hidden(heightTerm); tag(heightTerm, inverseAlgebra);
equation(xTerm, (630, 655), `\textcolor{cyan}{x}`, 40); hidden(xTerm); tag(xTerm, inverseAlgebra);
equation(oneTerm, (630, 674), `\textcolor{gold}{1}`, 36); hidden(oneTerm); tag(oneTerm, inverseAlgebra);
line(termBar, (616, 657), (644, 657)); color(termBar, fg); stroke(termBar, 2); untraced(termBar); tag(termBar, inverseAlgebra);
equation(ratioEquals, (685, 655), `=`, 40); hidden(ratioEquals); tag(ratioEquals, inverseAlgebra);
equation(ratioTerm, (780, 655), `\frac{\textcolor{cyan}{\mathrm{rise}}}{\textcolor{gold}{\mathrm{run}}}`, 40); hidden(ratioTerm); tag(ratioTerm, inverseAlgebra);
equation(derivativeTerm, (475, 655), `\frac{d}{dx}\ln(x)`, 41); hidden(derivativeTerm); tag(derivativeTerm, inverseAlgebra);
// Split glyphs let generic `cycle` retain their identity through xy → yx.
equation(planeX, (550, 640), `x`, 54); hidden(planeX); tag(planeX, planeLabel);
equation(planeY, (575, 640), `y`, 54); hidden(planeY); tag(planeY, planeLabel);
equation(planeWord, (685, 640), `\mathrm{plane}`, 54); hidden(planeWord); tag(planeWord, planeLabel);
// ---------------------------------------------------------------------------
// TIMELINE · 75.49 seconds including manic's final one-second tail
// ---------------------------------------------------------------------------
show(world, 1.20);
draw(curve, 2.40);
show(expName, 0.60);
wait(2.50);
show(slopeExp, 0.70);
wait(2.20);
par { draw(riseSide, 1.20); show(riseY, 0.45); show(contact, 0.30); }
par { fade(slopeExp, 0.55); show(slopeY, 0.55); }
wait(2.00);
par { draw(tangent, 1.20); draw(runSide, 1.00); show(runOne, 0.40); }
par { fade(slopeY, 0.60); show(slopeFracY, 0.60); }
wait(7.90);
// First turn: remove the measurement clutter, reveal the mirror hinge, and
// move from the top of the plane to its underside. Below + -90° roll makes
// screen-horizontal follow world y and screen-vertical follow world x.
par {
fade(curve, 0.70); fade(expName, 0.70); fade(tangent, 0.70);
fade(runSide, 0.70); fade(riseSide, 0.70); fade(contact, 0.70);
fade(riseY, 0.70); fade(runOne, 0.70); fade(slopeFracY, 0.70);
}
par { draw(mirror, 0.70); show(diagName, 0.50); show(planeLabel, 0.50); }
par {
orbit3(-90, -90, 12, 7.00, smooth);
roll3(90, 7.00, smooth);
// The graph is hidden here, so exchange its coordinates before it returns.
to(curve, morph, 1, 7.00, smooth);
to(tangent, morph, 1, 7.00, smooth);
to(runSide, morph, 1, 7.00, smooth);
to(riseSide, morph, 1, 7.00, smooth);
seq { wait(2.80); cycle(planeX, planeY, 1.40, 70, smooth); }
}
// Same world geometry, now read with exchanged screen axes.
par {
show(curve, 0.70); show(tangent, 0.70); show(runSide, 0.70); show(riseSide, 0.70);
show(contactTarget, 0.50); show(swappedName, 0.55); show(targetX, 0.45); show(targetOne, 0.45);
fade(diagName, 0.45); erase(mirror, 0.70); fade(planeLabel, 0.45);
}
par { show(slopeWord, 0.65); show(mainEquals, 0.65); show(heightTerm, 0.65); }
wait(3.00);
par { fade(heightTerm, 0.60); show(xTerm, 0.60); }
wait(3.00);
par { show(ratioEquals, 0.60); show(ratioTerm, 0.60); }
wait(3.00);
// x becomes x/1 in place: x lifts, the denominator arrives, and the rest of
// the displayed identity remains untouched.
par { move(xTerm, (630, 640), 0.60, smooth); show(oneTerm, 0.60); draw(termBar, 0.60); }
wait(6.20);
// Second turn: return the camera overhead while keeping the transformed curve
// and triangle visible. The same objects land as an ordinary y=ln(x) graph.
par {
draw(mirror, 0.60); show(diagName, 0.45); show(planeLabel, 0.45);
fade(slopeWord, 0.60); fade(mainEquals, 0.60); fade(xTerm, 0.60);
fade(oneTerm, 0.60); erase(termBar, 0.60); fade(ratioEquals, 0.60); fade(ratioTerm, 0.60);
}
par {
orbit3(-90, 90, 12, 7.00, smooth);
roll3(0, 7.00, smooth);
fade(swappedName, 3.00); show(logName, 3.00);
// Prepare the next algebra state invisibly while the plane is turning.
move(ratioTerm, (630, 655), 1.20, smooth);
move(oneTerm, (630, 640), 1.20, smooth);
move(xTerm, (630, 674), 1.20, smooth);
seq { wait(2.80); cycle(planeX, planeY, 1.40, -70, smooth); }
}
par {
fade(planeLabel, 0.45); fade(diagName, 0.45); erase(mirror, 0.70);
show(slopeWord, 0.65); show(mainEquals, 0.65); show(ratioTerm, 0.65);
}
wait(2.20);
// Only the right-hand term changes: rise/run becomes 1/x.
par { fade(ratioTerm, 0.60); show(oneTerm, 0.60); show(xTerm, 0.60); draw(termBar, 0.60); }
wait(3.00);
// The proven reciprocal stays; only the name on the left becomes d/dx ln(x).
par { fade(slopeWord, 0.70); show(derivativeTerm, 0.70); pulse(curve, 0.70); }
wait(9.89);
spline
Interpolation: one smooth curve drawn through a scattered set of points — it passes through every knot exactly.
title("A smooth curve through the data");
canvas("16:9");
text(hdr, (cx, 70), "Interpolation: one smooth curve through every point");
size(hdr, 26); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// scattered data points — the spline passes through all of them
spline(s, (300, 620), (520, 340), (760, 560), (1000, 300), (1240, 480), (1500, 260));
color(s, cyan); stroke(s, 4); untraced(s);
text(cap, (cx, h - 60), "Catmull-Rom — it hits each knot exactly");
size(cap, 20); color(cap, dim); display(cap); hidden(cap);
// ---- timeline ----
show(hdr, 0.5);
// pop the knots in first
show(s.knots, 0.6);
wait(0.3);
// then trace the smooth curve through them
draw(s, 2.2);
show(cap, 0.4);
sqrt-tanx
√(tan θ) by pure geometry — a 3B1B-style proof: on the unit circle, tan θ is the vertical tangent length; Euclid’s geometric mean (semicircle on diameter 1+a, altitude at 1) turns that length into √(tan θ). Then the curve itself, painted teal → violet → coral. Uses the expanded semantic palette.
// ============================================================================
// sqrt-tanx.manic — √(tan x) by PURE GEOMETRY (3B1B-style)
// ----------------------------------------------------------------------------
// THE PROBLEM: √(tan θ) looks like algebra. It is geometry.
//
// THE PROOF, in three pictures:
// 1. On the unit circle, tan θ IS the length of the vertical tangent segment.
// 2. √a is the geometric mean of 1 and a — Euclid: semicircle on diameter
// 1+a, erect a perpendicular at the "1" mark; its height is √a.
// 3. Set a = tan θ. The altitude IS √(tan θ). No algebra required.
//
// Then we sweep θ and watch the construction's height paint the curve —
// teal → violet → coral — using the expanded semantic palette.
//
// manic examples/sqrt-tanx.manic
// manic examples/sqrt-tanx.manic --still 22
// ============================================================================
title("√(tan x) — Pure Geometry");
canvas("16:9");
template("blank");
// ---- stage ----
circle(well, (cx, 400), 340);
filled(well);
gradient(well, panel, void, radial);
opacity(well, 0.55);
// ============================================================================
// LEFT — the unit circle, where tan θ is a length
// ============================================================================
let ox = 250;
let oy = 430;
let R = 145;
// a friendly angle: θ = 0.95 rad ≈ 54.4°, tan ≈ 1.40, √tan ≈ 1.18
let th = 0.95;
let Px = ox + R*cos(th);
let Py = oy - R*sin(th);
let Ty = oy - R*tan(th);
circle(O, (ox, oy), 7); outlined(O); color(O, fg); stroke(O, 1.5); hidden(O);
dot(A, (ox + R, oy), 5); color(A, dim); hidden(A); // (1, 0)
// contact points: open dashed rings (filled dots hide the circle + tangent)
circle(P, (Px, Py), 11);
outlined(P); color(P, mint); stroke(P, 2); dashed(P, 3.5, 2.8); untraced(P); hidden(P);
circle(T, (ox + R, Ty), 11);
outlined(T); color(T, coral); stroke(T, 2); dashed(T, 3.5, 2.8); untraced(T); hidden(T);
// ray as a line (not arrow) so the filled arrowhead doesn't cover P
line(ray, (ox, oy), (Px, Py));
color(ray, mint); stroke(ray, 2.5); untraced(ray); hidden(ray);
// the tangent SEGMENT — its length IS tan θ (in radii)
line(tanseg, (ox + R, oy), (ox + R, Ty));
color(tanseg, coral); stroke(tanseg, 5); untraced(tanseg); hidden(tanseg);
// angle mark — textbook style: a thin ARC, never a filled wedge (fills hide the diagram)
arc(wedge, (ox, oy), R*0.42, 0, -th*180/pi);
color(wedge, violet); stroke(wedge, 2.5); untraced(wedge); hidden(wedge);
circle(uc, (ox, oy), R);
outlined(uc); outline(uc, teal); stroke(uc, 3); untraced(uc); hidden(uc);
line(axX, (ox - R - 30, oy), (ox + R + 100, oy));
color(axX, dim); stroke(axX, 1.5); untraced(axX); hidden(axX);
line(axY, (ox, oy + R + 40), (ox, oy - R - 50));
color(axY, dim); stroke(axY, 1.5); untraced(axY); hidden(axY);
line(tline, (ox + R, oy + R + 50), (ox + R, oy - R - 90));
color(tline, dim); stroke(tline, 1.5); dashed(tline, 10, 8); untraced(tline); hidden(tline);
text(labP, (Px + 14, Py - 14), "P"); size(labP, 20); color(labP, mint); hidden(labP);
text(labO, (ox - 18, oy + 22), "O"); size(labO, 18); color(labO, dim); hidden(labO);
text(labA, (ox + R + 14, oy + 22), "1"); size(labA, 18); color(labA, dim); hidden(labA);
text(labT, (ox + R + 18, Ty), "tan θ"); size(labT, 20); color(labT, coral); hidden(labT);
text(labTh, (ox + 48, oy - 28), "θ"); size(labTh, 22); color(labTh, violet); hidden(labTh);
// ============================================================================
// RIGHT — Euclid's geometric mean: √a from 1 and a
// ============================================================================
let gx = 620;
let gy = 500;
let u = 110; // px per unit
let a = tan(th); // ≈ 1.40
let root = sqrt(a); // ≈ 1.18
let G0x = gx;
let G1x = gx + u; // the "1" mark
let G2x = gx + u*(1 + a); // the "1+a" mark
let mid = (G0x + G2x)*0.5;
let rad = u*(1 + a)*0.5;
dot(G0, (G0x, gy), 5); color(G0, dim); hidden(G0);
dot(G1, (G1x, gy), 6); color(G1, teal); glow(G1, 0.4); hidden(G1);
dot(G2, (G2x, gy), 5); color(G2, coral); hidden(G2);
line(base, (G0x, gy), (G2x, gy));
color(base, fg); stroke(base, 3); untraced(base); hidden(base);
// upper semicircle on diameter [0, 1+a]
// screen angles: 0° right, 90° down, 270° up — from left through UP: start 180, sweep +180
arc(semi, (mid, gy), rad, 180, 180);
color(semi, indigo); stroke(semi, 3); untraced(semi); hidden(semi);
// altitude at the "1" mark — height = √(1·a) = √(tan θ)
line(alt, (G1x, gy), (G1x, gy - root*u));
color(alt, violet); stroke(alt, 5); untraced(alt); hidden(alt);
dot(H, (G1x, gy - root*u), 8); color(H, violet); glow(H, 0.7); hidden(H);
text(lab0, (G0x, gy + 28), "0"); size(lab0, 18); color(lab0, dim); hidden(lab0);
text(lab1, (G1x, gy + 28), "1"); size(lab1, 18); color(lab1, teal); hidden(lab1);
text(labA2, (G2x, gy + 28), "1+tanθ"); size(labA2, 18); color(labA2, coral); hidden(labA2);
text(labH, (G1x + 16, gy - root*u - 8), "√(tan θ)"); size(labH, 22); color(labH, violet); hidden(labH);
// brace / connector: coral tan length → feeds the geometric mean
arrow(feed, (ox + R + 36, (oy + Ty)*0.5), (G2x - 10, gy - 40));
color(feed, coral); stroke(feed, 2); dashed(feed, 8, 6); untraced(feed); hidden(feed);
// ============================================================================
// BOTTOM — the curve itself, colored by the new palette
// ============================================================================
axes(ax, (920, 400), 280, 220);
hidden(ax);
plot(curve, (920, 400), 95, 85, "sqrt(tan(x))", (0.25, 1.35));
stroke(curve, 5); untraced(curve); hidden(curve);
gradient(curve, teal, violet, coral, 270);
// low x → small √tan (teal); high x → large (coral), via height = truth
// ============================================================================
// TYPOGRAPHY
// ============================================================================
caption(q, "what IS the square root of a tangent?", (cx, 58), 32, gold);
equation(law, (cx, 110), `\sqrt{\tan\theta}`, 44);
color(law, violet); hidden(law);
equation(mean, (900, 160), `\sqrt{a}=\sqrt{1\cdot a}`, 28);
color(mean, teal); hidden(mean);
text(cap, (cx, h - 36), ""); size(cap, 20); color(cap, dim); display(cap);
// ============================================================================
// SCRIPT
// ============================================================================
// 1 — HOOK
say(cap, "an expression that looks like algebra — but it is a length", 0.4);
wordpop(q, 0.16);
wait(0.3);
show(law, 0.5);
wait(0.6);
// 2 — UNIT CIRCLE
say(cap, "start with the unit circle and an angle θ", 0.4);
par { show(axX, 0.05); show(axY, 0.05); show(uc, 0.05); show(tline, 0.05); }
par {
draw(axX, 0.5); draw(axY, 0.5);
draw(uc, 1.0);
}
par { show(O, 0.3); show(labO, 0.3); }
draw(tline, 0.5);
show(A, 0.3); show(labA, 0.3);
wait(0.3);
// 3 — THE ANGLE AND THE RAY
say(cap, "draw the ray at angle θ — it meets the circle at P", 0.4);
show(wedge, 0.05); draw(wedge, 0.5); show(labTh, 0.3);
show(ray, 0.05); draw(ray, 0.7);
show(P, 0.05); draw(P, 0.35); show(labP, 0.3);
pulse(P);
wait(0.4);
// 4 — TAN θ IS A LENGTH
say(cap, "extend to the vertical tangent line — that segment's length IS tan θ", 0.4);
show(tanseg, 0.05); draw(tanseg, 0.9);
show(T, 0.05); draw(T, 0.35); show(labT, 0.3);
flash(tanseg, coral);
wait(0.7);
// 5 — GEOMETRIC MEAN
say(cap, "now forget the circle — Euclid: √a is the geometric mean of 1 and a", 0.4);
show(mean, 0.5);
par {
show(G0, 0.3); show(G1, 0.3); show(G2, 0.3);
show(lab0, 0.3); show(lab1, 0.3); show(labA2, 0.3);
show(base, 0.05);
}
draw(base, 0.6);
wait(0.3);
say(cap, "semicircle on diameter 1 + tan θ — then erect a perpendicular at 1", 0.4);
show(semi, 0.05); draw(semi, 1.2);
show(alt, 0.05); draw(alt, 0.9);
show(H, 0.3); show(labH, 0.3);
flash(alt, violet);
wait(0.5);
say(cap, "by Thales + similar triangles, that height equals √(1 · tan θ)", 0.4);
show(feed, 0.05); draw(feed, 0.7);
pulse(H); pulse(T);
wait(0.8);
// 6 — THE IDENTITY
recolor(cap, violet);
say(cap, "so the altitude IS √(tan θ) — the symbol was a length all along", 0.4);
rewrite(law, `\sqrt{\tan\theta}=h`, 0.8, smooth);
wait(0.9);
// 7 — THE CURVE
recolor(cap, dim);
say(cap, "every such height, for every θ, paints the curve √(tan x)", 0.4);
show(ax, 0.5);
show(curve, 0.05); draw(curve, 2.2);
wait(0.4);
say(cap, "teal near zero, coral as tan θ climbs toward the asymptote at π/2", 0.4);
flash(curve, violet);
wait(0.8);
// 8 — POINT
recolor(cap, teal);
say(cap, "√(tan θ) is not a formula to memorize — it is a geometric construction", 0.4);
wait(1.6);
integral-sqrt-tanx
Integrate √(tan P): substitute P = t + π/4, read A(t) from the circle x²+y²=2 and H(t) from the hyperbola x²−y²=2, then dA−dH recovers the integrand. Outline-only angle marks — no filled discs hiding the construction.
// ============================================================================
// integral-sqrt-tanx.manic — ∫ √(tan P) dP by PURE GEOMETRY
// Companion to sqrt-tanx.manic. Storyboard: the handwritten circle+hyperbola
// derivation (x²+y²=2 meets x²−y²=2; A(t)−H(t) recovers the integrand).
// ----------------------------------------------------------------------------
// THE PROBLEM: √(tan P) is a length. What is its antiderivative?
//
// THE GEOMETRY (one diagram, two curves of "radius" √2):
// · circle x² + y² = 2 → A(t) = arcsin(√2 sin t)
// · hyperbola x² − y² = 2 → H(t) = arcosh(√2 cos t)
// · substitution P = t + π/4 (so t ∈ (0, π/4) ⇒ P ∈ (π/4, π/2))
//
// THE TRUTH:
// dA − dH = √2 · √(tan P) · dP
// therefore
// ∫ √(tan P) dP = (1/√2)(A(t) − H(t)) + C
//
// Colours (expanded palette): teal = circle/A, coral = hyperbola/H,
// violet = the integral, mint = the moving point, gold = the hook.
//
// manic examples/integral-sqrt-tanx.manic
// manic examples/integral-sqrt-tanx.manic --still 20
// ============================================================================
title("∫ √(tan P) dP — Circle Meets Hyperbola");
canvas("16:9");
template("blank");
// ---- stage ----
circle(well, (cx, 400), 360);
filled(well);
gradient(well, panel, void, radial);
opacity(well, 0.5);
// ============================================================================
// THE DIAGRAM — circle x²+y²=2 and hyperbola x²−y²=2
// Screen: origin at (ox,oy), scale s px per unit. Radius √2 ≈ 1.414.
// ============================================================================
let ox = 340;
let oy = 400;
let s = 130; // px per math-unit
let R = s*sqrt(2); // circle radius = √2
// a friendly t inside (0, π/4): t = 0.40 rad ≈ 23°
let t = 0.40;
let P = t + pi/4; // ≈ 1.185 rad ≈ 68°
// point C on the circle: (√2 cos t, √2 sin t)
let Cx = ox + s*sqrt(2)*cos(t);
let Cy = oy - s*sqrt(2)*sin(t);
// foot on the x-axis and the "hyperbola x" = √2 cosh u with cosh u = √2 cos t?
// From notes: X = √2 cos t = cosh H, so the hyperbola vertex is at x=√2.
// Point on right branch with x = √2 cos t... wait cosh H = √2 cos t, so
// x_hyp = cosh H = √2 cos t (in math units where a=1 for cosh param form
// of x²−y²=1). Our hyperbola is x²−y²=2 = (x/√2)² − (y/√2)², so
// parametric: x = √2 cosh u, y = √2 sinh u. And cosh H = √2 cos t? Notes say
// cosh(H) = X = √2 cos t — that would require cosh H ≤ √2, H = arcosh(√2 cos t).
// For the diagram, mark the circle point and the matching hyperbola x.
// axes
line(axX, (ox - R - 40, oy), (ox + R + 120, oy));
color(axX, dim); stroke(axX, 1.5); untraced(axX); hidden(axX);
line(axY, (ox, oy + R + 50), (ox, oy - R - 60));
color(axY, dim); stroke(axY, 1.5); untraced(axY); hidden(axY);
// circle x² + y² = 2 — OUTLINED only. `circle` defaults to filled; a solid
// disc hides axes and labels underneath (engine-test: never fill constructions).
circle(circ, (ox, oy), R);
outlined(circ); outline(circ, teal); stroke(circ, 3.5); untraced(circ); hidden(circ);
// hyperbola x² − y² = 2 → a = b = √2 (in math units) → a_px = R
hyperbola(hyp, (ox, oy), R, R, 1.15);
color(hyp, coral); stroke(hyp, 3); untraced(hyp);
hidden(hyp.r); hidden(hyp.l);
// origin: thin open ring (tiny r + thick stroke reads as a filled blob)
circle(O, (ox, oy), 7);
outlined(O); color(O, fg); stroke(O, 1.5); hidden(O);
// contact point C: dashed open circle so circle + projections show through.
// Keep radius >> stroke so the interior stays empty (textbook marker).
circle(C, (Cx, Cy), 12);
outlined(C); color(C, mint); stroke(C, 2); dashed(C, 3.5, 2.8); untraced(C); hidden(C);
// radius toward C — a LINE, not an arrow: arrowheads are filled discs and
// hide the open marker at the contact point (engine-test visibility rule)
line(ray, (ox, oy), (Cx, Cy));
color(ray, mint); stroke(ray, 2.5); untraced(ray); hidden(ray);
// horizontal from C to y-axis? notes have Y = √2 sin t as height
line(ht, (ox, Cy), (Cx, Cy));
color(ht, violet); stroke(ht, 2); dashed(ht, 8, 6); untraced(ht); hidden(ht);
// vertical drop to x-axis
line(vt, (Cx, oy), (Cx, Cy));
color(vt, teal); stroke(vt, 2); dashed(vt, 8, 6); untraced(vt); hidden(vt);
// angle mark — textbook style: a thin ARC, never a filled disc (fills hide axes)
arc(wedge, (ox, oy), R*0.38, 0, -t*180/pi);
color(wedge, violet); stroke(wedge, 2.5); untraced(wedge); hidden(wedge);
// labels on the diagram
text(labO, (ox - 16, oy + 22), "O"); size(labO, 18); color(labO, dim); hidden(labO);
text(labC, (Cx + 22, Cy - 22), "C(t)"); size(labC, 20); color(labC, mint); hidden(labC);
text(labT, (ox + 42, oy - 22), "t"); size(labT, 22); color(labT, violet); hidden(labT);
text(labCirc, (ox - 20, oy - R - 24), "x² + y² = 2"); size(labCirc, 20); color(labCirc, teal); hidden(labCirc);
text(labHyp, (ox + R + 55, oy + 40), "x² − y² = 2"); size(labHyp, 20); color(labHyp, coral); hidden(labHyp);
// region hint: dashed circle (not a filled disc, not just a short arc)
circle(zone, (ox + R*0.42, oy - R*0.18), R*0.38);
outlined(zone); color(zone, indigo); stroke(zone, 2); dashed(zone, 7, 5);
untraced(zone); hidden(zone);
// ============================================================================
// RIGHT — the algebraic spine, rewritten live
// ============================================================================
equation(eqP, (920, 150), `P=t+\tfrac{\pi}{4}`, 32);
color(eqP, gold); hidden(eqP);
equation(eqA, (920, 230), `A(t)=\sin^{-1}(\sqrt{2}\sin t)`, 28);
color(eqA, teal); hidden(eqA);
equation(eqH, (920, 300), `H(t)=\cosh^{-1}(\sqrt{2}\cos t)`, 28);
color(eqH, coral); hidden(eqH);
equation(eqD, (920, 390), `dA-dH=\sqrt{2}\,\sqrt{\tan P}\,dP`, 28);
color(eqD, violet); hidden(eqD);
equation(eqI, (920, 480), `\int\sqrt{\tan P}\,dP=\tfrac{1}{\sqrt{2}}(A-H)+C`, 30);
color(eqI, mint); hidden(eqI);
// final closed form (boxed punchline)
equation(eqF, (920, 580), `\tfrac{1}{\sqrt{2}}\Big(\sin^{-1}(\sqrt{2}\sin(P-\tfrac{\pi}{4}))-\cosh^{-1}(\sqrt{2}\cos(P-\tfrac{\pi}{4}))\Big)+C`, 22);
color(eqF, gold); hidden(eqF);
// ============================================================================
// THE CURVE — √(tan x) on (π/4, ~1.4), then its antiderivative via ln-form of acosh
// ============================================================================
axes(ax, (920, 400), 300, 240);
hidden(ax);
// integrand (same spirit as the companion film)
plot(integrand, (920, 400), 90, 70, "sqrt(tan(x))", (0.85, 1.45));
stroke(integrand, 4); untraced(integrand); hidden(integrand);
gradient(integrand, teal, violet, coral, 270);
// antiderivative F(P) = (1/√2)(asin(√2 sin(P-π/4)) - acosh(√2 cos(P-π/4)))
// acosh(u) = ln(u + sqrt(u*u - 1))
plot(anti, (920, 400), 90, 55,
"(asin(sqrt(2)*sin(x-pi/4)) - ln(sqrt(2)*cos(x-pi/4) + sqrt(2*cos(x-pi/4)*cos(x-pi/4) - 1)))/sqrt(2)",
(0.85, 1.45));
stroke(anti, 4); untraced(anti); hidden(anti);
gradient(anti, mint, indigo, violet, 270);
// ============================================================================
// TYPOGRAPHY
// ============================================================================
caption(q, "how do you integrate a square root of a tangent?", (cx, 52), 30, gold);
equation(hook, (cx, 105), `\int\sqrt{\tan P}\,dP`, 42);
color(hook, violet); hidden(hook);
text(cap, (cx, h - 34), ""); size(cap, 20); color(cap, dim); display(cap);
// ============================================================================
// SCRIPT
// ============================================================================
// 1 — HOOK
say(cap, "√(tan P) is a length — we proved that. Now integrate it.", 0.4);
wordpop(q, 0.14);
wait(0.25);
show(hook, 0.5);
wait(0.6);
// 2 — THE SUBSTITUTION
say(cap, "one substitution opens the geometry: P = t + π/4", 0.4);
show(eqP, 0.5);
flash(eqP, gold);
wait(0.7);
// 3 — THE STAGE: two curves of the same "2"
say(cap, "draw two curves that share the constant 2 — a circle and a hyperbola", 0.4);
par { show(axX, 0.05); show(axY, 0.05); }
par { draw(axX, 0.4); draw(axY, 0.4); }
show(circ, 0.05); draw(circ, 1.0);
show(labCirc, 0.3); show(O, 0.3); show(labO, 0.3);
wait(0.3);
show(hyp.r, 0.05); show(hyp.l, 0.05);
draw(hyp.r, 1.0); draw(hyp.l, 1.0);
show(labHyp, 0.3);
wait(0.5);
say(cap, "circle: x² + y² = 2. hyperbola: x² − y² = 2. Same 2 — intentional.", 0.4);
pulse(circ); pulse(hyp.r);
wait(0.7);
// 4 — THE MOVING POINT
say(cap, "park a point C(t) = (√2 cos t, √2 sin t) on the circle — t < π/4", 0.4);
show(wedge, 0.05); draw(wedge, 0.45); show(labT, 0.3);
show(ray, 0.05); draw(ray, 0.7);
show(C, 0.05); draw(C, 0.4); show(labC, 0.3);
show(ht, 0.05); show(vt, 0.05);
draw(ht, 0.5); draw(vt, 0.5);
show(zone, 0.05); draw(zone, 0.6);
wait(0.6);
// 5 — A(t) FROM THE CIRCLE
say(cap, "from the y-coordinate: A(t) = arcsin(√2 sin t) — an angle on that circle", 0.4);
show(eqA, 0.5);
flash(eqA, teal);
wait(0.8);
// 6 — H(t) FROM THE HYPERBOLA
say(cap, "from the x-coordinate: H(t) = arcosh(√2 cos t) — a hyperbolic angle", 0.4);
show(eqH, 0.5);
flash(eqH, coral);
wait(0.8);
// 7 — THE DIFFERENTIAL IDENTITY
say(cap, "differentiate both — their difference IS the integrand", 0.4);
show(eqD, 0.6);
flash(eqD, violet);
wait(0.5);
say(cap, "dA − dH = √2 · √(tan P) · dP — geometry differentiated", 0.4);
wait(1.0);
// 8 — INTEGRATE
recolor(cap, mint);
say(cap, "integrate both sides — the √2 cancels into the prefactor", 0.4);
show(eqI, 0.6);
pulse(eqI);
wait(0.9);
// 9 — CLOSED FORM
recolor(cap, violet);
say(cap, "substitute t = P − π/4 back — the antiderivative in closed form", 0.4);
show(eqF, 0.7);
flash(eqF, violet);
wait(1.0);
// 10 — SEE IT: integrand then antiderivative
recolor(cap, dim);
say(cap, "the integrand √(tan x) climbing toward π/2", 0.4);
par {
fade(eqA, 0.35); fade(eqH, 0.35); fade(eqD, 0.35); fade(eqI, 0.35); fade(eqF, 0.35);
fade(eqP, 0.35);
}
show(ax, 0.4);
show(integrand, 0.05); draw(integrand, 1.8);
wait(0.5);
say(cap, "and its antiderivative — (1/√2)(A − H) — the circle minus the hyperbola", 0.4);
show(anti, 0.05); draw(anti, 2.0);
flash(anti, mint);
wait(0.8);
// 11 — POINT
recolor(cap, teal);
say(cap, "a hard integral, solved because two curves of the same 2 were waiting", 0.4);
wait(1.8);
trajectory
A phase portrait: three paths flowing under a differential system, each spiralling into the sink at the origin.
title("Phase portrait: flowing into a spiral sink");
canvas("16:9");
text(hdr, (cx, 60), "dx/dt = -y - 0.2x, dy/dt = x - 0.2y");
size(hdr, 26); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// a reference frame centered on the sink
axes(ax, (cx, cy + 20), 620, 360, 1);
color(ax, dim); untraced(ax);
// three trajectories from different starts, each spiraling into the origin
trajectory(t1, "-y - 0.2*x", "x - 0.2*y", (3.2, 0), (cx, cy + 20), 110, 520);
color(t1, cyan); stroke(t1, 3); untraced(t1);
trajectory(t2, "-y - 0.2*x", "x - 0.2*y", (0, 3.4), (cx, cy + 20), 110, 520);
color(t2, magenta); stroke(t2, 3); untraced(t2);
trajectory(t3, "-y - 0.2*x", "x - 0.2*y", (-3.0, -2.4), (cx, cy + 20), 110, 520);
color(t3, gold); stroke(t3, 3); untraced(t3);
// ---- timeline ----
show(hdr, 0.5);
draw(ax, 0.8);
par {
draw(t1, 3.5);
draw(t2, 3.5);
draw(t3, 3.5);
}
band
The area trapped between two curves, filled directly with band(top,bottom) while both
boundary plots remain visible.
title("The area between two curves");
canvas("16:9");
let ox = cx - 460;
let oy = cy + 40;
text(hdr, (cx, 58), "The region trapped between two curves");
size(hdr, 26); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
axes(ax, (ox, oy), 900, 260, 1);
color(ax, dim); untraced(ax);
// two curves over the same stretch of x
plot(upper, (ox, oy), 140, 90, "0.4*x + 1.4", (0, 6.3));
color(upper, cyan); stroke(upper, 3); untraced(upper);
plot(lower, (ox, oy), 140, 90, "sin(x)", (0, 6.3));
color(lower, magenta); stroke(lower, 3); untraced(lower);
// the band between them
band(gap, upper, lower);
color(gap, lime); hidden(gap);
text(cap, (cx, h - 56), "band(top, bottom) fills the space between");
size(cap, 20); color(cap, dim); display(cap); hidden(cap);
// ---- timeline ----
show(hdr, 0.6);
draw(ax, 0.7);
par {
draw(upper, 1.4);
draw(lower, 1.4);
}
wait(0.3);
to(gap, opacity, 0.3, 0.6); // fade in to translucent (so the curves read through)
show(cap, 0.4);
curve-features
Read a cubic by its geometry: maxima/minima where the slope is zero and an inflection
where the curve changes its bend (extrema, inflections).
title("Reading a curve's shape — maxima, minima, inflection");
canvas("16:9");
let ox = cx - 540; // math x=0 maps here (centres the domain 1..5 under cx)
let oy = cy;
text(hdr, (cx, 56), "Where the curve turns, and where it changes its bend");
size(hdr, 26); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
axes(ax, (ox, oy), 940, 240, 1);
color(ax, dim); untraced(ax);
// a cubic S-curve: one maximum, one minimum, one inflection
plot(f, (ox, oy), 180, 170, "0.4*((x-3)*(x-3)*(x-3) - 4*(x-3))", (1, 5));
color(f, cyan); stroke(f, 4); untraced(f);
// maxima & minima (slope = 0)
extrema(turn, f);
color(turn, gold);
// inflection (concavity flips, f'' = 0)
inflections(bend, f);
color(bend, magenta);
text(lg, (cx + 300, 150), "gold: max / min (slope 0)");
size(lg, 18); color(lg, gold); display(lg); hidden(lg);
text(lm, (cx + 300, 186), "pink: inflection (bend flips)");
size(lm, 18); color(lm, magenta); display(lm); hidden(lm);
// ---- timeline ----
show(hdr, 0.6);
draw(ax, 0.7);
draw(f, 1.6);
wait(0.3);
show(turn, 0.5);
show(lg, 0.4);
pulse(turn, 0.6);
wait(0.3);
show(bend, 0.5);
show(lm, 0.4);
pulse(bend, 0.6);
ftc
The Fundamental Theorem of Calculus: accumulate the area under a curve, differentiate that area function, and watch the original function return.
title("The Fundamental Theorem of Calculus");
canvas("16:9");
let ox = cx - 460; // math x=0 maps here
let oy = cy + 20;
text(hdr, (cx, 52), "Differentiate the area — and the function comes back");
size(hdr, 26); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
axes(ax, (ox, oy), 900, 200, 1);
color(ax, dim); untraced(ax);
// f(x) = cos x
plot(f, (ox, oy), 140, 150, "cos(x)", (0, 6.3));
color(f, cyan); stroke(f, 4); untraced(f);
// F(x) = area under f from 0 to x = sin x
accum(bigF, f);
color(bigF, lime); stroke(bigF, 3); untraced(bigF);
// F'(x): differentiate the area function — it lands right back on f
deriv(dF, bigF);
color(dF, gold); stroke(dF, 3); untraced(dF);
// legend
text(lf, (cx + 300, 150), "f(x) = cos x");
size(lf, 20); color(lf, cyan); display(lf); hidden(lf);
text(lF, (cx + 300, 190), "F(x) = area so far");
size(lF, 20); color(lF, lime); display(lF); hidden(lF);
text(ld, (cx + 300, 230), "F'(x) = f(x)");
size(ld, 20); color(ld, gold); bold(ld); display(ld); hidden(ld);
text(cap, (cx, h - 52), "the slope of the area-so-far IS the original curve");
size(cap, 20); color(cap, dim); display(cap); hidden(cap);
// ================= timeline =================
show(hdr, 0.6);
draw(ax, 0.7);
// 1. the function
draw(f, 1.3);
show(lf, 0.4);
wait(0.3);
// 2. its accumulated area, as a new curve (this is sin x)
draw(bigF, 1.6);
show(lF, 0.4);
wait(0.4);
// 3. differentiate that area function — it traces back onto f
draw(dF, 1.6);
show(ld, 0.4);
flash(dF, gold);
show(cap, 0.5);
limit
A removable discontinuity visualized as an approaching point, open circle and live finite limit at x→0.
title("A limit — the value a curve heads toward");
canvas("16:9");
let ox = cx - 120;
let oy = cy;
text(hdr, (cx, 54), "sin(x) / x is undefined at 0 — but it heads straight for 1");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
axes(ax, (ox, oy), 620, 300, 1);
color(ax, dim); untraced(ax);
// sin(x)/x — a removable hole at x = 0, where the limit is 1
plot(f, (ox, oy), 150, 220, "sin(x)/x", (-6.2, 6.2));
color(f, cyan); stroke(f, 4); untraced(f);
// the limit at x -> 0: open circle at (0, 1), guides, value, and an approaching dot
limit(lim, f, 0);
color(lim, gold);
text(cap, (cx, h - 54), "slide the point in — f(x) closes on the open circle");
size(cap, 20); color(cap, dim); display(cap); hidden(cap);
// ---- timeline ----
show(hdr, 0.6);
draw(ax, 0.7);
draw(f, 1.6);
wait(0.3);
show(lim, 0.5);
show(cap, 0.4);
wait(0.3);
// walk the point toward x = 0 from the left — it approaches the open circle
to(lim, x, 0, 3.5);
limit-infinity
A rational function settling onto its horizontal asymptote, with limit(...,inf)
detecting and marking the value at infinity.
title("A limit at infinity — limit(id, curve, inf)");
canvas("16:9");
let ox = 90; // x = 0 near the left
let oy = cy + 250; // y = 0 baseline
text(hdr, (cx, 52), "(5x^3 - 2x + 7) / (x^3 + 4x^2 + 3) -> 5 as x -> infinity");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
text(sub, (cx, 92), "same degree top and bottom: the ratio of leading coefficients, 5/1");
size(sub, 17); color(sub, dim); display(sub); hidden(sub);
// the x-axis (y = 0)
line(xaxis, (ox, oy), (ox + 1130, oy));
color(xaxis, dim); untraced(xaxis);
// the rational function, climbing toward its asymptote
plot(f, (ox, oy), 9, 88, "(5*x*x*x - 2*x + 7)/(x*x*x + 4*x*x + 3)", (0.5, 125));
color(f, cyan); stroke(f, 4); untraced(f);
// the limit at infinity: auto-detects the horizontal asymptote y = 5
limit(lim, f, inf);
color(lim, gold); hidden(lim);
// ---- timeline ----
show(hdr, 0.6);
show(sub, 0.4);
draw(xaxis, 0.5);
draw(f, 2.4);
show(lim, 0.7);
taylor
Taylor polynomials of increasing degree closing in on sin(x), one additional approximation at a time.
title("Taylor series — polynomials closing in on a curve");
canvas("16:9");
let ox = cx; // x=0 at centre
let oy = cy + 10;
text(hdr, (cx, 52), "Add one more term, and the polynomial hugs more of the curve");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
axes(ax, (ox, oy), 640, 260, 1);
color(ax, dim); untraced(ax);
// the target function
plot(f, (ox, oy), 95, 120, "sin(x)", (-6.2, 6.2));
color(f, cyan); stroke(f, 5); untraced(f);
// Taylor polynomials about x = 0, growing in degree
taylor(p1, f, 0, 1);
color(p1, dim); stroke(p1, 3); untraced(p1);
taylor(p3, f, 0, 3);
color(p3, gold); stroke(p3, 3); untraced(p3);
taylor(p5, f, 0, 5);
color(p5, magenta); stroke(p5, 3); untraced(p5);
taylor(p7, f, 0, 7);
color(p7, lime); stroke(p7, 3); untraced(p7);
text(l1, (cx + 300, 150), "n = 1 (a line)");
size(l1, 18); color(l1, dim); display(l1); hidden(l1);
text(l3, (cx + 300, 186), "n = 3");
size(l3, 18); color(l3, gold); display(l3); hidden(l3);
text(l5, (cx + 300, 222), "n = 5");
size(l5, 18); color(l5, magenta); display(l5); hidden(l5);
text(l7, (cx + 300, 258), "n = 7");
size(l7, 18); color(l7, lime); display(l7); hidden(l7);
// ---- timeline ----
show(hdr, 0.6);
draw(ax, 0.7);
draw(f, 1.6);
wait(0.3);
draw(p1, 0.8); show(l1, 0.3); wait(0.3);
draw(p3, 0.9); show(l3, 0.3); wait(0.3);
draw(p5, 1.0); show(l5, 0.3); wait(0.3);
draw(p7, 1.1); show(l7, 0.3);
Linear algebra & tables
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
linear-algebra — the whole subject in five ideas
A guided lesson, not a feature demo: five chapters that build linear algebra as
one connected story. Chapters 1–3 view the same matrix [[2,1],[1,2]]
through three lenses — a transformation of space (linmap), the determinant as
area scaling (determinant), and its eigenvectors / diagonalisation
(diagonalise) — then it moves on to solving Ax = b (linsolve → rref) and
projection / least-squares (project). Start here.
// ============================================================================
// linear-algebra.manic — Linear Algebra in five ideas (a lesson)
// ----------------------------------------------------------------------------
// A guided tour of the whole subject, not a feature demo. One stage, five
// chapters; chapters 1-3 view the SAME matrix A = [[2,1],[1,2]] through three
// lenses, so the ideas connect:
// 1. a matrix TRANSFORMS space (linmap)
// 2. the DETERMINANT is how area scales (determinant) -> det = 3
// 3. EIGENVECTORS only stretch (diagonalise) -> lambda = 3, 1
// 4. SOLVING A x = b (linsolve -> rref)
// 5. PROJECTION: the closest answer (project)
//
// Reveal recipe (works for lines, arrows, fills, and text alike, preserving
// each element's opacity): `untraced(tag)` at build time, `draw(tag)` to reveal,
// `fade(tag)` to clear before the next chapter. `tag` broadcasts to every entity
// carrying it (see the ergonomics in the manic guide).
// ============================================================================
title("Linear Algebra, in five ideas");
canvas("16:9");
let sx = cx;
let sy = cy + 44; // shared stage center
let u = 44;
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
// ---- build every chapter's visual up front, drawn-blank (untraced) ----
// A coordinate grid draws in with each chapter for consistent visual feedback.
// Chapters 1 & 3 bring their own (the deformed grid / the eigen-grid); chapters
// 2, 4, 5 get a plain reference grid that traces in at the start of the section.
plane(g2, (sx, sy), 176, 176, u); untraced(g2); // determinant — count the cells
plane(g4, (sx, sy), 240, 230, u); untraced(g4); // solving — read off (1, 3)
plane(g5, (sx, sy), 200, 200, u); untraced(g5); // projection
// 1. transformation of A = [[2,1],[1,2]]
linmap(lm, (sx, sy), u, 2, 1, 1, 2);
untraced(lm);
// 2. determinant of the same A (parallelogram is the bare id `dt`)
determinant(dt, (sx, sy), u, 2, 1, 1, 2);
untraced(dt); untraced(dt.unit); untraced(dt.val);
// 3. eigenvectors / diagonalisation of the same A
diagonalise(dg, (sx, sy), u, 2, 1, 1, 2);
untraced(dg);
// 4a. a 2x2 system as two lines (solution dot is the bare id `sys`)
linsolve(sys, (sx, sy), u, 2, 1, 1, 3, 5, 10);
untraced(sys.r1); untraced(sys.r2); hidden(sys); hidden(sys.val);
// 4b. the same system reduced by elimination
rref(rr, "2 1 5 ; 1 3 10", (sx, sy - 24), 120, 60);
untraced(rr.lbrack); untraced(rr.rbrack);
// 5. projection of a vector onto a line
project(pj, (sx, sy), u, (1, 3), (3, 1));
untraced(pj);
// ============================ the lesson ==================================
section("Transformations");
draw(lm, 1.2);
say(cap, "a matrix sends every point to a new one — and it does so LINEARLY", 0.5);
wait(1.8);
say(cap, "the grid stays parallel and evenly spaced; i-hat, j-hat land on its columns", 0.5);
wait(2.2);
fade(lm, 0.6);
wait(0.3);
say(cap, "", 0.2);
section("The determinant");
draw(g2, 0.7);
draw(dt.unit, 0.5); draw(dt, 0.7); draw(dt.val, 0.4);
say(cap, "how much does it stretch area? the unit square becomes this parallelogram", 0.5);
wait(2.0);
say(cap, "that area IS the determinant = 3 — three grid cells; a flip would be negative", 0.5);
wait(2.2);
fade(dt, 0.6); fade(dt.unit, 0.6); fade(dt.val, 0.6); fade(g2, 0.6);
wait(0.3);
say(cap, "", 0.2);
section("Eigenvectors");
draw(dg, 1.2);
say(cap, "most vectors change direction — but a few only STRETCH: the eigenvectors", 0.5);
wait(2.2);
say(cap, "in their basis A is pure scaling: A = P D P^-1, D = diag(3, 1)", 0.5);
wait(2.2);
fade(dg, 0.6);
wait(0.3);
say(cap, "", 0.2);
section("Solving A x = b");
draw(g4, 0.7);
draw(sys.r1, 0.7); draw(sys.r2, 0.7);
show(sys, 0.4); show(sys.val, 0.4);
say(cap, "a system of equations is a set of lines; the solution is where they meet", 0.5);
wait(2.2);
fade(sys, 0.5); fade(sys.r1, 0.5); fade(sys.r2, 0.5); fade(sys.val, 0.5); fade(g4, 0.5);
say(cap, "elimination finds it by reducing [ A | b ] to the identity beside the answer", 0.5);
draw(rr.lbrack, 0.4); draw(rr.rbrack, 0.4);
show(rr.s0, 0.4); show(rr.op0, 0.3);
wait(1.6);
par { show(rr.s4, 0.5); fade(rr.s0, 0.5); } show(rr.op4, 0.4); fade(rr.op0, 0.3);
wait(2.0);
fade(rr.s4, 0.5); fade(rr.op4, 0.4); fade(rr.lbrack, 0.4); fade(rr.rbrack, 0.4);
wait(0.3);
say(cap, "", 0.2);
section("Projection");
draw(g5, 0.7);
draw(pj, 1.0);
say(cap, "when no exact answer exists, take the CLOSEST point of the subspace", 0.5);
wait(2.0);
say(cap, "the error is perpendicular — that principle IS least-squares, the best fit", 0.5);
wait(2.4);
textbook-matrix-inverses
Two exam-style inverse proofs animated through structure rather than row reduction.
The first scales to an integer matrix, proves its columns are orthogonal, and gets
A⁻¹ = Aᵀ from AᵀA = I; the second computes A², observes A³ = I, and
concludes A⁻¹ = A². One persistent proof stage keeps every unchanged expression.
// ============================================================================
// textbook-matrix-inverses.manic
// Two textbook inverse proofs, animated as structural shortcuts:
// (i) orthogonal columns give A^T A = I;
// (ii) a finite-order matrix gives A^3 = I.
// ============================================================================
title("Two Matrix Inverse Proofs — See the Structure");
canvas("9:16");
template("paper");
watermark(manicMark,(220,175),"Made With Manic");
creator(me,"@anish2good name=Manic_Algebra tagline=Textbooks_in_motion yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social safe=reels");
socials(me);
text(kicker,(540,150),"MATRICES · INVERSES · STRUCTURE");
size(kicker,21); bold(kicker); color(kicker,cyan); hidden(kicker);
text(headline,(540,218),"Two proofs without row reduction");
size(headline,38); bold(headline); hidden(headline);
text(part,(540,300),"(i) TEST THE COLUMNS");
size(part,23); bold(part); color(part,gold); hidden(part);
rect(stage,(540,805),930,930);
color(stage,panel); outline(stage,dim); opacity(stage,0.72); hidden(stage);
equation(given,(540,500),
`A=\frac{1}{9}\begin{bmatrix}-8&1&4\\4&4&7\\1&-8&4\end{bmatrix}`,
40);
hidden(given);
equation(work,(540,800),`A^{-1}\stackrel{?}{=}A^{\mathsf T}`,48);
hidden(work);
text(reason,(540,1095),"The inverse appears when a product becomes the identity.");
size(reason,24); wrap(reason,820); color(reason,dim); hidden(reason);
line(rule,(170,1235),(910,1235));
color(rule,cyan); stroke(rule,2); dashed(rule,12,9); opacity(rule,0.32);
untraced(rule);
equation(summary,(540,1435),
`\boxed{A^{\mathsf T}A=I\Rightarrow A^{-1}=A^{\mathsf T}}`,
39);
hidden(summary);
text(takeaway,(540,1540),"LOOK FOR A SHORT PRODUCT THAT RETURNS TO I");
size(takeaway,18); bold(takeaway); color(takeaway,gold); hidden(takeaway);
step("read the two inverse claims") {
seq {
par {
show(kicker,0.35); show(headline,0.55); show(part,0.40);
show(stage,0.45); draw(rule,0.75); show(reason,0.40);
}
show(given,0.65);
show(work,0.55);
wait(0.85);
}
}
step("factor out the scale") {
seq {
say(reason,"Write A = M/9. Then ask whether M-transpose times M equals 81I.",0.50,smooth);
rewrite(work,
`M=\begin{bmatrix}-8&1&4\\4&4&7\\1&-8&4\end{bmatrix},\qquad A=\frac{1}{9}M`,
1.05,smooth);
wait(0.70);
}
}
step("recognize orthogonal columns") {
seq {
say(reason,"Every column has squared length 81, and distinct columns have dot product 0.",0.55,smooth);
rewrite(work,
`\begin{aligned}
\|c_1\|^2&=64+16+1=81\\
\|c_2\|^2&=1+16+64=81\\
\|c_3\|^2&=16+49+16=81
\end{aligned}`,
1.10,smooth);
wait(0.55);
rewrite(work,
`\begin{aligned}
c_1^{\mathsf T}c_2&=-8+16-8=0\\
c_1^{\mathsf T}c_3&=-32+28+4=0\\
c_2^{\mathsf T}c_3&=4+28-32=0
\end{aligned}`,
1.10,smooth);
wait(0.70);
}
}
step("the transpose is the inverse") {
seq {
say(reason,"Those six dot products are exactly the entries of M-transpose times M.",0.50,smooth);
rewrite(work,
`M^{\mathsf T}M=\begin{bmatrix}81&0&0\\0&81&0\\0&0&81\end{bmatrix}=81I`,
1.00,smooth);
wait(0.45);
rewrite(work,
`A^{\mathsf T}A=\frac{1}{81}M^{\mathsf T}M=I`,
0.90,smooth);
show(summary,0.55);
par { pulse(work,0.75); pulse(summary,0.75); }
wait(1.15);
}
}
step("begin the second matrix") {
seq {
par {
fade(summary,0.35);
say(part,"(ii) FIND A POWER THAT RETURNS TO I",0.45,smooth);
say(reason,"For the second matrix, compute powers instead of augmenting [A | I].",0.55,smooth);
rewrite(given,
`A=\begin{bmatrix}1&-1&1\\2&-1&0\\1&0&0\end{bmatrix}`,
0.95,smooth);
rewrite(work,`A^{-1}\stackrel{?}{=}A^2`,0.80,smooth);
}
wait(0.85);
}
}
step("square the matrix") {
seq {
say(reason,"First multiply A by itself. Keep this result for one more multiplication.",0.55,smooth);
rewrite(work,
`A^2=\begin{bmatrix}0&0&1\\0&-1&2\\1&-1&1\end{bmatrix}`,
1.00,smooth);
wait(0.80);
}
}
step("the third power is identity") {
seq {
say(reason,"Now A squared times A returns exactly to the identity matrix.",0.50,smooth);
rewrite(work,
`A^3=A^2A=\begin{bmatrix}1&0&0\\0&1&0\\0&0&1\end{bmatrix}=I`,
1.10,smooth);
wait(0.65);
rewrite(work,`AA^2=A^3=I`,0.80,smooth);
rewrite(summary,
`\boxed{AA^2=I\Rightarrow A^{-1}=A^2}`,
0.85,smooth);
show(summary,0.50);
par { pulse(work,0.75); pulse(summary,0.75); }
wait(1.10);
}
}
step("compare the two shortcuts") {
seq {
par {
show(takeaway,0.45);
say(part,"TWO STRUCTURES · ONE DEFINITION",0.45,smooth);
say(reason,"The inverse is whichever matrix multiplies A to give I.",0.50,smooth);
rewrite(given,
`\text{(i) orthogonality}\qquad\text{(ii) finite order}`,
0.90,smooth);
rewrite(work,
`\begin{aligned}
A^{\mathsf T}A=I&\Rightarrow A^{-1}=A^{\mathsf T}\\
AA^2=I&\Rightarrow A^{-1}=A^2
\end{aligned}`,
1.10,smooth);
fade(summary,0.45);
}
par { pulse(work,0.90); pulse(takeaway,0.90); }
wait(2.00);
}
}
linear-map
What a 2×2 matrix does to space: the grid deforms and the basis lands on its
columns (linmap), the unit square’s area becomes the determinant
(determinant), and two directions only stretch — the eigenvectors (eigen).
title("What a matrix does to space");
canvas("16:9");
let ox = cx - 60;
let oy = cy + 40;
text(hdr, (cx, 54), "the matrix [[2,1],[1,2]] — grid, determinant, eigenvectors");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
text(cap, (cx, h - 50), "");
size(cap, 22); color(cap, dim); display(cap);
// the plane deformed by the matrix (î, ĵ land on its columns)
linmap(lm, (ox, oy), 46, 2, 1, 1, 2, 2);
// the unit square's image — area = determinant
determinant(dt, (ox, oy), 46, 2, 1, 1, 2);
hidden(dt); hidden(dt.unit); hidden(dt.val);
// the two invariant directions (eigenvectors)
eigen(ev, (ox, oy), 46, 2, 1, 1, 2);
hidden(ev); hidden(ev.l0); hidden(ev.l1);
// ---- timeline ----
show(hdr, 0.6);
say(cap, "the matrix bends the grid; i-hat and j-hat land on its columns", 0.5);
wait(1.4);
say(cap, "the unit square maps to a parallelogram — its area IS the determinant", 0.5);
show(dt, 0.6);
show(dt.unit, 0.4);
show(dt.val, 0.5);
wait(1.4);
say(cap, "two directions only stretch, never turn: the eigenvectors", 0.5);
show(ev, 0.6);
show(ev.l0, 0.4);
show(ev.l1, 0.4);
wait(1.0);
linear-system
The geometry of solving and spanning, in three panels: a 2×2 system as two lines
crossing at the solution (linsolve), two independent vectors reaching the whole
plane, and two parallel vectors collapsing to a line — rank 1 (span).
// ============================================================================
// linear-system.manic — the geometry of solving & spanning (a template)
// ----------------------------------------------------------------------------
// Three side-by-side panels, each built up with animation:
// A) a 2x2 system as two lines meeting at the solution (linsolve)
// B) two independent vectors spanning the whole plane (span)
// C) two parallel vectors collapsing to a single line, rank 1 (span)
//
// HOW TO ADAPT THIS FILE:
// * Change the system in panel A: linsolve(sys, center, unit, a,b,c,d, e,f)
// draws a*x + b*y = e and c*x + d*y = f, meeting at the solution.
// * Change the vectors in panels B/C: span(id, center, unit, (vx,vy),(wx,wy)).
// * Everything is positioned from cx/cy/w/h + a per-panel origin (ax/bx/dx)
// and a `unit` = pixels-per-grid-step, so it scales cleanly. Grid point
// (gx,gy) maps to screen (origin_x + gx*u, origin_y - gy*u) [math y-up].
// * OPTIONAL: shade the closed region the two lines bound with the axes —
// see the commented `polygon(feasible, ...)` block in Panel A below.
// ============================================================================
title("Solving & spanning, visually");
canvas("16:9");
let y0 = cy + 20; // shared vertical center of the three panels
let ax = 280; // panel A origin x — a system as two lines
let bx = 640; // panel B origin x — span = the whole plane
let dx = 1000; // panel C origin x — parallel vectors collapse to a line
let u = 24; // pixels per grid unit (shared scale)
text(hdr, (cx, 46), "linear algebra: the geometry of solving & spanning");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// per-panel captions, revealed as each panel animates
text(la, (ax, 96), "a system = two lines"); size(la, 18); color(la, dim); display(la); hidden(la);
text(lb, (bx, 96), "span of two vectors"); size(lb, 18); color(lb, dim); display(lb); hidden(lb);
text(lc, (dx, 96), "parallel = rank 1"); size(lc, 18); color(lc, dim); display(lc); hidden(lc);
// faint coordinate backdrops (drawn once, static)
plane(pa, (ax, y0), 150, 190, u);
plane(pb, (bx, y0), 150, 190, u);
plane(pc, (dx, y0), 150, 190, u);
// ---- Panel A — the system 2x + y = 5 and x + 3y = 10, meeting at (1, 3) ----
linsolve(sys, (ax, y0), u, 2, 1, 1, 3, 5, 10);
untraced(sys.r1); untraced(sys.r2); // start blank, drawn in on the timeline
hidden(sys); hidden(sys.val); // the solution dot + its label fade in
// -- OPTIONAL: fill the closed area the two lines bound with the axes --------
// The two rows, the x-axis and the y-axis enclose a quadrilateral (the classic
// "feasible region"). Its corners, in grid coordinates, are:
// (0,0) -> (2.5,0) -> (1,3) -> (0,3.33)
// origin line1 hits the two line2 hits
// the x-axis lines meet the y-axis
// Uncomment to shade it (points are origin + gridX*u, origin - gridY*u):
//
// polygon(feasible,
// (ax, y0), // (0, 0) the origin
// (ax + 2.5*u, y0), // (2.5, 0) line 1 crosses the x-axis
// (ax + u, y0 - 3*u), // (1, 3) the two lines meet
// (ax, y0 - 3.333*u), // (0, 3.33) line 2 crosses the y-axis
// lime);
// opacity(feasible, 0.18); // translucent, so grid + lines show through
// z(feasible, -1); // sit behind the lines
// (for an animated reveal instead of a static fill, also `hidden(feasible);`
// above, then `to(feasible, opacity, 0.18, 0.6);` in Panel A's timeline.)
// ---------------------------------------------------------------------------
// ---- Panel B — two independent vectors reach every point: the whole plane --
span(fill, (bx, y0), u, (3, 1), (-1, 2));
untraced(fill.v); untraced(fill.w); // vectors draw in
hidden(fill.plane); // faint region fades to 0.14
// ---- Panel C — two parallel vectors only reach one line: rank 1 ------------
span(rank, (dx, y0), u, (2, 1), (-2, -1), gold);
untraced(rank.v); untraced(rank.w); untraced(rank.line);
text(cap, (cx, h - 44), ""); size(cap, 22); color(cap, dim); display(cap);
// ---- Panel A timeline: two lines draw in, then the solution pops -----------
show(la, 0.4);
say(cap, "two equations are two lines; where they cross solves the system", 0.5);
par { draw(sys.r1, 0.8); draw(sys.r2, 0.8); }
show(sys, 0.4);
show(sys.val, 0.4);
pulse(sys);
wait(1.6);
// ---- Panel B timeline: two vectors grow out, then the plane fills in -------
show(lb, 0.4);
say(cap, "two independent vectors combine to reach every point: the whole plane", 0.5);
par { draw(fill.v, 0.7); draw(fill.w, 0.7); }
to(fill.plane, opacity, 0.14, 0.9);
wait(1.6);
// ---- Panel C timeline: two parallel vectors, then their line (collapse) ----
show(lc, 0.4);
say(cap, "but parallel vectors only reach one line: rank 1, a collapse", 0.5);
par { draw(rank.v, 0.7); draw(rank.w, 0.7); }
draw(rank.line, 0.8);
wait(1.8);
diagonalise
A = P D P⁻¹ made visual: every real-diagonalisable matrix has a basis — its
eigenvectors — in which it does nothing but stretch each axis. The unit
eigen-cell stretches by λ along each eigenvector, with no rotation or shear
(diagonalise).
// ============================================================================
// diagonalise.manic — A = P D P^-1 made visual (a template)
// ----------------------------------------------------------------------------
// The big idea of diagonalisation: every (real-diagonalisable) matrix has a
// basis — its EIGENVECTORS — in which it does nothing but STRETCH each axis.
// No rotation, no shear: just a diagonal scaling D = diag(lambda1, lambda2).
//
// `diagonalise(id, (cx,cy), unit, a,b,c,d, [color])` draws, for [[a,b],[c,d]]:
// * the (generally skewed) eigen-grid — the coordinate frame of the eigenbasis
// * the two eigen-axes dg.axis1 / dg.axis2
// * the unit eigen-cell dg.cell and its image under A dg.img
// * the eigenvector images as arrows dg.v1 / dg.v2 (+ labels dg.v1l / dg.v2l)
// Complex or repeated eigenvalues (no real 2-D eigenbasis) leave a note instead.
//
// TO ADAPT: change the four matrix numbers below. Try a non-symmetric matrix
// like (2,1,0,3) to see a SKEWED eigenbasis, or (0,-1,1,0) to see the
// "no real eigenbasis" note (a pure rotation).
// ============================================================================
title("Diagonalisation: a matrix in its own basis");
canvas("16:9");
let ox = cx - 40;
let oy = cy + 20;
text(hdr, (cx, 54), "A = P D P^-1 — in the eigenbasis, A is only a stretch");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the matrix [[2,1],[1,2]] — eigenvalues 3 (along (1,1)) and 1 (along (1,-1))
diagonalise(dg, (ox, oy), 60, 2, 1, 1, 2);
// the faint eigen-grid stays as static context; reveal the rest in beats:
untraced(dg.axis1); untraced(dg.axis2); // eigen-axes draw in
hidden(dg.cell); hidden(dg.img); // unit cell fades in; image fades to 0.4
untraced(dg.v1); untraced(dg.v2); // eigenvector arrows draw in
hidden(dg.v1l); hidden(dg.v2l); // lambda labels fade in
text(cap, (cx, h - 50), ""); size(cap, 22); color(cap, dim); display(cap);
// ---- timeline ----
say(cap, "every matrix has special directions — its eigenvectors", 0.5);
par { draw(dg.axis1, 0.7); draw(dg.axis2, 0.7); }
wait(1.2);
say(cap, "build the unit cell from those two directions", 0.5);
show(dg.cell, 0.6);
wait(1.4);
say(cap, "apply A: the cell only STRETCHES along each axis — never rotates", 0.5);
to(dg.img, opacity, 0.4, 0.8);
par { draw(dg.v1, 0.7); draw(dg.v2, 0.7); }
wait(1.2);
say(cap, "the stretch factors ARE the eigenvalues — the diagonal of D", 0.5);
show(dg.v1l, 0.4); show(dg.v2l, 0.4);
wait(1.6);
rref
Gaussian elimination, animated: an augmented matrix [A | b] is reduced to
reduced row-echelon form one row operation at a time, the numbers transforming
in place until the left block is the identity and the last column is the
solution (rref).
// ============================================================================
// rref.manic — Gaussian elimination, animated (a template)
// ----------------------------------------------------------------------------
// Reduce an augmented matrix [A | b] to reduced row-echelon form, one row
// operation at a time. Each intermediate state is drawn at the same spot, so
// cross-fading s{k-1} -> s{k} makes the numbers transform IN PLACE.
//
// `rref(id, "row ; row ; ...", (cx,cy), [cellw], [rowh])` draws, for the given
// matrix (rows split on `;`, entries on spaces/commas):
// * static brackets (the frame the numbers fill)
// * one matrix per elimination state, tagged rr.s0, rr.s1, ... (hidden)
// * the row-op caption for each state: rr.op0, rr.op1, ...
// rr.s0 is the untouched input; the LAST state is the RREF (for [A|b] its final
// column is the solution). Reveal the states in order to animate the reduction.
//
// TO ADAPT: change the matrix string. A different system takes a different
// NUMBER of steps — add/remove reveal beats below to match (the ones past the
// last real state are harmless no-ops, so a few extra never hurt).
// ============================================================================
title("rref: Gaussian elimination, animated");
canvas("16:9");
text(hdr, (cx, 60), "reduce [ A | b ] to reduced row-echelon form");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the augmented matrix of the system 2x + y = 5, x + 3y = 10
rref(rr, "2 1 5 ; 1 3 10", (cx, cy - 10), 120, 64);
text(cap, (cx, h - 56), ""); size(cap, 22); color(cap, dim); display(cap);
// ---- reveal each state in place, captioned with its row operation ----
// Per beat: fade the old op, CROSS-FADE the matrix state (identical cells sit
// still, only the changed ones morph), then show the new op.
say(cap, "start with the augmented matrix [ A | b ]", 0.5);
show(rr.s0, 0.5); show(rr.op0, 0.4);
wait(1.6);
fade(rr.op0, 0.25); par { show(rr.s1, 0.5); fade(rr.s0, 0.5); } show(rr.op1, 0.4);
wait(1.5);
fade(rr.op1, 0.25); par { show(rr.s2, 0.5); fade(rr.s1, 0.5); } show(rr.op2, 0.4);
wait(1.5);
fade(rr.op2, 0.25); par { show(rr.s3, 0.5); fade(rr.s2, 0.5); } show(rr.op3, 0.4);
wait(1.5);
say(cap, "the left block is now the identity — the last column is the solution", 0.5);
fade(rr.op3, 0.25); par { show(rr.s4, 0.5); fade(rr.s3, 0.5); } show(rr.op4, 0.4);
wait(2.2);
projection
One idea, two faces: orthogonal projection drops a vector onto a subspace
(the shadow is the closest point, the error meets the space at a right angle),
and least-squares fits a line to data the same way — minimising the squared
residuals (project, leastsquares).
// ============================================================================
// projection.manic — projection & least-squares (a template)
// ----------------------------------------------------------------------------
// One idea, two faces. ORTHOGONAL PROJECTION drops a vector onto a subspace;
// the shadow p is the closest point, and the error b - p meets the subspace at
// a right angle. LEAST-SQUARES fits a line to data the SAME way — the best line
// is the one that minimises the (squared) residuals, i.e. the projection of the
// data onto the space of lines.
//
// `project(id, (cx,cy), unit, (bx,by), (ax,ay), [color])` draws:
// id.line (the subspace = span of a), id.b, id.p (the shadow), id.res (error),
// id.rt (right-angle mark), id.blabel / id.plabel.
// `leastsquares(id, (cx,cy), unit, "x1 y1 x2 y2 ...", [color])` draws:
// id.line (best fit), id.points (dots), id.residuals (verticals), id.eq.
//
// TO ADAPT: change b/a in `project`, or the point list in `leastsquares`.
// ============================================================================
title("Projection & least-squares");
canvas("16:9");
text(hdr, (cx, 50), "the closest point is a projection — and so is the best-fit line");
size(hdr, 23); color(hdr, cyan); bold(hdr); display(hdr);
// ---- LEFT: project vector b onto the line spanned by a ----
plane(pa, (cx - 330, cy + 10), 150, 210, 40);
project(pj, (cx - 330, cy + 10), 40, (1, 3), (3, 1));
untraced(pj.line); untraced(pj.b); untraced(pj.p); untraced(pj.res); untraced(pj.rt);
hidden(pj.blabel); hidden(pj.plabel);
// ---- RIGHT: fit a line to a point cloud ----
plane(pb, (cx + 310, cy + 90), 150, 150, 34);
leastsquares(ls, (cx + 310, cy + 90), 34, "1 2 2 3 3 5 4 4 5 6");
hidden(ls.points); untraced(ls.line); untraced(ls.residuals); hidden(ls.eq);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
// ---- Panel A: the projection ----
say(cap, "drop b onto the line spanned by a — its shadow is the projection p", 0.5);
draw(pj.line, 0.6);
draw(pj.b, 0.6);
wait(0.6);
par { draw(pj.p, 0.6); draw(pj.res, 0.6); }
draw(pj.rt, 0.3);
show(pj.blabel, 0.3); show(pj.plabel, 0.3);
wait(1.4);
say(cap, "the error b - p meets the line at a right angle: p is the nearest point", 0.5);
wait(1.8);
// ---- Panel B: least-squares is the same idea ----
say(cap, "fitting a line works the SAME way: minimise the squared residuals", 0.5);
show(ls.points, 0.5);
wait(0.7);
draw(ls.line, 0.7);
draw(ls.residuals, 0.6);
show(ls.eq, 0.4);
wait(2.0);
matrix
A bracketed matrix, rows/columns addressable via tags.
// Matrix — a bracketed grid of entries, addressable by row and column via tag
// broadcast (à la Manim's Matrix + set_row_colors / set_column_colors).
//
// manic examples/matrix.manic
// manic examples/matrix.manic --record out --fps 60
//
// Rows are separated by ';', entries by spaces/commas. Entry ids m.r{i}c{j};
// tags m.row{i} / m.col{j} / m.entries.
title("Matrix");
canvas(1280, 720);
text(head, (640, 130), "rows and columns you can address");
display(head); color(head, cyan); size(head, 28); hidden(head);
text(cap, (640, 620), ""); color(cap, dim); size(cap, 22);
matrix(m, "2 0 4; -1 1 5; 3 -2 0", (640, 370));
untraced(m.lbrack); untraced(m.rbrack);
hidden(m.entries);
show(head, 0.5);
say(cap, "a 3x3 matrix");
par { draw(m.lbrack, 0.5); draw(m.rbrack, 0.5); }
seq { show(m.row0, 0.35); show(m.row1, 0.35); show(m.row2, 0.35); }
wait(0.5);
section("Columns");
say(cap, "colour a column — set_column_colors");
recolor(m.col1, magenta, 0.4);
flash(m.col2, cyan);
wait(0.5);
section("Rows");
say(cap, "and highlight a row — set_row_colors");
recolor(m.row0, lime, 0.4);
par { pulse(m.r0c0); pulse(m.r0c1); pulse(m.r0c2); }
wait(1.2);
matrix_addition
Two matrices summed, cell by cell.
// Matrix Addition — A + B = C, computed entry by entry. Each matching pair of
// entries flashes, then their sum pops into the result matrix. A teaching
// animation: it shows *why* matrix addition is element-wise.
//
// manic examples/matrix_addition.manic
// manic examples/matrix_addition.manic --record out --fps 60
title("Matrix Addition");
canvas(1280, 720);
text(head, (640, 120), "add two matrices, entry by entry");
display(head); color(head, cyan); size(head, 28); hidden(head);
text(cap, (640, 600), ""); color(cap, dim); size(cap, 26);
// A + B = C
matrix(A, "2 1; 0 3", (280, 350), 74, 66);
matrix(B, "1 4; 5 2", (640, 350), 74, 66);
matrix(C, "3 5; 5 5", (1000, 350), 74, 66);
text(plus, (460, 350), "+"); display(plus); color(plus, magenta); size(plus, 44); hidden(plus);
text(eq, (820, 350), "="); display(eq); color(eq, magenta); size(eq, 44); hidden(eq);
// A and B trace/fade in; C is built up during the sweep
untraced(A.lbrack); untraced(A.rbrack);
untraced(B.lbrack); untraced(B.rbrack);
untraced(C.lbrack); untraced(C.rbrack);
hidden(A.entries); hidden(B.entries); hidden(C.entries);
// --- reveal the two matrices ---
show(head, 0.5);
say(cap, "two matrices, A and B");
par { draw(A.lbrack, 0.4); draw(A.rbrack, 0.4); draw(B.lbrack, 0.4); draw(B.rbrack, 0.4); }
par { show(A.entries, 0.4); show(B.entries, 0.4); }
show(plus, 0.3);
wait(0.5);
// --- add entry by entry ---
section("Entry by entry");
say(cap, "add matching entries, position by position");
par { show(eq, 0.3); draw(C.lbrack, 0.4); draw(C.rbrack, 0.4); }
seq {
par { flash(A.r0c0, lime); flash(B.r0c0, lime); }
say(cap, "2 + 1 = 3");
par { show(C.r0c0, 0.3); pulse(C.r0c0); }
par { flash(A.r0c1, lime); flash(B.r0c1, lime); }
say(cap, "1 + 4 = 5");
par { show(C.r0c1, 0.3); pulse(C.r0c1); }
par { flash(A.r1c0, lime); flash(B.r1c0, lime); }
say(cap, "0 + 5 = 5");
par { show(C.r1c0, 0.3); pulse(C.r1c0); }
par { flash(A.r1c1, lime); flash(B.r1c1, lime); }
say(cap, "3 + 2 = 5");
par { show(C.r1c1, 0.3); pulse(C.r1c1); }
}
wait(0.4);
// --- the result ---
section("Result");
say(cap, "A + B — every entry, all at once");
recolor(C.entries, cyan, 0.4);
par { pulse(C.r0c0); pulse(C.r0c1); pulse(C.r1c0); pulse(C.r1c1); }
wait(1.5);
matrix_addition_plane
The same sum, laid out on a coordinate plane.
// Matrix Addition, Geometrically — a 2x1 matrix IS a vector. Adding two of them
//
// [3] [1] [4]
// [1] + [2] = [3]
//
// is the same as sliding one arrow onto the tip of the other (tip-to-tail) and
// reading off where you land. The column matrices at the top stay in lockstep
// with the arrows on the plane, so you see the algebra and the geometry at once.
//
// manic examples/matrix_addition_plane.manic
// manic examples/matrix_addition_plane.manic --record out --fps 60
title("Matrix Addition on the Plane");
canvas(1280, 720);
text(head, (640, 96), "a 2x1 matrix is a vector — adding them is tip-to-tail");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (640, 686), ""); color(cap, dim); size(cap, 24);
// --- the equation, as column matrices across the top ---
matrix(MA, "3; 1", (452, 186), 44, 42); color(MA, cyan);
text(plus, (528, 186), "+"); display(plus); color(plus, dim); size(plus, 40); hidden(plus);
matrix(MB, "1; 2", (600, 186), 44, 42); color(MB, magenta);
text(eq, (676, 186), "="); display(eq); color(eq, dim); size(eq, 40); hidden(eq);
matrix(MC, "4; 3", (748, 186), 44, 42); color(MC, lime);
// --- the plane, centred low so the arrows have room to climb ---
plane(pl, (640, 438), 560, 250, 48);
// vectors from the plane's origin (dy is up); unit = 48 px
vector(va, (640, 438), (144, 48), cyan); // a = (3, 1) -> tip (784, 390)
vector(vb, (640, 438), (48, 96), magenta); // b = (1, 2) -> tip (688, 342)
vector(vs, (640, 438), (192, 144), lime); // a+b = (4, 3) -> tip (832, 294)
// the two translated copies that build the parallelogram
arrow(vb2, (784, 390), (832, 294)); color(vb2, magenta); glow(vb2, 0);
arrow(va2, (688, 342), (832, 294)); color(va2, cyan); glow(va2, 0);
// everything but the plane grid starts hidden / untraced
untraced(pl.x); untraced(pl.y); hidden(pl.grid);
untraced(va); untraced(vb); untraced(vs); untraced(vb2); untraced(va2);
untraced(MA.lbrack); untraced(MA.rbrack); hidden(MA);
untraced(MB.lbrack); untraced(MB.rbrack); hidden(MB);
untraced(MC.lbrack); untraced(MC.rbrack); hidden(MC);
// --- reveal the plane ---
show(head, 0.5);
section("The plane");
say(cap, "a cartesian grid, arrows pinned to the origin");
show(pl.grid, 0.6);
par { draw(pl.x, 0.5); draw(pl.y, 0.5); }
wait(0.3);
// --- vector a ---
section("Vector a");
say(cap, "a = [3, 1] — three right, one up");
par { draw(MA.lbrack, 0.3); draw(MA.rbrack, 0.3); }
par { show(MA, 0.3); draw(va, 0.6); }
wait(0.4);
// --- vector b ---
section("Vector b");
say(cap, "b = [1, 2] — one right, two up");
show(plus, 0.3);
par { draw(MB.lbrack, 0.3); draw(MB.rbrack, 0.3); }
par { show(MB, 0.3); draw(vb, 0.6); }
wait(0.4);
// --- tip to tail ---
section("Tip to tail");
say(cap, "slide b so its tail sits on the tip of a");
draw(vb2, 0.7);
wait(0.5);
// --- the sum ---
section("The sum");
say(cap, "the arrow to that new point is a + b = [4, 3]");
show(eq, 0.3);
par { draw(MC.lbrack, 0.3); draw(MC.rbrack, 0.3); }
par { show(MC, 0.3); draw(vs, 0.8); }
par { pulse(vs); flash(MC, lime); }
wait(0.6);
// --- parallelogram ---
section("Either order");
say(cap, "slide a onto b instead — same point. a + b = b + a");
draw(va2, 0.7);
wait(0.4);
say(cap, "the two paths frame a parallelogram; a + b is its diagonal");
par { pulse(va); pulse(vb); pulse(vs); }
wait(1.4);
linear_transform
A 2x2 matrix shearing a grid + basis vectors.
// Linear Transformation — a 2x2 matrix bends the whole plane. The grid, the
// basis vectors i-hat / j-hat, and a sample point all carry the tag `pl`, so a
// single `transform` applies the matrix to everything at once (Manim's
// ApplyMatrix). Straight lines stay straight; the grid shears / rotates.
//
// manic examples/linear_transform.manic
// manic examples/linear_transform.manic --template blueprint
title("Linear Transformation");
canvas("16:9");
let ox = cx; let oy = cy;
text(head, (cx, 84), "a matrix bends the whole plane -- watch the grid");
display(head); color(head, cyan); size(head, 24); hidden(head);
text(cap, (cx, 672), ""); color(cap, dim); size(cap, 23);
// the plane (its grid + axes are all tagged `pl`)
plane(pl, (ox, oy), 580, 320, 60);
// basis vectors + a sample point, all tagged `pl` so they transform together
vector(vi, (ox, oy), (120, 0), cyan); stroke(vi, 4); tag(vi, pl);
vector(vj, (ox, oy), (0, -120), magenta); stroke(vj, 4); tag(vj, pl);
dot(mark, (ox + 180, oy - 100), 9); color(mark, lime); glow(mark, 1.6); tag(mark, pl);
// --- script ---
show(head, 0.5);
say(cap, "the identity grid, with i-hat (cyan) and j-hat (magenta)");
wait(0.7);
section("Shear");
say(cap, "shear: i-hat stays put, j-hat leans over");
transform(pl, (ox, oy), 1, 0.5, 0, 1, 1.4, smooth);
wait(0.8);
section("Undo");
say(cap, "the inverse matrix brings it right back");
transform(pl, (ox, oy), 1, -0.5, 0, 1, 1.4, smooth);
wait(0.6);
section("Rotate");
say(cap, "a rotation matrix turns the whole plane");
transform(pl, (ox, oy), 0.707, -0.707, 0.707, 0.707, 1.5, smooth);
wait(1.3);
table
A ruled table; cells, rows, columns, labels all addressable.
// Tables — a ruled grid of entries with row/column headers, manic's Table /
// MathTable / IntegerTable. This is an addition table: each body cell is
// row + column. We reveal it, then "look up" 2 + 5 by flashing that row and
// column and lighting the answer — a demo of the table's tag addressing
// (row{i} / col{j} / the labels / the grid lines are all recolourable).
//
// manic examples/table.manic
// manic examples/table.manic --record out --fps 60
title("Tables");
canvas(1280, 720);
text(head, (640, 110), "a grid you can read by row and column");
display(head); color(head, cyan); size(head, 28); hidden(head);
text(cap, (640, 620), ""); color(cap, dim); size(cap, 24);
// body cells are the sums; headers are the addends (top-left corner is blank)
table(t, "0 5 10; 2 7 12; 4 9 14", (640, 372), 120, 78, "0 5 10", "0 2 4");
untraced(t.lines);
hidden(t.labels); hidden(t.entries);
// --- reveal ---
show(head, 0.5);
say(cap, "rule the grid, then fill it in");
draw(t.lines, 1.0);
show(t.labels, 0.5);
show(t.entries, 0.5);
wait(0.5);
// --- a lookup: 2 + 5 = 7 ---
section("Look it up");
say(cap, "read a cell as row + column");
par { flash(t.rowlabel1, lime); flash(t.collabel1, lime); }
say(cap, "row 2, column 5 ...");
par { flash(t.row1, cyan); flash(t.col1, cyan); }
say(cap, "2 + 5 = 7");
recolor(t.r1c1, lime, 0.3);
pulse(t.r1c1);
wait(1.4);
table_braces
A table annotated with braces.
// Table + Braces — a quarterly sales table, annotated with curly braces that
// group its columns (the four quarters into two halves of the year) and its
// rows (the two regions). A practical pattern: use a table for the data and
// braces to call out how its rows/columns cluster.
//
// The brace coordinates are aligned to the table's grid lines by hand — the
// table is centred at (640,360) with 110x70 cells, so its vertical rules fall
// at x = 365 + k*110 and its rows span y = 325..465.
//
// manic examples/table_braces.manic
// manic examples/table_braces.manic --record out --fps 60
title("Sales by Region");
canvas(1280, 720);
text(head, (640, 96), "a data table, with its groups braced");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (640, 640), ""); color(cap, dim); size(cap, 24);
// rows = regions, columns = quarters; body cells are sales (in $k)
table(t, "12 15 18 20; 9 11 14 16", (640, 360), 110, 70, "Q1 Q2 Q3 Q4", "North South");
untraced(t.lines);
hidden(t.labels); hidden(t.entries);
// column braces over the header row (bulge up: points run right -> left)
bracelabel(h1, (695, 248), (475, 248), "H1", 26); color(h1, magenta); hidden(h1);
bracelabel(h2, (915, 248), (695, 248), "H2", 26); color(h2, lime); hidden(h2);
// a vertical brace to the left of the row labels, grouping the two regions
bracelabel(reg, (356, 325), (356, 465), "Regions", 26); color(reg, cyan); hidden(reg);
// --- reveal the table ---
show(head, 0.5);
say(cap, "quarterly sales for two regions");
draw(t.lines, 1.0);
show(t.labels, 0.5);
show(t.entries, 0.5);
wait(0.4);
// --- brace the columns into halves of the year ---
section("Halves of the year");
say(cap, "Q1-Q2 are the first half, Q3-Q4 the second");
par { flash(t.col0, magenta); flash(t.col1, magenta); }
show(h1, 0.5);
par { flash(t.col2, lime); flash(t.col3, lime); }
show(h2, 0.5);
wait(0.4);
// --- brace the rows into regions ---
section("The regions");
say(cap, "and the two rows are the regions");
par { flash(t.rowlabel0, cyan); flash(t.rowlabel1, cyan); }
show(reg, 0.6);
wait(1.4);
Statistics & probability
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
statistics — the whole story in three ideas
A guided lesson, not a feature demo: describe a dataset, meet the normal curve, then see why the bell is everywhere (the Central Limit Theorem). The stats companion to the linear-algebra lesson. Start here.
// ============================================================================
// statistics.manic — Statistics, from data to the bell (a lesson)
// ----------------------------------------------------------------------------
// The stats-rung capstone, companion to linear-algebra.manic. Three chapters on
// one stage, chaptered with section():
// 1. DESCRIBE — any dataset has a shape, a centre, a spread (histogram)
// 2. THE BELL — measurements often pile into a normal curve (bellcurve)
// 3. WHY (CLT) — averages of anything become a bell (clt)
//
// Each chapter reveals its whole builtin with `untraced(tag)` → `draw(tag)`
// (preserving faint band opacities) and clears with `fade(tag)`.
// ============================================================================
title("Statistics: from data to the bell");
canvas("16:9");
let sy = cy + 30;
// 1. a real dataset (exam scores, already roughly bell-shaped)
histogram(hg, (cx, sy),
"68 72 75 71 74 77 73 70 76 72 74 78 71 73 75 69 74 72 76 73 71 75 70 74 72 73 74 71 75 73", 12, 620, 230);
untraced(hg);
// 2. the idealised normal curve
bellcurve(bc, (cx, sy - 24), 100, 15, 78);
untraced(bc);
// 3. why the bell is everywhere — the Central Limit Theorem
clt(ct, (cx, sy), 5, 1000, 7, 620, 250);
untraced(ct);
text(cap, (cx, h - 44), ""); size(cap, 22); color(cap, dim); display(cap);
// ---- 1. describe ----
section("Describe the data");
draw(hg, 1.3);
say(cap, "any dataset has a shape, a centre, and a spread", 0.5);
wait(1.8);
say(cap, "these exam scores cluster around the middle, tailing off either side", 0.5);
wait(2.0);
fade(hg, 0.6);
wait(0.3);
// ---- 2. the bell ----
say(cap, "", 0.2);
section("The normal curve");
draw(bc, 1.3);
say(cap, "so many measurements pile into the same bell — the normal distribution", 0.5);
wait(2.0);
say(cap, "68% within one standard deviation, 95% within two, 99.7% within three", 0.5);
wait(2.2);
fade(bc, 0.6);
wait(0.3);
// ---- 3. why ----
say(cap, "", 0.2);
section("Why? The Central Limit Theorem");
draw(ct, 1.4);
say(cap, "average five dice, a thousand times — however flat one die is…", 0.5);
wait(2.0);
say(cap, "…the averages form a bell. THAT is why the normal is everywhere", 0.5);
wait(2.4);
histogram
The shape of a dataset: a list of numbers binned into bars that stagger in one
at a time, with the mean marked and the range labelled (histogram). Paste your
own numbers into the data string — grades, prices, heights, times.
// ============================================================================
// histogram.manic — the shape of a dataset (stats Tier 1)
// ----------------------------------------------------------------------------
// `histogram(id, (cx,cy), "v1 v2 v3 ...", [bins], [width], [height], [color])`
// bins a list of numbers into bars — the SHAPE of the data. Bars are
// `{id}.bar{k}` (tagged `{id}.bars`) so they stagger in and recolour as a
// group; `{id}.meanline` + `{id}.mean` mark the mean, `{id}.min`/`{id}.max`
// label the range. Default bin count ≈ √n.
//
// TO ADAPT: paste your own numbers into the data string (grades, prices,
// heights, times…). More `bins` = finer shape; fewer = smoother. Pass a colour
// as the last argument, or `rainbow` to give every bar its own hue (below).
// ============================================================================
title("The shape of a dataset");
canvas("16:9");
let n = 14;
text(hdr, (cx, 60), "35 exam scores — where do they cluster?");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// `rainbow` colours each bar across the spectrum — no loop needed
histogram(hg, (cx, cy + 10),
"72 85 90 68 95 88 76 91 83 79 84 60 97 81 78 86 74 89 82 93 71 87 80 77 92 85 83 88 75 90 66 82 84 79 88",
n, 640, 300, rainbow);
untraced(hg.bars); // bars sketch in one at a time
hidden(hg.meanline); hidden(hg.mean);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
// ---- bars build up, left to right ----
say(cap, "each bar counts how many scores fall in that range", 0.5);
stagger(0.06) { for k in 0..n { draw(hg.bar{k}, 0.35); } }
wait(1.0);
say(cap, "the shape emerges: most scores cluster in the 80s, tailing off below", 0.5);
wait(1.6);
// ---- reveal the mean ----
say(cap, "and the average sits right in the thick of it", 0.5);
show(hg.meanline, 0.5); show(hg.mean, 0.4);
pulse(hg.mean);
wait(1.8);
summary
Describe a dataset in one call: the numbers as dots on a number line, with the
mean, median and mode marked, a ±1σ spread band, and readouts of the range,
variance and standard deviation (summary). Central tendency and dispersion,
together.
// ============================================================================
// summary.manic — describe a dataset (stats Tier 1)
// ----------------------------------------------------------------------------
// `summary(id, (cx,cy), "v1 v2 v3 …", [width], [color])` is the descriptive-
// statistics workhorse: the data as dots on a number line, with the **mean**
// (gold), **median** (magenta) and **mode** (lime) marked, a translucent
// **±1σ spread band**, and a readout of **n / range / variance / std**.
// Pieces: `{id}.dots` (the cloud), `{id}.meanmark`/`.medianmark`/`.modemark`
// (+ `.*lbl`), `{id}.band`, `{id}.min`/`.max`, `{id}.readout`.
//
// TO ADAPT: paste your own numbers. Central tendency (mean/median/mode) and
// dispersion (range/variance/std) come out in one call.
// ============================================================================
title("Describe a dataset");
canvas("16:9");
text(hdr, (cx, 64), "20 daily temperatures — centre and spread");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
summary(sm, (cx, cy - 10),
"18 21 20 22 19 23 21 20 21 24 22 21 19 20 23 21 22 20 21 25", 640);
// reveal in beats
untraced(sm.line);
hidden(sm.dots);
hidden(sm.band);
hidden(sm.meanmark); hidden(sm.meanlbl);
hidden(sm.medianmark); hidden(sm.medianlbl);
hidden(sm.modemark); hidden(sm.modelbl);
hidden(sm.readout);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "start with the raw numbers, laid out on a line", 0.5);
draw(sm.line, 0.6);
show(sm.dots, 0.6);
wait(1.4);
say(cap, "the centre: mean, median and mode", 0.5);
show(sm.meanmark, 0.4); show(sm.meanlbl, 0.4);
show(sm.medianmark, 0.4); show(sm.medianlbl, 0.4);
show(sm.modemark, 0.4); show(sm.modelbl, 0.4);
wait(1.6);
say(cap, "the spread: most values fall within one standard deviation of the mean", 0.5);
to(sm.band, opacity, 0.12, 0.6);
show(sm.readout, 0.4);
wait(2.0);
boxplot
The five-number summary as a box-and-whisker: the box spans Q1→Q3 (its width is
the interquartile range), a line marks the median, the whiskers reach the rest,
and a value far outside is flagged as an outlier (boxplot).
// ============================================================================
// boxplot.manic — the five-number summary (stats Tier 1)
// ----------------------------------------------------------------------------
// `boxplot(id, (cx,cy), "v1 v2 v3 …", [width], [color])` draws a box-and-whisker:
// the box spans Q1→Q3 (its width IS the interquartile range), a line marks the
// median, whiskers reach the extreme non-outliers (within 1.5·IQR), and points
// beyond are flagged as outliers. Pieces: `{id}.box`, `{id}.med`,
// `{id}.whiskerlo`/`.whiskerhi` (+ `.caplo`/`.caphi`), `{id}.outliers`,
// `{id}.iqr` and the value labels.
//
// TO ADAPT: paste your numbers. A value far from the box (here 40) shows up as
// an outlier automatically.
// ============================================================================
title("Box-and-whisker: the five-number summary");
canvas("16:9");
text(hdr, (cx, 70), "response times (seconds) — one slow outlier");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
boxplot(bp, (cx, cy), "12 15 14 10 18 16 13 15 14 17 40 11 16 15 14", 700);
untraced(bp.whiskerlo); untraced(bp.whiskerhi); untraced(bp.caplo); untraced(bp.caphi); untraced(bp.med);
hidden(bp.box);
hidden(bp.outliers);
hidden(bp.iqr); hidden(bp.lmin); hidden(bp.lmax); hidden(bp.lmed); hidden(bp.lq1); hidden(bp.lq3);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "the box holds the middle half of the data — Q1 to Q3, the IQR", 0.5);
to(bp.box, opacity, 0.2, 0.6);
show(bp.lq1, 0.3); show(bp.lq3, 0.3); show(bp.iqr, 0.3);
wait(1.5);
say(cap, "the line inside is the median; the whiskers reach the rest", 0.5);
draw(bp.med, 0.4); show(bp.lmed, 0.3);
par { draw(bp.whiskerlo, 0.5); draw(bp.whiskerhi, 0.5); }
draw(bp.caplo, 0.3); draw(bp.caphi, 0.3);
show(bp.lmin, 0.3); show(bp.lmax, 0.3);
wait(1.6);
say(cap, "and a value far outside the whiskers is flagged as an outlier", 0.5);
show(bp.outliers, 0.4); pulse(bp.outliers);
wait(1.9);
boxplot-wave
Reading a box plot, on a labelled chart: eight months of daily-rainfall box plots JUMP up from below into their true medians (building the seasonal wave), a dashed trend line draws through them, then manic signs off.
// boxplot-wave — reading a box plot: the seasonal rainfall wave, built by jumps.
//
// Eight months of daily-rainfall box plots. They start below the frame and JUMP
// up (springy `shift`) into their true medians on the mm axis — staggered, so the
// seasonal wave assembles month by month. A dashed trend line then draws through
// the medians, and the manic wordmark signs off. Educational (labelled axes, a
// real seasonal pattern, how to read a box plot) and lively.
//
// manic examples/boxplot-wave.manic
title("Monthly rainfall through the year");
canvas(1600, 1000);
template("paper");
let ybase = 850; // y of 0 mm
let sc = 9; // pixels per mm
let start = 1060; // off-screen below: the boxes launch from here
// headings
text(hdr, (830, 66), "Monthly rainfall through the year");
size(hdr, 40); color(hdr, #222222);
text(sub, (830, 112), "each box = a month of daily rainfall · line = median, box = middle 50% (IQR), whiskers = range, dot = a storm");
size(sub, 21); color(sub, #7f8aa3);
// ---- y axis (rainfall, mm) ----
line(yax, (200, 300), (200, ybase)); stroke(yax, 2.5); color(yax, #444444);
for k in 0..4 {
let mm = k * 20;
let yv = ybase - mm * sc;
line(ytk{k}, (192, yv), (208, yv)); stroke(ytk{k}, 2); color(ytk{k}, #444444);
counter(ytl{k}, (160, yv), mm, 0); size(ytl{k}, 22); color(ytl{k}, #555555);
}
text(ytit, (205, 270), "mm"); size(ytit, 22); color(ytit, #7f8aa3);
// ---- x axis (month) ----
line(xax, (220, ybase), (1470, ybase)); stroke(xax, 2.5); color(xax, #444444);
text(xtit, (845, 958), "month"); size(xtit, 24); color(xtit, #7f8aa3);
// ---- the eight monthly box plots, created OFF-SCREEN BELOW (they jump up) ----
boxplot(m0, (290, start), "0 1 2 3 4 5 8", 72);
boxplot(m1, (450, start), "1 3 4 5 6 8 11", 72);
boxplot(m2, (610, start), "5 8 10 12 14 18 24", 72);
boxplot(m3, (770, start), "14 20 26 30 34 40 52", 72);
boxplot(m4, (930, start), "18 26 30 35 39 46 60 82", 72);
boxplot(m5, (1090, start), "10 16 20 22 25 30 40", 72);
boxplot(m6, (1250, start), "4 7 9 11 13 17 24", 72);
boxplot(m7, (1410, start), "0 2 3 4 5 7 10", 72);
// month labels
text(x0, (290, 900), "Jan"); text(x1, (450, 900), "Feb"); text(x2, (610, 900), "Apr");
text(x3, (770, 900), "Jun"); text(x4, (930, 900), "Jul"); text(x5, (1090, 900), "Sep");
text(x6, (1250, 900), "Oct"); text(x7, (1410, 900), "Dec");
size(x0, 22); size(x1, 22); size(x2, 22); size(x3, 22); size(x4, 22); size(x5, 22); size(x6, 22); size(x7, 22);
color(x0, #555555); color(x1, #555555); color(x2, #555555); color(x3, #555555);
color(x4, #555555); color(x5, #555555); color(x6, #555555); color(x7, #555555);
// hide each plot's own number labels — the shared axis carries the values now
hidden(m0.iqr); hidden(m0.lmin); hidden(m0.lmed); hidden(m0.lmax); hidden(m0.lq1); hidden(m0.lq3);
hidden(m1.iqr); hidden(m1.lmin); hidden(m1.lmed); hidden(m1.lmax); hidden(m1.lq1); hidden(m1.lq3);
hidden(m2.iqr); hidden(m2.lmin); hidden(m2.lmed); hidden(m2.lmax); hidden(m2.lq1); hidden(m2.lq3);
hidden(m3.iqr); hidden(m3.lmin); hidden(m3.lmed); hidden(m3.lmax); hidden(m3.lq1); hidden(m3.lq3);
hidden(m4.iqr); hidden(m4.lmin); hidden(m4.lmed); hidden(m4.lmax); hidden(m4.lq1); hidden(m4.lq3);
hidden(m5.iqr); hidden(m5.lmin); hidden(m5.lmed); hidden(m5.lmax); hidden(m5.lq1); hidden(m5.lq3);
hidden(m6.iqr); hidden(m6.lmin); hidden(m6.lmed); hidden(m6.lmax); hidden(m6.lq1); hidden(m6.lq3);
hidden(m7.iqr); hidden(m7.lmin); hidden(m7.lmed); hidden(m7.lmax); hidden(m7.lq1); hidden(m7.lq3);
// the trend line through the medians (drawn in after the boxes land)
line(t0, (290, ybase-3*sc), (450, ybase-5*sc)); tag(t0, trend);
line(t1, (450, ybase-5*sc), (610, ybase-12*sc)); tag(t1, trend);
line(t2, (610, ybase-12*sc), (770, ybase-30*sc)); tag(t2, trend);
line(t3, (770, ybase-30*sc), (930, ybase-35*sc)); tag(t3, trend);
line(t4, (930, ybase-35*sc), (1090, ybase-22*sc)); tag(t4, trend);
line(t5, (1090, ybase-22*sc), (1250, ybase-11*sc)); tag(t5, trend);
line(t6, (1250, ybase-11*sc), (1410, ybase-4*sc)); tag(t6, trend);
stroke(trend, 2.5); color(trend, #ff2d95); dashed(trend); untraced(trend);
// the wordmark, waiting for the sign-off
text(word, (1400, 210), "manic"); size(word, 46); color(word, #ff2d95); hidden(word);
// ---- the show ----
// 1) each month JUMPS up into place (springy), building the seasonal wave
stagger(0.26) {
shift(m0, (0, -(start - (ybase - 3 * sc))), 1.3, overshoot);
shift(m1, (0, -(start - (ybase - 5 * sc))), 1.3, overshoot);
shift(m2, (0, -(start - (ybase - 12 * sc))), 1.3, overshoot);
shift(m3, (0, -(start - (ybase - 30 * sc))), 1.3, overshoot);
shift(m4, (0, -(start - (ybase - 35 * sc))), 1.3, overshoot);
shift(m5, (0, -(start - (ybase - 22 * sc))), 1.3, overshoot);
shift(m6, (0, -(start - (ybase - 11 * sc))), 1.3, overshoot);
shift(m7, (0, -(start - (ybase - 4 * sc))), 1.3, overshoot);
}
// 2) draw the seasonal trend, then 3) sign off
draw(trend, 2.5);
show(word, 0.9); pulse(word);
skew
Which way does the tail point? A histogram with the mean and median marked and a
labelled skewness — when the mean is dragged right of the median, the data is
right-skewed (skew).
// ============================================================================
// skew.manic — the shape of a dataset: skewness (stats Tier 1)
// ----------------------------------------------------------------------------
// `skew(id, (cx,cy), "v1 v2 v3 …", [bins], [width], [height], [color])` draws a
// histogram with the **mean** (gold) and **median** (magenta) marked and a
// labelled skewness. The tell: when the mean sits right of the median, a right
// tail is pulling it — the data is right-skewed (positive); left of it, left-
// skewed; on top of it, symmetric.
//
// TO ADAPT: paste your numbers. Incomes, wait times and prices are classically
// right-skewed (a few big values pull the mean up).
// ============================================================================
title("Skewness: which way does the tail point?");
canvas("16:9");
text(hdr, (cx, 66), "monthly salaries (k) — a few big earners pull the mean up");
size(hdr, 23); color(hdr, cyan); bold(hdr); display(hdr);
skew(sk, (cx, cy - 10),
"28 30 32 31 29 33 35 30 31 34 42 38 30 32 31 33 30 36 55 68 90 30 34 31", 14, 560, 240);
untraced(sk.bars);
hidden(sk.meanline); hidden(sk.meanlbl);
hidden(sk.medianline); hidden(sk.medianlbl);
hidden(sk.skewlbl);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "most salaries cluster low, with a long tail of high earners", 0.5);
stagger(0.05) { for k in 0..14 { draw(sk.bar{k}, 0.3); } }
wait(1.2);
say(cap, "the median sits in the cluster — but the mean is dragged toward the tail", 0.5);
show(sk.medianline, 0.4); show(sk.medianlbl, 0.3);
show(sk.meanline, 0.4); show(sk.meanlbl, 0.3);
wait(1.6);
say(cap, "mean to the right of median = right-skewed (positive skew)", 0.5);
show(sk.skewlbl, 0.4);
wait(1.8);
bellcurve
The normal (Gaussian) bell curve and the 68-95-99.7 rule: the bell draws in,
then the ±1σ / ±2σ / ±3σ bands shade one at a time, showing that 68% of values
fall within one standard deviation, 95% within two, and 99.7% within three
(bellcurve, alias gaussian).
// ============================================================================
// normal.manic — the bell curve & the 68-95-99.7 rule (stats Tier 2)
// ----------------------------------------------------------------------------
// `bellcurve(id, (cx,cy), mu, sigma, [unit], [color])` (alias `gaussian`) draws
// bell curve and shades the ±1σ/±2σ/±3σ bands — the 68-95-99.7 rule. Pieces:
// `{id}.curve` (the bell), `{id}.band1/2/3` (tagged `{id}.bands`), `{id}.mean`,
// `{id}.p1/p2/p3` (the percentages), `{id}.t{-3..3}` (value ticks).
// `unit` = pixels per σ; the bell is standardised, μ/σ set the axis values.
//
// TO ADAPT: change mu/sigma to your distribution (heights, IQ, measurement
// error…). The 68-95-99.7 rule holds for every normal.
// ============================================================================
title("The bell curve & the 68-95-99.7 rule");
canvas("16:9");
// IQ scores: mean 100, standard deviation 15
bellcurve(nd, (cx, cy + 10), 100, 15, 95);
untraced(nd.curve); // the bell draws in
hidden(nd.mean);
hidden(nd.bands); // the three σ-bands reveal one at a time
hidden(nd.p1); hidden(nd.p2); hidden(nd.p3);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "IQ scores: mean 100, standard deviation 15 — the classic bell", 0.5);
draw(nd.curve, 1.1);
show(nd.mean, 0.4);
wait(1.2);
say(cap, "68% of values fall within ONE standard deviation of the mean", 0.5);
to(nd.band1, opacity, 0.16, 0.6); show(nd.p1, 0.4);
wait(1.7);
say(cap, "95% fall within two — almost everyone", 0.5);
to(nd.band2, opacity, 0.16, 0.6); show(nd.p2, 0.4);
wait(1.7);
say(cap, "and 99.7% within three: the tails are tiny", 0.5);
to(nd.band3, opacity, 0.16, 0.6); show(nd.p3, 0.4);
wait(1.9);
clt
The Central Limit Theorem — the flagship: however flat a single die is, the
average of five dice, taken 1200 times, piles into a bell that hugs the normal
curve (clt). Seeded, so it renders the same every time.
// ============================================================================
// clt.manic — the Central Limit Theorem (stats Tier 3, the flagship payoff)
// ----------------------------------------------------------------------------
// `clt(id, (cx,cy), samplesize, trials, [seed], [width], [height])` runs `trials`
// experiments — each the average of `samplesize` dice — and histograms those
// averages. However flat a single die is, the averages pile into a BELL. Draws
// the histogram of sample means (`{id}.bar{k}`, tagged `{id}.bars`), the normal
// curve they converge to (`{id}.curve`), the mean line, ticks, and an info
// label. Seeded → the render is identical every time.
//
// TO ADAPT: change `samplesize` (bigger n → tighter bell) or `trials` (more →
// smoother). `seed` picks the reproducible random sequence.
// ============================================================================
title("The Central Limit Theorem");
canvas("16:9");
clt(ct, (cx, cy + 20), 5, 1200, 7, 660, 300);
untraced(ct.bars); // the sample means pile up, bar by bar
untraced(ct.curve); // the normal curve draws in last
hidden(ct.mean);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "one die is flat — 1 to 6 are equally likely. but average FIVE dice…", 0.5);
stagger(0.03) { for k in 0..30 { draw(ct.bar{k}, 0.3); } }
wait(1.0);
say(cap, "…and repeat 1200 times: the averages pile up around the middle", 0.5);
show(ct.mean, 0.4);
wait(1.6);
say(cap, "they trace a bell — the Central Limit Theorem: averages are normal", 0.5);
draw(ct.curve, 1.1);
wait(2.0);
correlation
Do two things move together? The scatter of paired data, the best-fit line, and
the Pearson correlation r — near +1 a tight upward line, near −1 downward, near
0 a shapeless blob (correlation).
// ============================================================================
// correlation.manic — how strongly two variables move together (stats T3)
// ----------------------------------------------------------------------------
// `correlation(id, (cx,cy), unit, "x1 y1 x2 y2 …", [color])` scatters the
// points, fits the best line, and reports the **Pearson correlation r** with a
// strong/moderate/weak · positive/negative reading. `unit` = pixels per data
// unit (x and y share it, so use data with comparable ranges). Points
// `{id}.p{k}` (tagged `{id}.points`), the fit `{id}.line`, and `{id}.r`.
//
// TO ADAPT: paste your paired data. r near ±1 = a tight line; near 0 = a blob.
// ============================================================================
title("Correlation: do they move together?");
canvas("16:9");
text(hdr, (cx, 70), "hours studied vs grade (out of 10)");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
correlation(co, (cx, cy - 10), 42,
"1 3 2 4 3 4 4 6 5 6 6 7 7 8 8 8 9 10 10 9");
hidden(co.points); untraced(co.line); hidden(co.r);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "each dot is one student: hours studied, and the grade they got", 0.5);
show(co.points, 0.6);
wait(1.3);
say(cap, "the cloud slopes up — more study tends to mean a higher grade", 0.5);
draw(co.line, 0.9);
wait(1.3);
say(cap, "r measures how tightly the points track that line: near +1 is strong", 0.5);
show(co.r, 0.4); pulse(co.r);
wait(1.8);
lln
The Law of Large Numbers: flip a fair coin over and over and track the running
proportion of heads. It swings wildly at first, then settles onto the true 0.5
as the trials pile up (lln). Draw the curve in to watch it converge.
// ============================================================================
// lln.manic — the Law of Large Numbers (stats Tier 3)
// ----------------------------------------------------------------------------
// `lln(id, (cx,cy), trials, [seed], [width], [height])` plots the running
// proportion of heads over many coin flips: wild at first, settling onto the
// true 0.5. Draws `{id}.curve`, the reference line `{id}.ref`, axis labels, and
// the final value. Seeded → the render is identical every time.
//
// TO ADAPT: change `trials` (more → tighter settling) or `seed` (a different
// reproducible run).
// ============================================================================
title("The Law of Large Numbers");
canvas("16:9");
lln(ll, (cx, cy + 10), 600, 3, 720, 300);
untraced(ll.curve); // the proportion traces in — watch it settle
hidden(ll.finallbl);
hidden(ll.truelbl);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "flip a fair coin over and over; track the proportion of heads so far", 0.5);
show(ll.truelbl, 0.4);
wait(0.8);
say(cap, "early on it swings wildly — a few flips prove nothing", 0.5);
draw(ll.curve, 2.6);
wait(0.4);
say(cap, "but over many trials it settles onto the true probability, 0.5", 0.5);
show(ll.finallbl, 0.4);
wait(1.9);
hypothesis
Is a result surprising enough to be real? Under the null hypothesis the test
statistic follows the standard normal; the observed z cuts off tails whose area
is the p-value. Smaller than α, reject (hypothesis).
// ============================================================================
// hypothesis.manic — a significance test & the p-value (stats Tier 5)
// ----------------------------------------------------------------------------
// `hypothesis(id, (cx,cy), z, [alpha], [unit])` — under the null hypothesis the
// test statistic is standard-normal; the observed z cuts off tails whose area is
// the p-value. If that area is smaller than alpha, the result is too surprising
// to be chance — reject. Pieces: `{id}.curve`, `{id}.tails`, `{id}.zline`,
// `{id}.p`, `{id}.verdict`.
// ============================================================================
title("Hypothesis testing: is this surprising?");
canvas("16:9");
hypothesis(hy, (cx, cy - 10), 2.3, 0.05, 92);
untraced(hy.curve);
hidden(hy.tails); hidden(hy.zline); hidden(hy.zlbl);
hidden(hy.p); hidden(hy.verdict);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "if nothing's going on, the test statistic follows this bell", 0.5);
draw(hy.curve, 1.0);
wait(1.2);
say(cap, "we observed z = 2.3 — out here in the tails", 0.5);
show(hy.zline, 0.4); show(hy.zlbl, 0.3);
wait(1.4);
say(cap, "the shaded tail area is the p-value: how likely a result this extreme is", 0.5);
to(hy.tails, opacity, 0.5, 0.6); show(hy.p, 0.4);
wait(1.6);
say(cap, "p = 0.021 < 0.05, so it's too surprising for chance — reject the null", 0.5);
show(hy.verdict, 0.4);
wait(1.9);
covariance
Covariance as signed area: a cross at the means, and a rectangle from each point
to the centre — cyan where x and y agree, magenta where they disagree. Their
balance is the covariance (covariance).
// ============================================================================
// covariance.manic — covariance as signed area (stats Tier 5)
// ----------------------------------------------------------------------------
// `covariance(id, (cx,cy), unit, "x1 y1 x2 y2 …", [color])` — a cross at the
// means splits the plane into quadrants; each point draws a rectangle to the
// mean-corner, cyan where (x-x̄)(y-ȳ) > 0 (agreeing) and magenta where negative.
// Their signed-area balance IS the covariance. `{id}.points`, `{id}.rects`,
// `{id}.cross`, `{id}.cov`.
// ============================================================================
title("Covariance: signed area about the mean");
canvas("16:9");
covariance(cv, (cx, cy - 10), 34, "1 2 2 1 3 4 4 3 5 6 6 5 7 8 8 7 9 9");
hidden(cv.points); hidden(cv.rects);
untraced(cv.crossv); untraced(cv.crossh);
hidden(cv.cov);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "mark each point, then draw the cross at the mean of x and the mean of y", 0.5);
show(cv.points, 0.5);
par { draw(cv.crossv, 0.5); draw(cv.crossh, 0.5); }
wait(1.4);
say(cap, "each point makes a rectangle to the centre — cyan if x and y agree", 0.5);
to(cv.rects, opacity, 0.13, 0.7);
wait(1.6);
say(cap, "more agreeing (cyan) area than disagreeing = positive covariance", 0.5);
show(cv.cov, 0.4);
wait(1.9);
bayes
Bayesian updating: a prior belief about a coin’s bias, the likelihood from the
data, and the posterior that combines them — pulled toward the evidence and
sharpening as it accumulates (bayes).
// ============================================================================
// bayes.manic — Bayesian updating (stats Tier 5)
// ----------------------------------------------------------------------------
// `bayes(id, (cx,cy), heads, tails, [width], [height])` — belief about a coin's
// bias: a mild PRIOR, the LIKELIHOOD from the data, and the POSTERIOR that
// combines them (pulled toward the data, sharpening as evidence grows). Pieces:
// `{id}.prior`, `{id}.likelihood`, `{id}.posterior`, `{id}.mean`.
// ============================================================================
title("Bayes: updating a belief with data");
canvas("16:9");
bayes(by, (cx, cy + 10), 7, 2, 640, 250);
untraced(by.prior); untraced(by.likelihood); untraced(by.posterior);
hidden(by.mean); hidden(by.priorlbl); hidden(by.postlbl); hidden(by.datalbl);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "before any flips, a mild belief: the coin is probably fair-ish (the prior)", 0.5);
draw(by.prior, 0.8); show(by.priorlbl, 0.3);
wait(1.4);
say(cap, "then we flip: 7 heads, 2 tails — the data favours a biased coin (likelihood)", 0.5);
draw(by.likelihood, 0.8); show(by.datalbl, 0.3);
wait(1.6);
say(cap, "combine them and the posterior lands between: p about 0.69", 0.5);
draw(by.posterior, 0.8); show(by.mean, 0.4); show(by.postlbl, 0.3);
wait(2.0);
probability
A probability & sampling playground in four chapters: named distributions
(uniform / exponential / binomial / Poisson), a confidence interval, a
Monte-Carlo estimate of π, and a random walk (distribution, confidence,
montecarlo, randomwalk).
// ============================================================================
// probability.manic — a probability & sampling playground (stats)
// ----------------------------------------------------------------------------
// Four ideas on one stage, chaptered with section():
// 1. named DISTRIBUTIONS (uniform / exponential / binomial / poisson)
// 2. CONFIDENCE intervals (an estimate ± a margin)
// 3. MONTE-CARLO (estimate pi by throwing darts)
// 4. RANDOM WALK (a path that wanders)
// Each chapter reveals with untraced(tag) -> draw(tag) and clears with fade(tag).
// The seeded builtins (montecarlo, randomwalk) render identically every time.
// ============================================================================
title("A probability playground");
canvas("16:9");
// ---- 1. named distributions (a 2x2 gallery) ----
distribution(du, (cx - 350, cy - 120), "uniform", 2, 6); untraced(du);
distribution(de, (cx + 350, cy - 120), "exponential", 1); untraced(de);
distribution(db, (cx - 350, cy + 180), "binomial", 12, 0.4); untraced(db);
distribution(dp, (cx + 350, cy + 180), "poisson", 4); untraced(dp);
// ---- 2. a confidence interval ----
confidence(ci, (cx, cy), 50, 8, 25, 95, 620); untraced(ci); hidden(ci.estimate);
// ---- 3. monte-carlo pi ----
montecarlo(mc, (cx, cy - 10), 1200, 7, 210); untraced(mc);
// ---- 4. a random walk ----
randomwalk(rw, (cx, cy), 500, 4, 12); untraced(rw);
text(cap, (cx, h - 44), ""); size(cap, 22); color(cap, dim); display(cap);
section("Named distributions");
par { draw(du, 0.9); draw(de, 0.9); draw(db, 0.9); draw(dp, 0.9); }
say(cap, "flat, decaying, discrete counts — the classic shapes of chance", 0.5);
wait(2.2);
par { fade(du, 0.5); fade(de, 0.5); fade(db, 0.5); fade(dp, 0.5); }
wait(0.3);
say(cap, "", 0.2);
section("Confidence intervals");
draw(ci, 0.9); show(ci.estimate, 0.4);
say(cap, "an estimate is never exact — the interval says how sure we are", 0.5);
wait(2.2);
fade(ci, 0.6);
wait(0.3);
say(cap, "", 0.2);
section("Monte Carlo");
draw(mc, 1.2);
say(cap, "throw darts at random: the fraction landing in the circle gives pi", 0.5);
wait(2.4);
fade(mc, 0.6);
wait(0.3);
say(cap, "", 0.2);
section("Random walk");
draw(rw, 1.4);
say(cap, "each step a random direction — chance draws a wandering path", 0.5);
wait(2.2);
Machine learning
Small, deterministic models whose displayed values are computed rather than staged. The ML examples use progressive focus so forward values, supervised loss, reverse gradients, and parameter updates stay readable on one persistent network.
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
manic-ml-scalar-to-tensor
Start with one value, extend it into a vector, arrange values into a matrix, then stack channels into a rank-3 tensor. The only ML noun is tensor; ordinary Manic steps, arrows, reveals, and captions tell the complete dimensional story.
// manic-ml-scalar-to-tensor.manic — ML foundation story
// One value gains an axis, then rows, then stacked channels. The ML-specific
// surface is only tensor(...); ordinary Manic verbs own the explanation.
title("Manic ML — From Scalar to Tensor");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Build_the_dimensions safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
let cell = 38*u;
if h > 1.45*w {
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · DATA FOUNDATIONS");
text(headline, (cx, h*0.105), "One value gains dimensions");
text(caption, (cx, h*0.84), "Begin with one measured value.");
tensor(scalar, (cx, h*0.20), "7", cell, gold);
tensor(vector, (cx, h*0.35), "7 2 -1 4", cell, cyan);
tensor(matrix, (cx, h*0.53), "7 2 -1; 4 0 3; 1 5 6", cell, magenta);
tensor(volume, (cx, h*0.72), "7 2 -1; 4 0 3; 1 5 6 | 2 4 8; 1 3 9; 0 5 6", cell, lime);
text(scalarTitle, (cx, h*0.155), "SCALAR · RANK 0");
text(vectorTitle, (cx, h*0.295), "VECTOR · RANK 1");
text(matrixTitle, (cx, h*0.445), "MATRIX · RANK 2");
text(volumeTitle, (cx, h*0.625), "TENSOR · RANK 3");
arrow(grow1, (cx, h*0.245), (cx, h*0.285));
arrow(grow2, (cx, h*0.395), (cx, h*0.435));
arrow(grow3, (cx, h*0.585), (cx, h*0.615));
}
else if w > 1.25*h {
watermark(manicMark, (w*0.17, h*0.09), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · DATA FOUNDATIONS");
text(headline, (cx, h*0.14), "One value gains dimensions");
text(caption, (cx, h*0.82), "Begin with one measured value.");
tensor(scalar, (w*0.11, h*0.46), "7", cell, gold);
tensor(vector, (w*0.34, h*0.46), "7 2 -1 4", cell, cyan);
tensor(matrix, (w*0.61, h*0.46), "7 2 -1; 4 0 3; 1 5 6", cell, magenta);
tensor(volume, (w*0.86, h*0.46), "7 2 -1; 4 0 3; 1 5 6 | 2 4 8; 1 3 9; 0 5 6", cell, lime);
text(scalarTitle, (w*0.11, h*0.34), "SCALAR · RANK 0");
text(vectorTitle, (w*0.34, h*0.34), "VECTOR · RANK 1");
text(matrixTitle, (w*0.61, h*0.34), "MATRIX · RANK 2");
text(volumeTitle, (w*0.86, h*0.34), "TENSOR · RANK 3");
arrow(grow1, (w*0.17, h*0.46), (w*0.23, h*0.46));
arrow(grow2, (w*0.44, h*0.46), (w*0.51, h*0.46));
arrow(grow3, (w*0.70, h*0.46), (w*0.76, h*0.46));
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC ML · DATA FOUNDATIONS");
text(headline, (cx, h*0.17), "One value gains dimensions");
text(caption, (cx, h*0.84), "Begin with one measured value.");
tensor(scalar, (w*0.24, h*0.34), "7", cell, gold);
tensor(vector, (w*0.73, h*0.34), "7 2 -1 4", cell, cyan);
tensor(matrix, (w*0.24, h*0.65), "7 2 -1; 4 0 3; 1 5 6", cell, magenta);
tensor(volume, (w*0.73, h*0.65), "7 2 -1; 4 0 3; 1 5 6 | 2 4 8; 1 3 9; 0 5 6", cell, lime);
text(scalarTitle, (w*0.24, h*0.235), "SCALAR · RANK 0");
text(vectorTitle, (w*0.73, h*0.235), "VECTOR · RANK 1");
text(matrixTitle, (w*0.24, h*0.505), "MATRIX · RANK 2");
text(volumeTitle, (w*0.73, h*0.505), "TENSOR · RANK 3");
arrow(grow1, (w*0.34, h*0.34), (w*0.50, h*0.34));
arrow(grow2, (w*0.73, h*0.40), (w*0.36, h*0.56));
arrow(grow3, (w*0.35, h*0.65), (w*0.50, h*0.65));
}
size(kicker, 19*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 34*u); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 21*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
size(scalarTitle, 17*u); bold(scalarTitle); color(scalarTitle, dim); hidden(scalarTitle);
size(vectorTitle, 17*u); bold(vectorTitle); color(vectorTitle, dim); hidden(vectorTitle);
size(matrixTitle, 17*u); bold(matrixTitle); color(matrixTitle, dim); hidden(matrixTitle);
size(volumeTitle, 17*u); bold(volumeTitle); color(volumeTitle, dim); hidden(volumeTitle);
color(grow1, dim); stroke(grow1, 2.5*u); hidden(grow1);
color(grow2, dim); stroke(grow2, 2.5*u); hidden(grow2);
color(grow3, dim); stroke(grow3, 2.5*u); hidden(grow3);
hidden(scalar.cells); hidden(scalar.values); hidden(scalar.labels);
hidden(vector.cells); hidden(vector.values); hidden(vector.labels);
hidden(matrix.cells); hidden(matrix.values); hidden(matrix.labels);
hidden(volume.cells); hidden(volume.values); hidden(volume.labels);
step("scalar") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
show(scalarTitle, 0.30);
par {
show(scalar.cells, 0.55);
show(scalar.values, 0.55);
}
}
wait(0.55);
step("vector") {
par {
show(grow1, 0.35);
show(vectorTitle, 0.30);
show(vector.cells, 0.60);
show(vector.values, 0.60);
say(caption, "Repeat the value along one ordered axis: position now matters.", 0.40);
}
}
wait(0.60);
step("matrix") {
par {
show(grow2, 0.35);
show(matrixTitle, 0.30);
show(matrix.cells, 0.65);
show(matrix.values, 0.65);
say(caption, "Add rows to the columns: one axis becomes a two-dimensional matrix.", 0.40);
}
}
wait(0.65);
step("tensor") {
par {
show(grow3, 0.35);
show(volumeTitle, 0.30);
show(volume.cells, 0.70);
show(volume.values, 0.70);
say(caption, "Stack matrices as channels: the same values now carry depth and context.", 0.40);
}
}
wait(0.75);
step("one-family") {
pulse(volume.channel1, 0.75);
say(caption, "Scalar, vector, and matrix are all tensors—distinguished by their axes.", 0.45);
}
wait(1.50);
manic-ml-activation-focus
A focused ReLU lesson: draw the truthful activation curve, test one negative and one positive input, then connect the bend to nonlinearity. activation supplies the mathematics while core Manic owns the probes, guides, equation, and pacing.
// manic-ml-activation-focus.manic — one ML noun, one complete lesson
// `activation` supplies the truthful ReLU curve. Core Manic supplies the
// question, equation, input probes, local emphasis, timing, and takeaway.
title("Manic ML — Why ReLU Changes a Neuron");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=See_the_rule safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · ACTIVATION");
text(headline, (cx, h*0.11), "Why does a neuron need ReLU?");
text(caption, (cx, h*0.79), "A neuron first receives an unrestricted number.");
equation(rule, (cx, h*0.60), `\operatorname{ReLU}(x)=\max(0,x)`, 35*u);
activation(reluView, (cx, h*0.38), relu, w*0.74, h*0.22);
line(negativePath, (w*0.13, h*0.466), (cx, h*0.466));
line(positivePath, (cx, h*0.466), (w*0.87, h*0.27));
line(positiveGuide, (w*0.685, h*0.466), (w*0.685, h*0.368));
circle(negativeProbe, (w*0.315, h*0.466), 8*u);
circle(positiveProbe, (w*0.685, h*0.368), 8*u);
text(negativeNote, (w*0.27, h*0.525), "x = −2 → 0");
text(positiveNote, (w*0.73, h*0.525), "x = 2 → 2");
}
else if w > 1.25*h {
watermark(manicMark, (w*0.17, h*0.09), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · ACTIVATION");
text(headline, (cx, h*0.14), "Why does a neuron need ReLU?");
text(caption, (cx, h*0.82), "A neuron first receives an unrestricted number.");
equation(rule, (w*0.79, h*0.36), `\operatorname{ReLU}(x)=\max(0,x)`, 34*u);
activation(reluView, (w*0.38, h*0.49), relu, w*0.52, h*0.48);
line(negativePath, (w*0.12, h*0.677), (w*0.38, h*0.677));
line(positivePath, (w*0.38, h*0.677), (w*0.64, h*0.25));
line(positiveGuide, (w*0.51, h*0.677), (w*0.51, h*0.463));
circle(negativeProbe, (w*0.25, h*0.677), 8*u);
circle(positiveProbe, (w*0.51, h*0.463), 8*u);
text(negativeNote, (w*0.79, h*0.50), "x = −2 → 0");
text(positiveNote, (w*0.79, h*0.59), "x = 2 → 2");
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC ML · ACTIVATION");
text(headline, (cx, h*0.145), "Why does a neuron need ReLU?");
text(caption, (cx, h*0.82), "A neuron first receives an unrestricted number.");
equation(rule, (cx, h*0.67), `\operatorname{ReLU}(x)=\max(0,x)`, 34*u);
activation(reluView, (cx, h*0.40), relu, w*0.72, h*0.28);
line(negativePath, (w*0.14, h*0.509), (cx, h*0.509));
line(positivePath, (cx, h*0.509), (w*0.86, h*0.26));
line(positiveGuide, (w*0.68, h*0.509), (w*0.68, h*0.384));
circle(negativeProbe, (w*0.32, h*0.509), 8*u);
circle(positiveProbe, (w*0.68, h*0.384), 8*u);
text(negativeNote, (w*0.27, h*0.58), "x = −2 → 0");
text(positiveNote, (w*0.73, h*0.58), "x = 2 → 2");
}
size(kicker, 19*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 34*u); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 21*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
hidden(rule);
color(negativePath, magenta); stroke(negativePath, 5*u); glow(negativePath, 0.35); untraced(negativePath);
color(positivePath, cyan); stroke(positivePath, 5*u); glow(positivePath, 0.35); untraced(positivePath);
color(positiveGuide, dim); stroke(positiveGuide, 2*u); dashed(positiveGuide, 9*u, 7*u); untraced(positiveGuide);
color(negativeProbe, magenta); filled(negativeProbe); glow(negativeProbe, 0.55); hidden(negativeProbe);
color(positiveProbe, cyan); filled(positiveProbe); glow(positiveProbe, 0.55); hidden(positiveProbe);
size(negativeNote, 18*u); color(negativeNote, magenta); bold(negativeNote); hidden(negativeNote);
size(positiveNote, 18*u); color(positiveNote, cyan); bold(positiveNote); hidden(positiveNote);
untraced(reluView.axes);
untraced(reluView.curve);
hidden(reluView.label);
step("question") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
draw(reluView.axes, 0.75);
}
wait(0.55);
step("rule") {
show(rule, 0.55);
say(caption, "ReLU makes one transparent promise: return the larger of zero and x.", 0.40);
}
wait(0.55);
step("negative-input") {
par {
draw(negativePath, 0.75);
show(negativeProbe, 0.35);
show(negativeNote, 0.35);
say(caption, "Negative input is muted at zero; it cannot send negative evidence onward.", 0.40);
}
}
wait(0.65);
step("positive-input") {
par {
draw(positivePath, 0.85);
draw(positiveGuide, 0.55);
show(positiveProbe, 0.35);
show(positiveNote, 0.35);
say(caption, "Positive input passes through unchanged, preserving its strength.", 0.40);
}
}
wait(0.70);
step("activation") {
par {
draw(reluView.curve, 1.10);
show(reluView.label, 0.35);
say(caption, "That small bend gives a network a nonlinear decision boundary.", 0.40);
par { cam((cx, h*0.47), 0.70, smooth); zoom(1.10, 0.70, smooth); }
}
}
wait(0.75);
step("takeaway") {
par {
pulse(reluView.curve, 0.75);
say(caption, "ReLU does not invent a signal: it gates what the neuron already computed.", 0.45);
par { cam((cx, cy), 0.70, smooth); zoom(1.0, 0.70, smooth); }
}
}
wait(1.50);
manic-ml-forward-pass
A creator-first neural-network story: introduce the ReLU activation, reveal a seeded 3→6→4→3 model, then follow one real forward pass into softmax probabilities. network, activation, and forward provide the truthful structure while ordinary named steps, captions, and Creator branding tell the lesson.
// manic-ml-forward-pass.manic — ML1 creator proof
// A real deterministic network computes one prediction. The story keeps the
// model persistent and reveals only the active computation instead of flashing
// every connection at once.
title("Manic ML — A Forward Pass You Can Follow");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=See_the_computation safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
// One story reflows to portrait, feed, square, and landscape. The portrait
// network narrows to reserve a clean probability lane at the right.
if h > 1.45*w {
watermark(manicMark, (w*0.18, h*0.075), "Made With Manic");
text(kicker, (cx, h*0.08), "MANIC ML · FORWARD PASS");
text(headline, (cx, h*0.13), "How does a network choose?");
text(caption, (cx, h*0.72), "First, a neuron keeps positive evidence and removes negative evidence.");
activation(reluView, (cx, h*0.40), relu, w*0.72, h*0.24);
network(model, (cx, h*0.42), "3 6 4 3", "relu tanh softmax", w*0.55, h*0.32, 21);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.18, h*0.10), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · FORWARD PASS");
text(headline, (cx, h*0.14), "How does a network choose?");
text(caption, (cx, h*0.83), "First, a neuron keeps positive evidence and removes negative evidence.");
activation(reluView, (cx, h*0.49), relu, w*0.42, h*0.36);
network(model, (cx, h*0.47), "3 6 4 3", "relu tanh softmax", w*0.64, h*0.39, 21);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.07), "MANIC ML · FORWARD PASS");
text(headline, (cx, h*0.13), "How does a network choose?");
text(caption, (cx, h*0.82), "First, a neuron keeps positive evidence and removes negative evidence.");
activation(reluView, (cx, h*0.43), relu, w*0.68, h*0.30);
network(model, (cx, h*0.45), "3 6 4 3", "relu tanh softmax", w*0.64, h*0.40, 21);
}
size(kicker, 20*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 36*u); bold(headline); hidden(headline);
size(caption, 22*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
// A truthful ReLU curve introduces the operation used by the hidden layer.
hidden(reluView);
untraced(reluView.axes);
untraced(reluView.curve);
// 3 inputs → 6 ReLU units → 4 tanh units → 3 softmax probabilities.
// Seed 21 makes the educational model reproducible across every render.
hidden(model);
step("activation") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
show(reluView, 0.55);
draw(reluView.axes, 0.70);
draw(reluView.curve, 1.20);
}
wait(0.75);
step("meet-the-network") {
fade(reluView, 0.45);
show(model, 0.65);
say(caption, "The same operation now lives inside a small, deterministic network.", 0.45);
}
wait(0.65);
step("forward-pass") {
par {
forward(model, "0.15 0.92 0.38", 4.20, smooth);
say(caption, "Follow the bright path: inputs become evidence, then probabilities.", 0.45);
seq {
par { cam((w*0.24, h*0.47), 0.55, smooth); zoom(1.06, 0.55, smooth); }
wait(0.55);
cam((cx, h*0.47), 0.80, smooth);
wait(0.55);
cam((w*0.76, h*0.47), 0.80, smooth);
wait(0.40);
par { cam((cx, cy), 0.55, smooth); zoom(1.0, 0.55, smooth); }
}
}
}
wait(0.90);
step("takeaway") {
pulse(model.output, 0.75);
say(caption, "The picture is driven by the computed values—not a decorative animation.", 0.45);
}
wait(1.60);
manic-ml-learning-step
One complete supervised learning beat on a persistent network: predict, compare with a target using cross-entropy, send exact reverse-mode gradients backward, update every weight and bias, then restore the exact pre-update checkpoint. loss, backward, checkpoint, update, and restore expose both learning and truthful rollback without pretending that rollback is general machine unlearning.
// manic-ml-learning-step.manic — ML2 creator proof
// One persistent network predicts, measures its mistake, sends exact gradients
// backward, changes its parameters, then rolls that one saved change back.
// No layer is cleared and redrawn; rollback is not claimed as full unlearning.
title("Manic ML — How One Mistake Becomes Learning");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=See_the_learning safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.13, h*0.075), "Made With Manic");
text(kicker, (cx, h*0.08), "MANIC ML · ONE LEARNING STEP");
text(headline, (cx, h*0.13), "How does a model learn?");
text(caption, (cx, h*0.72), "First the network predicts from the current weights.");
network(model, (cx, h*0.42), "3 6 4 3", "relu tanh softmax", w*0.55, h*0.32, 21);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.18, h*0.10), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · ONE LEARNING STEP");
text(headline, (cx, h*0.14), "How does a model learn?");
text(caption, (cx, h*0.83), "First the network predicts from the current weights.");
network(model, (cx, h*0.47), "3 6 4 3", "relu tanh softmax", w*0.64, h*0.39, 21);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.07), "MANIC ML · ONE LEARNING STEP");
text(headline, (cx, h*0.13), "How does a model learn?");
text(caption, (cx, h*0.82), "First the network predicts from the current weights.");
network(model, (cx, h*0.45), "3 6 4 3", "relu tanh softmax", w*0.64, h*0.40, 21);
}
size(kicker, 20*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 35*u); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 22*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
hidden(model);
step("question") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
show(model, 0.65);
}
wait(0.55);
step("predict") {
par {
forward(model, "0.15 0.92 0.38", 3.20, smooth);
say(caption, "A forward pass turns the input into three probabilities.", 0.40);
}
}
wait(0.55);
step("measure-the-mistake") {
par {
loss(model, "1 0 0", crossentropy, 1.50, smooth);
say(caption, "The correct answer is class 1. Cross-entropy measures the mismatch.", 0.40);
}
}
wait(0.55);
step("send-credit-backward") {
par {
backward(model, 3.20, smooth);
say(caption, "The gradient carries responsibility backward through the same connections.", 0.40);
seq {
par { cam((w*0.76, h*0.47), 0.40, smooth); zoom(1.08, 0.40, smooth); }
wait(0.30);
cam((cx, h*0.47), 0.55, smooth);
wait(0.25);
cam((w*0.24, h*0.47), 0.55, smooth);
wait(0.25);
par { cam((cx, cy), 0.45, smooth); zoom(1.0, 0.45, smooth); }
}
}
}
wait(0.55);
// A zero-duration authored checkpoint captures the exact pre-update weights,
// prediction, target, and loss. It does not add a hidden runtime state.
checkpoint(beforeUpdate, model);
step("learn") {
par {
update(model, 0.18, 2.30, smooth);
say(caption, "Each parameter moves opposite its gradient; the same input is computed again.", 0.40);
}
}
wait(0.70);
step("unlearn") {
par {
restore(model, beforeUpdate, 2.30, smooth);
say(caption, "Exact rollback restores the saved parameters and their earlier prediction.", 0.40);
seq {
par { cam((w*0.24, h*0.47), 0.40, smooth); zoom(1.08, 0.40, smooth); }
wait(0.30);
cam((cx, h*0.47), 0.55, smooth);
wait(0.25);
cam((w*0.76, h*0.47), 0.55, smooth);
wait(0.25);
par { cam((cx, cy), 0.45, smooth); zoom(1.0, 0.45, smooth); }
}
}
}
wait(0.70);
step("takeaway") {
pulse(model.output, 0.75);
say(caption, "This undoes one saved update. Dataset-level unlearning is a different process.", 0.45);
}
wait(1.50);
manic-ml-cnn-edge-story
A tiny image becomes an edge-response feature map and then a pooled summary. The shared scan choreography keeps each receptive field, kernel/operator, arithmetic line, and destination cell synchronized while tensor, kernel, convolve, and pool supply the exact values.
// manic-ml-cnn-edge-story.manic — ML3 creator proof
// A tiny image becomes a feature map and then a pooled summary. Every number is
// computed by Manic; scan coordinates the receptive field and destination.
title("Manic ML — How a CNN Finds an Edge");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=See_the_feature safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
let cell = 48*u;
if h > 1.45*w {
watermark(manicMark, (w*0.13, h*0.075), "Made With Manic");
text(kicker, (cx, h*0.08), "MANIC ML · CONVOLUTION");
text(headline, (cx, h*0.13), "How does a CNN see edges?");
text(caption, (cx, h*0.72), "Start with a tiny image: bright cells form a simple shape.");
tensor(image, (w*0.28, h*0.35), "0 0 0 0 0; 0 0 1 1 0; 0 1 1 1 0; 0 0 1 1 0; 0 0 0 0 0", cell, cyan);
kernel(edge, (w*0.72, h*0.35), "-1 0 1; -2 0 2; -1 0 1", cell, magenta);
convolve(feature, image, edge, (w*0.34, h*0.54), 1, 0, 0, relu, cell);
pool(pooled, feature, (w*0.72, h*0.54), max, 2, 1, 0, cell);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.18, h*0.10), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · CONVOLUTION");
text(headline, (cx, h*0.14), "How does a CNN see edges?");
text(caption, (cx, h*0.82), "Start with a tiny image: bright cells form a simple shape.");
tensor(image, (w*0.17, h*0.47), "0 0 0 0 0; 0 0 1 1 0; 0 1 1 1 0; 0 0 1 1 0; 0 0 0 0 0", cell, cyan);
kernel(edge, (w*0.42, h*0.47), "-1 0 1; -2 0 2; -1 0 1", cell, magenta);
convolve(feature, image, edge, (w*0.68, h*0.47), 1, 0, 0, relu, cell);
pool(pooled, feature, (w*0.89, h*0.47), max, 2, 1, 0, cell);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.07), "MANIC ML · CONVOLUTION");
text(headline, (cx, h*0.13), "How does a CNN see edges?");
text(caption, (cx, h*0.82), "Start with a tiny image: bright cells form a simple shape.");
tensor(image, (w*0.18, h*0.44), "0 0 0 0 0; 0 0 1 1 0; 0 1 1 1 0; 0 0 1 1 0; 0 0 0 0 0", cell, cyan);
kernel(edge, (w*0.45, h*0.44), "-1 0 1; -2 0 2; -1 0 1", cell, magenta);
convolve(feature, image, edge, (w*0.70, h*0.44), 1, 0, 0, relu, cell);
pool(pooled, feature, (w*0.89, h*0.44), max, 2, 1, 0, cell);
}
size(kicker, 20*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 35*u); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 22*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
hidden(image); hidden(edge); hidden(feature); hidden(pooled);
step("pixels") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
show(image, 0.65);
}
wait(0.55);
step("edge-detector") {
show(edge, 0.55);
say(caption, "This 3×3 kernel compares the left side of each patch with the right.", 0.40);
}
wait(0.55);
step("feature-map") {
show(feature, 0.50);
say(caption, "The feature map begins quiet; each destination waits for one receptive field.", 0.40);
}
wait(0.35);
step("convolution-scan") {
par {
scan(feature, 4.80, smooth);
say(caption, "The same kernel slides, multiplies, sums, applies ReLU, and writes one cell.", 0.40);
seq {
par { cam((w*0.38, h*0.45), 0.75, smooth); zoom(1.10, 0.75, smooth); }
wait(2.95);
par { cam((cx, cy), 0.85, smooth); zoom(1.0, 0.85, smooth); }
}
}
}
wait(0.60);
step("pooling-map") {
show(pooled, 0.50);
say(caption, "Max pooling asks a simpler question: where is the strongest local evidence?", 0.40);
}
wait(0.40);
step("pooling-scan") {
par {
scan(pooled, 3.40, smooth);
say(caption, "Each 2×2 window keeps its first maximum; ties are deterministic.", 0.40);
}
}
wait(0.70);
step("takeaway") {
pulse(pooled.cells, 0.75);
say(caption, "A CNN builds meaning locally: pixels → feature responses → compact evidence.", 0.45);
}
wait(1.50);
manic-ml-token-embedding
A sentence becomes honest word tokens, stable seeded educational lookup vectors, exact sinusoidal positions, and model-input vectors. Repeated words prove that token identity keeps one base embedding while position distinguishes each occurrence.
// manic-ml-token-embedding.manic — ML5 acceptance story
// A repeated word keeps one seeded educational token embedding, while exact
// sinusoidal position makes each occurrence a different model input.
title("Manic ML — From Words to Positioned Embeddings");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Follow_the_representation safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.16, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.055), "MANIC ML · EMBEDDINGS");
text(headline, (cx, h*0.095), "A word needs meaning—and a place");
text(caption, (cx, h*0.84), "Start with text. Keep every boundary and every number inspectable.");
tokenize(words, (cx, h*0.19), "the cat chased the cat", word, w*0.80);
embedding(context, words, (cx, h*0.50), "seeded 6 37", sinusoidal, w*0.90, h*0.48);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.16, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.07), "MANIC ML · EMBEDDINGS");
text(headline, (cx, h*0.125), "A word needs meaning—and a place");
text(caption, (cx, h*0.84), "Start with text. Keep every boundary and every number inspectable.");
tokenize(words, (cx, h*0.23), "the cat chased the cat", word, w*0.68);
embedding(context, words, (cx, h*0.54), "seeded 6 37", sinusoidal, w*0.90, h*0.48);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC ML · EMBEDDINGS");
text(headline, (cx, h*0.17), "A word needs meaning—and a place");
text(caption, (cx, h*0.83), "Start with text. Keep every boundary and every number inspectable.");
tokenize(words, (cx, h*0.30), "the cat chased the cat", word, w*0.76);
embedding(context, words, (cx, h*0.60), "seeded 6 37", sinusoidal, w*0.90, h*0.38);
}
size(kicker, 18*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 32*u); bold(headline); wrap(headline, w*0.84); hidden(headline);
size(caption, 20*u); color(caption, dim); wrap(caption, w*0.82); hidden(caption);
hidden(words);
hidden(context);
step("sentence") {
show(kicker, 0.30);
show(headline, 0.42);
show(words.labels, 0.35);
show(words.source, 0.40);
show(caption, 0.35);
}
wait(0.55);
step("tokens") {
par {
show(words.tokens, 0.75);
show(words.indices, 0.75);
say(caption, "Word tokenization turns the sentence into five ordered identities.", 0.38);
}
}
wait(0.60);
step("lookup") {
fade(words, 0.35);
par {
show(context.labels, 0.40);
show(context.tokens, 0.55);
show(context.vectors, 0.95);
say(caption, "A seeded educational lookup gives each token identity one six-number vector.", 0.40);
}
}
wait(0.60);
step("same-word") {
par {
pulse(context.row1, 0.70);
pulse(context.row4, 0.70);
say(caption, "Both copies of cat reuse the same base embedding. The lookup depends on the token—not its location.", 0.42);
}
}
wait(0.65);
step("position") {
par {
show(context.positions, 1.00);
show(context.operators, 0.45);
say(caption, "Sinusoidal position adds a deterministic coordinate for places 0 through 4.", 0.42);
}
}
wait(0.65);
step("model-input") {
par {
show(context.combined, 1.00);
say(caption, "Token vector plus position becomes the model input. The repeated word now carries two different locations.", 0.42);
}
}
wait(0.70);
step("takeaway") {
par {
pulse(context.row1, 0.72);
pulse(context.row4, 0.72);
say(caption, "Meaning says what the token is. Position says where this occurrence belongs.", 0.42);
}
}
wait(1.40);
manic-ml-transformer-attention
One token finds context through a real scaled dot-product self-attention head. Explicit embeddings become Q/K/V, one selected softmax row drives the weighted value mix and residual, and a deterministic educational output projection produces exact top-k probabilities without pretending to be a pretrained language model.
// manic-ml-transformer-attention.manic — ML4 acceptance story
// Explicit token embeddings become Q/K/V, one query row becomes normalized
// attention weights, values mix, a residual is added, and a real output
// projection produces top-k probabilities.
title("Manic ML — One Transformer Attention Head");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Follow_the_attention safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · SELF-ATTENTION");
text(headline, (cx, h*0.105), "One token finds context");
text(caption, (cx, h*0.84), "Start with explicit token embeddings—not decorative wires.");
attention(head, (cx, h*0.38), "Art | ificial | intelligence | transforms | business",
"1 0.2 -0.4 0.7; 0.8 0.1 -0.3 0.6; -0.2 1 0.5 0.3; 0.1 0.6 0.9 -0.2; 0.7 -0.1 0.4 1",
w*0.82, h*0.40, 23);
topk(next, head, 3, (cx, h*0.71), "business | work | world | industry | future | people", 4, w*0.72, h*0.18, 29);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.17, h*0.09), "Made With Manic");
text(kicker, (cx, h*0.075), "MANIC ML · SELF-ATTENTION");
text(headline, (cx, h*0.14), "One token finds context");
text(caption, (cx, h*0.83), "Start with explicit token embeddings—not decorative wires.");
attention(head, (w*0.40, h*0.48), "Art | ificial | intelligence | transforms | business",
"1 0.2 -0.4 0.7; 0.8 0.1 -0.3 0.6; -0.2 1 0.5 0.3; 0.1 0.6 0.9 -0.2; 0.7 -0.1 0.4 1",
w*0.68, h*0.58, 23);
topk(next, head, 3, (w*0.84, h*0.52), "business | work | world | industry | future | people", 4, w*0.26, h*0.34, 29);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC ML · SELF-ATTENTION");
text(headline, (cx, h*0.13), "One token finds context");
text(caption, (cx, h*0.84), "Start with explicit token embeddings—not decorative wires.");
attention(head, (cx, h*0.39), "Art | ificial | intelligence | transforms | business",
"1 0.2 -0.4 0.7; 0.8 0.1 -0.3 0.6; -0.2 1 0.5 0.3; 0.1 0.6 0.9 -0.2; 0.7 -0.1 0.4 1",
w*0.84, h*0.42, 23);
topk(next, head, 3, (cx, h*0.72), "business | work | world | industry | future | people", 4, w*0.72, h*0.18, 29);
}
size(kicker, 19*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 34*u); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 21*u); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
hidden(head); hidden(next);
step("tokens") {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.40);
show(head.labels, 0.40);
show(head.tokens, 0.60);
}
wait(0.55);
step("projections") {
par {
show(head.q, 0.55);
show(head.k, 0.55);
show(head.v, 0.55);
say(caption, "Each embedding is projected into a Query, Key, and Value vector.", 0.40);
}
}
wait(0.60);
step("scores") {
show(head.matrix, 0.70);
say(caption, "Scaled dot products become one softmax row: every weight is positive and the row sums to 100%.", 0.45);
}
wait(0.65);
step("attend") {
par {
attend(head, 3, 5.20, smooth);
say(caption, "Focus intelligence: Q asks, K measures relevance, and weighted V carries the context.", 0.45);
seq {
par { cam((cx, h*0.46), 0.75, smooth); zoom(1.07, 0.75, smooth); }
wait(3.45);
par { cam((cx, cy), 0.85, smooth); zoom(1.0, 0.85, smooth); }
}
}
}
wait(0.75);
step("prediction") {
show(next, 0.70);
say(caption, "The residual plus attention mix enters an output projection and a truthful softmax ranking.", 0.45);
}
wait(0.80);
step("takeaway") {
pulse(next.rank0, 0.75);
say(caption, "Attention is selective information flow—not every connection shouting at once.", 0.45);
}
wait(1.50);
manic-ml-transformer-block
One persistent token lane crosses a complete deterministic transformer block: two causal attention heads, concatenation and output projection, both residual/norm stages, a GELU MLP, and exact settled outputs. transformer owns the computation while encode provides a smooth directly seekable explanation.
// manic-ml-transformer-block.manic — ML6 acceptance story
// One persistent token lane passes through multi-head causal attention,
// concatenation, two residual paths, pre-normalization, and a GELU MLP.
title("Manic ML — Inside One Transformer Block");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Follow_the_computation safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · TRANSFORMER BLOCK");
text(headline, (cx, h*0.105), "Inside one transformer block");
text(caption, (cx, h*0.84), "Follow one token lane through every computed stage.");
tokenize(words, (cx, h*0.15), "the cat slept because it dreamed", word, w*0.82);
embedding(context, words, (cx, h*0.41), "seeded 6 37", sinusoidal, w*0.90, h*0.38);
transformer(block, context, (cx, h*0.49), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.90, h*0.56);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.17, h*0.09), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · TRANSFORMER BLOCK");
text(headline, (cx, h*0.14), "Inside one transformer block");
text(caption, (cx, h*0.82), "Follow one token lane through every computed stage.");
tokenize(words, (cx, h*0.22), "the cat slept because it dreamed", word, w*0.72);
embedding(context, words, (cx, h*0.54), "seeded 6 37", sinusoidal, w*0.90, h*0.48);
transformer(block, context, (cx, h*0.52), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.92, h*0.62);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.06), "MANIC ML · TRANSFORMER BLOCK");
text(headline, (cx, h*0.13), "Inside one transformer block");
text(caption, (cx, h*0.83), "Follow one token lane through every computed stage.");
tokenize(words, (cx, h*0.23), "the cat slept because it dreamed", word, w*0.78);
embedding(context, words, (cx, h*0.52), "seeded 6 37", sinusoidal, w*0.90, h*0.44);
transformer(block, context, (cx, h*0.52), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.90, h*0.57);
}
size(kicker, 18*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 30*u); bold(headline); wrap(headline, w*0.86); hidden(headline);
size(caption, 19*u); color(caption, dim); wrap(caption, w*0.84); hidden(caption);
hidden(words);
hidden(context);
hidden(block);
step("tokens") {
par {
show(kicker, 0.28);
show(headline, 0.42);
show(words.labels, 0.30);
show(words.source, 0.36);
show(words.tokens, 0.70);
show(words.indices, 0.70);
show(caption, 0.32);
say(caption, "Six ordered tokens begin as one persistent lane.", 0.36);
}
}
wait(0.55);
step("model-input") {
seq {
fade(words, 0.32);
par {
show(context.labels, 0.32);
show(context.tokens, 0.42);
show(context.vectors, 0.82);
say(caption, "Each identity receives a stable lookup vector.", 0.36);
}
par {
show(context.positions, 0.72);
show(context.operators, 0.34);
show(context.combined, 0.78);
say(caption, "Position is added exactly. These six vectors are the block input.", 0.38);
}
}
}
wait(0.60);
step("encode") {
seq {
par {
fade(context, 0.34);
fade(caption, 0.24);
}
par {
encode(block, 6.2, smooth);
seq {
par { cam((cx - w*0.25, h*0.52), 0.75, smooth); zoom(1.08, 0.75, smooth); }
wait(1.35);
cam((cx + w*0.17, h*0.52), 1.15, smooth);
wait(1.50);
par { cam((cx, cy), 0.85, smooth); zoom(1.0, 0.85, smooth); }
}
}
}
}
wait(0.70);
step("takeaway") {
seq {
show(caption, 0.20);
par {
pulse(block.residual1, 0.72);
pulse(block.residual2, 0.72);
pulse(block.output, 0.72);
say(caption, "Attention shares context. The MLP reshapes each token. Residuals keep its identity continuous.", 0.42);
}
}
}
wait(1.35);
manic-ml-logits-sampling
The same educational LM projection is viewed at low and high temperature before greedy and top-p decoding make their support explicit. logits computes every candidate from the final hidden row; sample filters, renormalizes, and selects one reproducible next token without pretending to run a pretrained model.
// manic-ml-logits-sampling.manic — ML7 acceptance story
// The same LM projection is viewed at two temperatures, then top-p sampling
// filters, renormalizes, and makes one reproducible next-token choice.
title("Manic ML — How a Transformer Chooses the Next Token");
canvas("16:9");
template("mono");
creator(me, "@anish2good name=Manic_ML tagline=Models_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Follow_the_computation safe=clean");
socials(me);
let u = (w+h-abs(w-h))/1440;
if h > 1.45*w {
watermark(manicMark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.07), "MANIC ML · LOGITS → PROBABILITIES → TOKEN");
text(headline, (cx, h*0.115), "How does a transformer choose its next word?");
text(caption, (cx, h*0.83), "One hidden vector. One full distribution. One reproducible choice.");
tokenize(words, (cx, h*0.17), "the model learned to", word, w*0.78);
embedding(context, words, (cx, h*0.42), "seeded 6 37", sinusoidal, w*0.88, h*0.34);
transformer(block, context, (cx, h*0.47), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.90, h*0.50);
logits(cool, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 0.55, w*0.86, h*0.54, 73);
logits(warm, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 1.45, w*0.86, h*0.54, 73);
}
else if w > 1.25*h {
watermark(manicMark, (w*0.17, h*0.09), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · LOGITS → PROBABILITIES → TOKEN");
text(headline, (cx, h*0.13), "How does a transformer choose its next word?");
text(caption, (cx, h*0.82), "One hidden vector. One full distribution. One reproducible choice.");
tokenize(words, (cx, h*0.21), "the model learned to", word, w*0.64);
embedding(context, words, (cx, h*0.50), "seeded 6 37", sinusoidal, w*0.88, h*0.42);
transformer(block, context, (cx, h*0.50), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.90, h*0.58);
logits(cool, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 0.55, w*0.78, h*0.54, 73);
logits(warm, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 1.45, w*0.78, h*0.54, 73);
}
else {
watermark(manicMark, (w*0.18, h*0.08), "Made With Manic");
text(kicker, (cx, h*0.065), "MANIC ML · LOGITS → PROBABILITIES → TOKEN");
text(headline, (cx, h*0.155), "How does a transformer choose its next word?");
text(caption, (cx, h*0.82), "One hidden vector. One full distribution. One reproducible choice.");
tokenize(words, (cx, h*0.21), "the model learned to", word, w*0.70);
embedding(context, words, (cx, h*0.50), "seeded 6 37", sinusoidal, w*0.88, h*0.40);
transformer(block, context, (cx, h*0.50), "heads=2 mask=causal mlp=12 activation=gelu norm=pre dropout=0 mode=inference seed=41", w*0.90, h*0.55);
logits(cool, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 0.55, w*0.82, h*0.54, 73);
logits(warm, block, 4, (cx, h*0.51), "reason | predict | learn | adapt | explain | .", 1.45, w*0.82, h*0.54, 73);
}
size(kicker, 17*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88); hidden(headline);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86); hidden(caption);
hidden(words);
hidden(context);
hidden(block);
hidden(cool);
hidden(warm);
step("prompt") {
par {
show(kicker, 0.28);
show(headline, 0.42);
show(words.labels, 0.28);
show(words.source, 0.34);
show(words.tokens, 0.62);
show(words.indices, 0.62);
show(caption, 0.28);
say(caption, "The prompt becomes an ordered token lane.", 0.34);
}
}
wait(0.45);
step("hidden-state") {
seq {
fade(words, 0.28);
par {
show(block.output, 0.70);
show(block.labels, 0.26);
say(caption, "The transformer MLP ends at a hidden representation—not probabilities.", 0.40);
}
pulse(block.output, 0.72);
}
}
wait(0.45);
step("cool-temperature") {
seq {
fade(block, 0.30);
sample(cool, "greedy", 3.4, smooth);
say(caption, "Low temperature sharpens every candidate. Greedy keeps only the maximum.", 0.42);
}
}
wait(0.65);
step("warm-temperature") {
seq {
fade(cool, 0.30);
sample(warm, "top-p 0.90 seed=17", 3.8, smooth);
say(caption, "Higher temperature spreads the full softmax. Top-p keeps the smallest 90% nucleus, renormalizes it, then samples.", 0.46);
}
}
wait(0.75);
step("takeaway") {
par {
pulse(warm.temperature, 0.70);
pulse(warm.probabilities, 0.70);
say(caption, "Temperature reshapes probability. Sampling turns that distribution into one reproducible next token.", 0.44);
}
}
wait(1.30);
Physics — simulations
Each simulation is pre-simulated with RK4 at build time — deterministic and replayable — and its parts are ordinary manic entities the whole language composes with. The phase / time / well / energy views are optional and generic: any sim inherits them.
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
pendulum
One pendulum shown four ways from a single deterministic swing: the motion (with a
velocity arrow + KE/PE bars), the phase portrait (θ vs ω), a time series, the
potential-energy well, and energy over time (pendulum + phase/timegraph/
well/energygraph + swing).
// ============================================================================
// pendulum.manic — the physics kit's first sim, seen FOUR ways (Layer 1)
// ----------------------------------------------------------------------------
// `pendulum(id, [center], [length], [angle0], [unit], [damping])` builds a
// pendulum from its physics — PRE-SIMULATED with RK4 at build time
// (deterministic). Only `id` is required. The OPTIONAL, generic view builtins
// render the SAME simulation as math panels and all animate together on `swing`:
// · phase(id,(cx,cy),[size]) — phase portrait (θ vs ω): a closed loop
// · timegraph(id,(cx,cy),[size]) — θ(t) & ω(t) with a sweep line
// · well(id,(cx,cy),[size]) — energy well U(θ), bob = ball in a bowl
// · energygraph(id,(cx,cy),[size]) — KE / PE / total over time
//
// TO ADAPT: add damping (6th arg) and watch the phase loop spiral inward, the
// ball settle to the bottom of the well, and total energy decay — e.g.
// `pendulum(p, (250,220), 1.2, 55, 105, 0.5)`.
// ============================================================================
title("Pendulum — one swing, four views");
canvas("16:9");
text(hdr, (cx, 40), "One pendulum, four views");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the physical sim (left), with velocity arrow + KE/PE bars
pendulum(p, (250, 220), 1.2, 55, 105);
untraced(p.path);
// four math views of the SAME simulation, 2×2 on the right
phase(p, (715, 165), 90); // θ vs ω — a closed loop
timegraph(p, (1000, 165), 90); // θ(t), ω(t) with a sweep line
well(p, (715, 455), 90); // U(θ) with the bob as a rolling ball
energygraph(p, (1000, 455), 90); // KE / PE / total over time
text(cap, (cx, h - 30), "sim · phase portrait · time series · energy well · energy over time — all from one pre-simulated swing");
size(cap, 16); color(cap, dim); display(cap);
draw(p.path, 0.8);
swing(p, 10); // every panel animates in lockstep
second-law-thermodynamics
Why entropy only grows, in five vignettes: gas particles mixing into far more microstates, heat flowing until two sides equalize, free expansion into new volume, a heat engine that must dump waste heat, and the reversible-vs-irreversible limit.
// ============================================================================
// second-law-thermodynamics.manic — entropy, irreversibility, and time's arrow
// ----------------------------------------------------------------------------
// Engine-test recreation of engine-test-4.mp4. The subject is thermodynamics,
// but the vocabulary stays generic: persistent particles, authored layouts,
// plots, counters, arrows, LaTeX, and ordinary timeline composition.
// ============================================================================
title("Second Law of Thermodynamics");
canvas("9:16");
template("mono");
watermark(manicMark, (150, 145), "Made With Manic");
let cold = 194;
let pink = 326;
let hot = 40;
let blue = 226;
let green = 154;
let red = 10;
// ---------------------------------------------------------------------------
// 1 · MIXING MULTIPLIES THE NUMBER OF POSSIBLE MICROSTATES
// ---------------------------------------------------------------------------
equation(law, (540, 175), `S=k_B\ln\Omega`, 58);
rect(mixBox, (540, 520), 720, 280);
outline(mixBox, dim); outlined(mixBox); stroke(mixBox, 3);
rect(mixLeft, (360, 520), 350, 260); hidden(mixLeft);
rect(mixRight, (720, 520), 350, 260); hidden(mixRight);
line(mixDivider, (540, 390), (540, 650)); color(mixDivider, dim); stroke(mixDivider, 2);
particles(mixCold, mixLeft, 36, 6, 17);
particles(mixWarm, mixRight, 36, 6, 29);
hue(mixCold, cold, 0.72, 0.68); hue(mixWarm, pink, 0.72, 0.68);
glow(mixCold, 0.8); glow(mixWarm, 0.8);
text(mixCaption, (540, 745), "Mixing creates overwhelmingly more possible states");
size(mixCaption, 27); color(mixCaption, dim);
line(mixX, (260, 1040), (570, 1040)); line(mixY, (260, 1040), (260, 840));
color(mixX, dim); color(mixY, dim); stroke(mixX, 2); stroke(mixY, 2);
plot(mixCurve, (260, 1040), 78, 175, "1-exp(-2*x)", (0, 3.6));
hue(mixCurve, blue, 0.55, 0.68); stroke(mixCurve, 4); untraced(mixCurve);
dot(mixMarker, (260, 1040), 7); color(mixMarker, fg); glow(mixMarker, 1.0);
equation(mixDelta, (385, 815), `\Delta S/(Nk_B\ln 2)`, 27); color(mixDelta, dim);
counter(mixCount, (735, 945), 0, 1, "log₁₀(Ω/Ω₀) = ", "");
size(mixCount, 28); hue(mixCount, hot, 0.45, 0.72);
tag(mixBox, s1); tag(mixDivider, s1);
tag(mixCold, s1); tag(mixWarm, s1); tag(mixCaption, s1);
tag(mixX, s1); tag(mixY, s1); tag(mixCurve, s1); tag(mixMarker, s1); tag(mixDelta, s1); tag(mixCount, s1);
// ---------------------------------------------------------------------------
// 2 · HEAT FLOW MAKES TOTAL ENTROPY INCREASE
// ---------------------------------------------------------------------------
rect(hotBox, (315, 500), 260, 240); rect(coldBox, (765, 500), 260, 240);
outline(hotBox, dim); outline(coldBox, dim); outlined(hotBox); outlined(coldBox);
stroke(hotBox, 3); stroke(coldBox, 3);
particles(hotMatter, hotBox, 26, 6, 41); particles(coldMatter, coldBox, 26, 6, 73);
hue(hotMatter, hot, 0.66, 0.68); hue(coldMatter, blue, 0.66, 0.68);
link(heatLink, hotBox, coldBox); hue(heatLink, blue, 0.46, 0.62); stroke(heatLink, 4);
hidden(heatLink);
equation(hotLabel, (315, 325), `T_h`, 36); equation(coldLabel, (765, 325), `T_c`, 36);
hue(hotLabel, hot, 0.58, 0.72); hue(coldLabel, blue, 0.58, 0.72);
counter(hotTemp, (315, 680), 403, 0, "", " K");
counter(coldTemp, (765, 680), 397, 0, "", " K");
hue(hotTemp, hot, 0.55, 0.70); hue(coldTemp, blue, 0.55, 0.70);
text(flowCaption, (540, 780), "Heat flows until both sides reach one temperature");
size(flowCaption, 27); color(flowCaption, dim);
line(flowX, (305, 1130), (650, 1130)); line(flowY, (305, 1130), (305, 880));
color(flowX, dim); color(flowY, dim); stroke(flowX, 2); stroke(flowY, 2);
plot(flowCurve, (305, 1130), 78, 215, "1-exp(-2.4*x)", (0, 3.6));
hue(flowCurve, blue, 0.56, 0.70); stroke(flowCurve, 4); untraced(flowCurve);
dot(flowMarker, (305, 1130), 7); color(flowMarker, fg); glow(flowMarker, 1.0);
equation(flowS, (430, 855), `S_{\mathrm{tot}}`, 30); color(flowS, dim);
tag(hotBox, s2); tag(coldBox, s2); tag(hotMatter, s2); tag(coldMatter, s2);
tag(hotLabel, s2); tag(coldLabel, s2); tag(hotTemp, s2); tag(coldTemp, s2);
tag(flowCaption, s2); tag(flowX, s2); tag(flowY, s2); tag(flowCurve, s2); tag(flowMarker, s2); tag(flowS, s2);
hidden(s2);
// ---------------------------------------------------------------------------
// 3 · FREE EXPANSION: ONE GAS, TWICE THE VOLUME
// ---------------------------------------------------------------------------
rect(expandBox, (540, 520), 720, 280); outline(expandBox, dim); outlined(expandBox); stroke(expandBox, 3);
rect(expandLeft, (360, 520), 350, 260); hidden(expandLeft);
line(expandDivider, (540, 390), (540, 650)); color(expandDivider, dim); stroke(expandDivider, 2);
hidden(expandDivider);
particles(expandGas, expandLeft, 54, 6, 101);
hue(expandGas, cold, 0.38, 0.76); glow(expandGas, 0.7);
text(expandCaption, (540, 745), "More volume means exponentially more possible states");
size(expandCaption, 27); color(expandCaption, dim);
line(expandX, (250, 1040), (560, 1040)); line(expandY, (250, 1040), (250, 845));
color(expandX, dim); color(expandY, dim); stroke(expandX, 2); stroke(expandY, 2);
plot(expandCurve, (250, 1040), 85, 60, "x", (0, 2.6));
hue(expandCurve, green, 0.52, 0.70); stroke(expandCurve, 4); untraced(expandCurve);
dot(expandMarker, (250, 1040), 7); color(expandMarker, fg); glow(expandMarker, 1.0);
equation(expandAxis, (390, 820), `\ln(V/V_0)/\ln 2`, 27); color(expandAxis, dim);
equation(expandStates, (760, 905), `V^N\longrightarrow(2V)^N`, 37);
hue(expandStates, hot, 0.52, 0.70);
counter(expandCount, (760, 1010), 8.9, 1, "log₁₀(Ω/Ω₀) = ", "");
size(expandCount, 26); color(expandCount, dim);
tag(expandBox, s3); tag(expandGas, s3);
tag(expandCaption, s3); tag(expandX, s3); tag(expandY, s3); tag(expandCurve, s3);
tag(expandMarker, s3); tag(expandAxis, s3); tag(expandStates, s3); tag(expandCount, s3);
hidden(s3);
// ---------------------------------------------------------------------------
// 4 · A HEAT ENGINE CANNOT TURN EVERY JOULE INTO WORK
// ---------------------------------------------------------------------------
line(hotRail, (230, 410), (850, 410)); hue(hotRail, hot, 0.72, 0.65); stroke(hotRail, 5);
line(coldRail, (230, 1200), (850, 1200)); hue(coldRail, blue, 0.72, 0.65); stroke(coldRail, 5);
equation(hotRailLabel, (540, 365), `T_h`, 31); equation(coldRailLabel, (540, 1250), `T_c`, 31);
hue(hotRailLabel, hot, 0.56, 0.70); hue(coldRailLabel, blue, 0.56, 0.70);
line(engineX, (265, 1010), (805, 1010)); line(engineY, (265, 1010), (265, 555));
color(engineX, dim); color(engineY, dim); stroke(engineX, 2); stroke(engineY, 2);
equation(engineP, (250, 520), `p`, 28); equation(engineV, (835, 1010), `V`, 28);
color(engineP, dim); color(engineV, dim);
// Two paths share the hot state, separate during the cycle, and reconverge.
// Each visible path is split only to carry the warm/cool colour progression;
// the hidden whole paths give `travel` one continuous geometric route.
plot(engineHotPath, (285, 990), 112, 235, "1/(x+0.65)+0.14+0.05*x*(4.1-x)", (0, 4.1));
plot(engineColdPath, (285, 990), 112, 235, "1/(x+0.65)+0.14-0.035*x*(4.1-x)", (0, 4.1));
hidden(engineHotPath); hidden(engineColdPath);
plot(engineHotA, (285, 990), 112, 235, "1/(x+0.65)+0.14+0.05*x*(4.1-x)", (0, 1.55));
plot(engineHotB, (285, 990), 112, 235, "1/(x+0.65)+0.14+0.05*x*(4.1-x)", (1.55, 4.1));
plot(engineColdA, (285, 990), 112, 235, "1/(x+0.65)+0.14-0.035*x*(4.1-x)", (0, 1.85));
plot(engineColdB, (285, 990), 112, 235, "1/(x+0.65)+0.14-0.035*x*(4.1-x)", (1.85, 4.1));
hue(engineHotA, hot, 0.68, 0.70); hue(engineHotB, red, 0.58, 0.69);
hue(engineColdA, 260, 0.52, 0.72); hue(engineColdB, blue, 0.62, 0.70);
stroke(engineHotA, 5); stroke(engineHotB, 5); stroke(engineColdA, 5); stroke(engineColdB, 5);
untraced(engineHotA); untraced(engineHotB); untraced(engineColdA); untraced(engineColdB);
dot(engineDot, (285, 596), 8); color(engineDot, fg); glow(engineDot, 1.1); hidden(engineDot);
arrow(qHot, (330, 410), (330, 610)); hue(qHot, hot, 0.72, 0.68); stroke(qHot, 4); untraced(qHot);
equation(qHotLabel, (360, 525), `Q_h`, 28); hue(qHotLabel, hot, 0.56, 0.70); hidden(qHotLabel);
arrow(workArrow, (650, 850), (875, 700)); color(workArrow, dim); stroke(workArrow, 4); untraced(workArrow);
equation(workLabel, (880, 660), `W`, 28); color(workLabel, dim); hidden(workLabel);
arrow(qCold, (675, 1000), (675, 1170)); hue(qCold, blue, 0.72, 0.68); stroke(qCold, 4); untraced(qCold);
equation(qColdLabel, (735, 1090), `|Q_c|`, 28); hue(qColdLabel, blue, 0.56, 0.70); hidden(qColdLabel);
text(engineCaption, (540, 1345), "A heat engine must release some heat");
size(engineCaption, 30); hue(engineCaption, hot, 0.38, 0.74);
equation(engineEta, (540, 1490), `\eta_C=1-\frac{T_c}{T_h}<1`, 47);
// Persistent transition strokes preserve object identity between scenes:
// entropy curve → heat connector → expansion divider, then the volume graph
// seeds the heat-engine curve. All are ordinary generic `morph` paths.
plot(mixToHeat, (260, 1040), 78, 175, "1-exp(-2*x)", (0, 3.6));
hue(mixToHeat, blue, 0.55, 0.68); stroke(mixToHeat, 4);
morph(mixToHeat, heatLink); hidden(mixToHeat);
line(heatToExpand, (445, 500), (635, 500));
hue(heatToExpand, blue, 0.46, 0.62); stroke(heatToExpand, 4);
morph(heatToExpand, expandDivider); hidden(heatToExpand);
line(engineSeed, (285, 596), (355, 790)); hidden(engineSeed);
plot(expandToEngine, (250, 1040), 85, 60, "x", (0, 2.6));
hue(expandToEngine, green, 0.52, 0.70); stroke(expandToEngine, 4);
morph(expandToEngine, engineSeed); hidden(expandToEngine);
tag(hotRail, s4); tag(coldRail, s4); tag(hotRailLabel, s4); tag(coldRailLabel, s4);
tag(engineX, s4); tag(engineY, s4); tag(engineP, s4); tag(engineV, s4);
tag(engineHotA, s4); tag(engineHotB, s4); tag(engineColdA, s4); tag(engineColdB, s4);
tag(engineCaption, s4); tag(engineEta, s4);
tag(engineDot, s4future); tag(qHot, s4future); tag(qHotLabel, s4future);
tag(workArrow, s4future); tag(workLabel, s4future);
tag(qCold, s4future); tag(qColdLabel, s4future);
hidden(s4);
// ---------------------------------------------------------------------------
// 5 · SAME ENDPOINTS, DIFFERENT ENTROPY PRODUCTION
// ---------------------------------------------------------------------------
text(revHead, (300, 350), "REV. LIMIT"); text(irrHead, (780, 350), "IRREV");
size(revHead, 28); size(irrHead, 28); hue(revHead, green, 0.45, 0.72); hue(irrHead, red, 0.55, 0.68);
rect(revPanel, (300, 610), 330, 330); rect(irrPanel, (780, 610), 330, 330);
outline(revPanel, dim); outline(irrPanel, dim); outlined(revPanel); outlined(irrPanel);
stroke(revPanel, 3); stroke(irrPanel, 3);
rect(revGasBox, (260, 610), 220, 210); rect(irrGasBox, (740, 610), 220, 210);
hidden(revGasBox); hidden(irrGasBox);
rect(revSmall, (215, 610), 120, 210); rect(irrSmall, (695, 610), 120, 210);
hidden(revSmall); hidden(irrSmall);
particles(revGas, revGasBox, 20, 5, 131, "grid");
particles(irrGas, irrGasBox, 20, 5, 151, "grid");
hue(revGas, green, 0.55, 0.72); hue(irrGas, red, 0.58, 0.68);
line(revPiston, (405, 505), (405, 715)); line(irrPiston, (885, 505), (885, 715));
color(revPiston, dim); color(irrPiston, dim); stroke(revPiston, 5); stroke(irrPiston, 5);
text(compareCaption, (540, 865), "Same endpoints · different entropy production");
size(compareCaption, 28); color(compareCaption, dim);
rect(revMeter, (300, 1120), 70, 260); rect(irrMeter, (780, 1120), 70, 260);
outline(revMeter, dim); outline(irrMeter, dim); outlined(revMeter); outlined(irrMeter);
stroke(revMeter, 3); stroke(irrMeter, 3);
line(irrBar, (780, 1235), (780, 1235)); hue(irrBar, red, 0.72, 0.67); stroke(irrBar, 28);
line(revZero, (278, 1235), (322, 1235)); hue(revZero, green, 0.58, 0.70); stroke(revZero, 5);
equation(revEq, (300, 1325), `S_{\mathrm{gen}}=0`, 31);
equation(irrEq, (780, 1325), `S_{\mathrm{gen}}>0`, 31);
hue(revEq, green, 0.48, 0.70); hue(irrEq, red, 0.55, 0.68);
tag(revHead, s5); tag(irrHead, s5); tag(revPanel, s5); tag(irrPanel, s5);
tag(revGas, s5); tag(irrGas, s5); tag(revPiston, s5); tag(irrPiston, s5); tag(compareCaption, s5);
tag(revMeter, s5); tag(irrMeter, s5); tag(irrBar, s5); tag(revZero, s5); tag(revEq, s5); tag(irrEq, s5);
hidden(s5);
// ---------------------------------------------------------------------------
// 6 · THE ARROW OF TIME IS A STATISTICAL ARROW
// ---------------------------------------------------------------------------
rect(timeBox, (540, 610), 720, 310); outline(timeBox, dim); outlined(timeBox); stroke(timeBox, 3);
particles(timeGas, timeBox, 63, 6, 211, "grid");
hue(timeGas, hot, 0.48, 0.74); glow(timeGas, 0.7);
arrow(timeForward, (300, 980), (780, 980)); hue(timeForward, red, 0.62, 0.69); stroke(timeForward, 5);
equation(timeForwardLabel, (540, 1035), `t`, 28); color(timeForwardLabel, dim);
arrow(timeReverse, (780, 980), (300, 980)); color(timeReverse, fg); stroke(timeReverse, 5); hidden(timeReverse);
equation(timeReverseLabel, (540, 1035), `t`, 28); color(timeReverseLabel, dim); hidden(timeReverseLabel);
text(timeCaption, (540, 1135), "A special state naturally spreads into a typical one");
size(timeCaption, 29); hue(timeCaption, hot, 0.34, 0.74);
equation(returnProb, (540, 285), `\Pr(\mathrm{return})=\frac{\Omega_{\mathrm{low}}}{\Omega_{\mathrm{eq}}}=e^{-\Delta S/k_B}`, 43);
equation(returnOdds, (540, 365), `\Pr(\mathrm{ordered\ return})<10^{-40}`, 34);
hue(returnOdds, hot, 0.48, 0.72); hidden(returnProb); hidden(returnOdds);
tag(timeBox, s6); tag(timeGas, s6gas); tag(timeForward, s6); tag(timeForwardLabel, s6);
tag(timeCaption, s6);
tag(timeReverse, s6future); tag(timeReverseLabel, s6future);
tag(returnProb, s6future); tag(returnOdds, s6future);
hidden(s6); hidden(s6gas);
circle(finalOrbit, (540, 790), 270); hidden(finalOrbit);
for i in 0..72 {
let a = tau*i/72;
dot(finalDot{i}, (540 + 270*cos(a), 790 + 270*sin(a)), 4);
hue(finalDot{i}, 10 + 300*i/72, 0.54, 0.62);
glow(finalDot{i}, 0.5);
tag(finalDot{i}, finalRing);
}
equation(finalLaw, (540, 765), `\Delta S_{\mathrm{universe}}=S_{\mathrm{gen}}\ge 0`, 48);
text(finalCaption, (540, 875), "Irreversible change produces entropy");
size(finalCaption, 31); color(finalCaption, dim);
hidden(finalRing); hidden(finalLaw); hidden(finalCaption);
// ---------------------------------------------------------------------------
// TIMELINE · six continuous beats, approximately 39 seconds
// ---------------------------------------------------------------------------
step("mixing") {
wait(5.5);
par {
seq { wait(0.45); fade(mixDivider, 0.20); }
seq { wait(0.55); arrange(mixCold, mixBox, "random", 0.95, out); wander(mixCold, 4.0); }
seq { wait(0.55); arrange(mixWarm, mixBox, "random", 0.95, out); wander(mixWarm, 4.0); }
seq {
wait(0.55);
par {
draw(mixCurve, 2.20, out);
travel(mixMarker, mixCurve, 2.20, out);
to(mixCount, value, 25.3, 2.20, out);
}
}
}
}
step("heat-flow") {
seq {
par {
fade(s1, 0.65);
show(mixToHeat, 0.10);
to(mixToHeat, morph, 1, 0.65, smooth);
seq { wait(0.20); show(s2, 0.45); }
rewrite(law, `\dot S_{\mathrm{tot}}=K\frac{(T_h-T_c)^2}{T_hT_c}>0`, 0.65, smooth);
}
par {
wander(hotMatter, 4.4);
wander(coldMatter, 4.4);
draw(flowCurve, 3.6, smooth);
travel(flowMarker, flowCurve, 3.6, smooth);
to(hotTemp, value, 400, 4.0, smooth);
to(coldTemp, value, 400, 4.0, smooth);
recolor(hotMatter, cyan, 4.0);
recolor(coldMatter, cyan, 4.0);
seq { flow(mixToHeat, 1.1); flow(mixToHeat, 1.1); flow(mixToHeat, 1.1); flow(mixToHeat, 1.1); }
}
wait(0.45);
}
}
step("free-expansion") {
seq {
par {
fade(s2, 0.65);
fade(mixToHeat, 0.25);
show(heatToExpand, 0.10);
to(heatToExpand, morph, 1, 0.65, smooth);
recolor(heatToExpand, dim, 0.65);
seq { wait(0.20); show(s3, 0.45); }
rewrite(law, `\Delta S=Nk_B\ln 2`, 0.65, smooth);
}
par {
seq { arrange(expandGas, expandBox, "random", 1.10, out); wander(expandGas, 1.45); }
fade(heatToExpand, 0.65);
draw(expandCurve, 2.45, smooth);
travel(expandMarker, expandCurve, 2.45, smooth);
to(expandCount, value, 20.9, 2.45, smooth);
}
wait(0.25);
}
}
step("heat-engine") {
seq {
par {
fade(s3, 0.65);
show(expandToEngine, 0.10);
to(expandToEngine, morph, 1, 0.65, smooth);
seq { wait(0.20); show(s4, 0.45); }
rewrite(law, `\frac{Q_h}{T_h}=\frac{|Q_c|}{T_c}`, 0.65, smooth);
}
par {
seq { draw(engineHotA, 1.85, smooth); draw(engineHotB, 3.35, smooth); }
seq { draw(engineColdA, 2.35, smooth); draw(engineColdB, 2.85, smooth); }
seq {
wait(0.15); show(engineDot, 0.15);
travel(engineDot, engineColdPath, 4.9, smooth);
}
fade(expandToEngine, 0.30);
seq { draw(qHot, 0.7); show(qHotLabel, 0.2); wait(1.0); }
seq { wait(1.3); draw(workArrow, 0.8); show(workLabel, 0.2); }
seq { wait(2.6); draw(qCold, 0.8); show(qColdLabel, 0.2); }
}
wait(2.15);
}
}
step("entropy-generation") {
seq {
par {
fade(s4, 0.50); fade(s4future, 0.50);
show(s5, 0.50);
rewrite(law, `\Delta S_{\mathrm{universe}}=S_{\mathrm{gen}}`, 0.50, smooth);
}
par {
arrange(revGas, revSmall, "grid", 2.4, smooth);
arrange(irrGas, irrSmall, "random", 2.4, smooth);
par { move(revPiston, (275, 505), 2.4, smooth); grow(revPiston, (275, 715), 2.4, smooth); }
par { move(irrPiston, (755, 505), 2.4, smooth); grow(irrPiston, (755, 715), 2.4, smooth); }
grow(irrBar, (780, 1025), 2.4, smooth);
}
wait(1.10);
}
}
step("time-arrow") {
seq {
par {
fade(s5, 0.50); fade(law, 0.50);
show(s6, 0.50); show(s6gas, 0.50);
}
par {
arrange(timeGas, timeBox, "random", 4.0, smooth);
seq { wait(3.0); pulse(timeForward, 0.8); }
}
wait(0.35);
par {
fade(timeForward, 0.30); fade(timeForwardLabel, 0.30);
show(timeReverse, 0.30); show(timeReverseLabel, 0.30);
say(timeCaption, "Exact reversal can reconstruct the past", 0.35);
}
arrange(timeGas, timeBox, "grid", 3.1, smooth);
par {
show(returnProb, 0.45); show(returnOdds, 0.45);
say(timeCaption, "But that reversal must be fantastically precise", 0.40);
}
wait(1.15);
par {
fade(s6, 0.90);
fade(s6future, 0.90);
arrange(timeGas, finalOrbit, "ring", 0.90, smooth);
seq { wait(0.55); fade(s6gas, 0.35); }
seq { wait(0.55); show(finalRing, 0.35); }
seq { wait(0.55); transform(finalRing, (540,790), 0.9511, -0.3090, 0.3090, 0.9511, 0.55, out); }
seq { wait(0.30); par { show(finalLaw, 0.60); show(finalCaption, 0.60); } }
}
wait(1.70);
}
}
maxwell-boltzmann-process
A seeded reservoir releases persistent molecules along one authored path while the same compiled process grows an empirical speed histogram and arrival count. The first acceptance story for evolving collections: motion and measurement stay connected and seekable.
// MAXWELL–BOLTZMANN PROCESS FOUNDATION
// First acceptance story for deterministic evolving collections. The emitted
// particles, arrival count, and speed histogram all come from one compiled
// process. The formula is authored truth; Manic is not pretending to be a CAS.
title("Maxwell–Boltzmann — From Motion to Distribution");
canvas("9:16");
template("blank");
watermark(mark, (155, 92), "Made With Manic");
text(kicker, (540, 80), "STATISTICAL MECHANICS · PROCESS VIEW");
text(headline, (540, 145), "How does molecular motion become a distribution?");
size(kicker, 19); color(kicker, dim); bold(kicker);
size(headline, 33); bold(headline); wrap(headline, 850);
equation(law, (540, 245),
`f(v)=4\pi\left(\frac{m}{2\pi k_BT}\right)^{3/2}v^2e^{-mv^2/(2k_BT)}`, 36);
rect(reservoir, (410, 500), 560, 330);
outlined(reservoir); outline(reservoir, dim); stroke(reservoir, 4);
line(divider, (630, 340), (630, 660));
color(divider, dim); stroke(divider, 3);
text(gasLabel, (330, 685), "seeded molecules");
size(gasLabel, 20); color(gasLabel, dim);
particles(molecules, reservoir, 72, 5, 29);
// The route starts at the aperture and bends into the measurement region.
spline(nozzle, (630, 500), (735, 430), (850, 570), (900, 735));
stroke(nozzle, 4); color(nozzle, fg); untraced(nozzle);
text(nozzleLabel, (850, 395), "effusive path");
size(nozzleLabel, 19); color(nozzleLabel, dim);
livehistogram(speedBins, (540, 1160), 0.35, 0.95, 14, 760, 300, cyan);
text(histTitle, (540, 970), "EMPIRICAL SPEEDS");
size(histTitle, 23); bold(histTitle);
text(axisUnit, (540, 1355), "normalized path lengths per second");
size(axisUnit, 18); color(axisUnit, dim);
counter(arrived, (540, 1460), 0, 0, "detected molecules ", " / 72");
size(arrived, 24); color(arrived, dim);
text(caption, (540, 1545),
"Each bar grows only when a molecule from the same process arrives.");
size(caption, 22); color(caption, dim); wrap(caption, 820);
hidden(molecules); hidden(speedBins); hidden(arrived); hidden(caption);
hidden(nozzleLabel);
step("reservoir") {
par {
show(molecules, 0.55);
show(speedBins, 0.45);
show(arrived, 0.35);
show(caption, 0.35);
}
}
wait(0.45);
step("open-the-aperture") {
par {
draw(nozzle, 0.55);
show(nozzleLabel, 0.35);
}
}
wait(0.25);
step("motion-becomes-data") {
par {
stream(molecules, nozzle, 5.2, 42, smooth);
observe(speedBins, molecules, speed);
observe(arrived, molecules, arrived);
say(caption,
"The motion and the distribution are two views of one deterministic experiment.",
0.45);
}
}
wait(0.7);
step("takeaway") {
par {
pulse(speedBins.bars, 0.8);
say(caption,
"Manic can now connect a changing collection to measurements without frame callbacks.",
0.45);
}
}
wait(1.2);
maxwell-boltzmann-effusion-v2
Expressive Process V2: 180 persistent molecules receive deterministic Maxwell-distributed speeds, traverse sooner when faster, and visibly settle into their measured bins. A bundled native SVG apparatus frames one truthful process, empirical histogram, theory curve, and count.
// EXPRESSIVE PROCESS V2 — MAXWELL–BOLTZMANN EFFUSION
//
// A polished 2.5D acceptance story inspired by engine-test-6.mov. The
// apparatus is a bundled native SVG, while every molecule, detector bin,
// arrival count, and empirical distribution remains a normal Manic entity.
// `emit`, `collect`, and `observe` all read one deterministic process.
title("Maxwell–Boltzmann Effusion — Motion Becomes Evidence");
canvas("9:16");
template("blank");
watermark(mark, (150, 90), "Made With Manic");
text(kicker, (540, 84), "STATISTICAL MECHANICS · EXPRESSIVE PROCESS V2");
text(headline, (540, 142), "A distribution you can watch forming");
size(kicker, 18); color(kicker, dim); bold(kicker);
size(headline, 34); bold(headline);
equation(law, (540, 242),
`f(v)=4\pi\left(\frac{m}{2\pi k_BT}\right)^{3/2}v^2e^{-mv^2/(2k_BT)}`, 34);
// The decorative shell is an asset; the changing evidence stays native.
svg(apparatus, (240, 540), "asset:svg/physics/effusion-reservoir.svg", 330);
text(reservoirLabel, (225, 705), "THERMAL RESERVOIR");
size(reservoirLabel, 17); bold(reservoirLabel); color(reservoirLabel, gold);
rect(detectorPanel, (730, 560), 570, 300);
filled(detectorPanel); color(detectorPanel, panel); opacity(detectorPanel, 0.92);
outlined(detectorPanel); outline(detectorPanel, dim); stroke(detectorPanel, 3);
text(detectorTitle, (730, 382), "PARTICLE SPEED v");
size(detectorTitle, 18); bold(detectorTitle); color(detectorTitle, dim);
// The invisible source is precisely at the SVG nozzle.
circle(source, (348, 540), 20);
opacity(source, 0);
particles(molecules, source, 180, 3.2, 73);
color(molecules, magenta); glow(molecules, 0.8); z(molecules, 8);
// Individual lanes diverge around this authored route before being collected.
spline(flight, (350, 540), (415, 500), (465, 515), (500, 545));
hidden(flight);
livehistogram(empirical, (730, 560), 0, 2.2, 14, 540, 240, cyan);
z(empirical, 3); hidden(empirical.count);
// Same horizontal range as the live histogram: 0 .. 4*scale = 2.2.
distribution(theory, (730, 560), "maxwell", 0.55, 0, gold);
hidden(theory.axis); hidden(theory.name);
untraced(theory.curve); z(theory, 7);
text(empiricalKey, (610, 750), "● empirical arrivals");
text(theoryKey, (850, 750), "— theoretical curve");
size(empiricalKey, 17); color(empiricalKey, cyan);
size(theoryKey, 17); color(theoryKey, gold);
counter(arrivals, (540, 850), 0, 0, "detected molecules ", " / 180");
size(arrivals, 24); color(arrivals, dim);
text(caption, (540, 920),
"A thermal source emits many molecular speeds—not one average speed.");
size(caption, 23); wrap(caption, 880); color(caption, dim);
rect(evidencePanel, (540, 1110), 880, 230);
filled(evidencePanel); color(evidencePanel, panel); opacity(evidencePanel, 0.95);
outlined(evidencePanel); outline(evidencePanel, dim); stroke(evidencePanel, 2);
text(evidenceTitle, (540, 1030), "ONE PROCESS · THREE VIEWS");
size(evidenceTitle, 18); bold(evidenceTitle); color(evidenceTitle, dim);
equation(processLaw, (540, 1100),
`\text{motion}\;\longrightarrow\;\text{arrival bin}\;\longrightarrow\;\hat f(v)`, 32);
text(truth, (540, 1190),
"The dots, bars, and count are derived from the same seeded experiment.");
size(truth, 20); color(truth, dim); wrap(truth, 790);
hidden(apparatus); hidden(reservoirLabel); hidden(detectorPanel);
hidden(detectorTitle); hidden(molecules); hidden(empirical);
hidden(empiricalKey); hidden(theoryKey); hidden(arrivals);
hidden(caption); hidden(evidencePanel); hidden(evidenceTitle);
hidden(processLaw); hidden(truth);
step("build-the-apparatus") {
par {
show(apparatus, 0.65);
show(reservoirLabel, 0.45);
show(detectorPanel, 0.55);
show(detectorTitle, 0.35);
show(empirical.axis, 0.55);
show(empirical.bars, 0.55);
show(empirical.min, 0.35);
show(empirical.max, 0.35);
show(empiricalKey, 0.35);
show(theoryKey, 0.35);
show(arrivals, 0.35);
show(caption, 0.40);
}
}
wait(0.45);
step("the-prediction") {
par {
draw(theory.curve, 0.9, smooth);
say(caption,
"Maxwell predicts a characteristic spread with a most probable speed.",
0.40);
par {
cam((cx, cy), 0.75, smooth);
zoom(1.025, 0.75, smooth);
}
}
}
wait(0.35);
step("run-the-experiment") {
par {
emit(molecules, flight, maxwell, 7.0, 48, smooth);
collect(empirical, molecules, speed, 0.48, smooth);
observe(empirical, molecules, speed);
observe(arrivals, molecules, arrived);
show(molecules, 0.15);
say(caption,
"Each molecule keeps its sampled speed. Faster molecules cross the apparatus sooner.",
0.45);
}
}
wait(0.55);
step("motion-becomes-evidence") {
par {
pulse(empirical.bars, 0.8);
show(evidencePanel, 0.40);
show(evidenceTitle, 0.35);
show(processLaw, 0.45);
show(truth, 0.40);
say(caption,
"Collection does not fake the chart: every arrival lands in the bin selected by its speed.",
0.45);
par {
cam((cx, cy), 0.65, smooth);
zoom(1.0, 0.65, smooth);
}
}
}
wait(1.4);
timing-v2-scene
Generic Timing v2 controlling an ordinary physics scene: one named-phase clock schedules the intro, pendulum motion and finish independently from its native timer look.
// Generic Timing v2 — one named clock coordinates a non-quiz physics scene.
// `timed` runs the visual timer and places every `during` block at the exact
// offset declared by `timing`; short blocks are padded, overruns are errors.
title("Generic Timing v2 — Pendulum");
canvas("16:9");
template("mono");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
text(head, (315, 72), "ONE CLOCK. EVERY BEAT.");
size(head, 30); bold(head); color(head, fg); hidden(head);
text(sub, (315, 112), "Generic Timing v2 — not a quiz");
size(sub, 18); color(sub, dim); hidden(sub);
pendulum(p, (640, 245), 1.5, 42, 140, 0.03);
untraced(p.path);
equation(law, (640, 602), `T\approx2\pi\sqrt{L/g}`, 54);
color(law, gold); hidden(law);
text(note, (640, 655), "The scene and clock share the same named phases.");
size(note, 21); color(note, dim); hidden(note);
text(done, (640, 680), "INTRO → EXPERIMENT → INSIGHT → OUTRO");
size(done, 18); color(done, cyan); hidden(done);
// A fresh id creates a format-neutral timing controller. Its phases total
// 10.4 seconds; no duplicate duration is passed to `timed` or `run`.
timing(showclock, (970, 86), "intro=1.2 experiment=6 insight=2 outro=1.2");
timerstyle(showclock,
"look=segments number=outside direction=drain size=small thickness=1.15 color=cyan track=dim label=MASTER_CLOCK font=display finish=pulse");
timed(showclock) {
during("intro") {
par { show(head, 0.7); show(sub, 0.7); }
}
during("experiment") {
par { run(p, 6); draw(p.path, 6); }
}
during("insight") {
par { show(law, 0.6); show(note, 0.6); }
pulse(p.bob, 0.7);
}
during("outro") {
show(done, 0.6);
}
}
zeroth-law-thermodynamics
The Zeroth Law told through three particle-filled bodies: thermal relations connect, the bodies settle onto one temperature axis, and equilibrium becomes visible.
// ============================================================================
// zeroth-law-thermodynamics.manic — thermal equalisation explained visually
// ----------------------------------------------------------------------------
// A shot-for-shot structural recreation of engine-test.mp4 using only the
// generic std + math vocabulary. It deliberately avoids a thermal-specific
// kit: circles, dots, lines, LaTeX, formula plots and timeline composition
// should be enough to tell the story.
//
// Reference beats: network 0–9s · temperature axis 9–20s · transfer 20–35s
// · closing thought 35–39.4s. Record at 60 fps with the reel preset.
// ============================================================================
title("Zeroth Law of Thermodynamics");
canvas("9:16");
template("mono");
// Reels put controls down the right edge and captions near the bottom. Keep
// identity in the upper-left safe area, clear of both UI and the three bodies.
watermark(manicMark, (155, 145), "Made With Manic");
// Shared palette expressed as hues so the example keeps its identity even
// when somebody overrides the template from the CLI.
let teal = 168;
let warm = 38;
let cool = 246;
// ---------------------------------------------------------------------------
// SCENE 1 · three bodies connected by possible heat exchange
// ---------------------------------------------------------------------------
circle(s1A, (540, 280), 92);
circle(s1B, (300, 565), 92);
circle(s1C, (780, 565), 92);
outline(s1A, cyan); outline(s1B, cyan); outline(s1C, cyan);
stroke(s1A, 5); stroke(s1B, 5); stroke(s1C, 5);
glow(s1A, 1.5); glow(s1B, 1.5); glow(s1C, 1.5);
tag(s1A, s1); tag(s1B, s1); tag(s1C, s1);
equation(s1la, (540, 280), `A`, 68);
equation(s1lb, (300, 565), `B`, 68);
equation(s1lc, (780, 565), `C`, 68);
tag(s1la, s1); tag(s1lb, s1); tag(s1lc, s1);
// Generic contained dots: the ids make these "matter" here, but the engine
// words are equally useful for bubbles, dust, stars, or moving data.
particles(s1matterA, s1A, 22, 5, 7);
particles(s1matterB, s1B, 22, 5, 17);
particles(s1matterC, s1C, 22, 5, 27);
hue(s1matterA, teal, 0.65, 0.70); hue(s1matterB, teal, 0.65, 0.70); hue(s1matterC, teal, 0.65, 0.70);
glow(s1matterA, 1.0); glow(s1matterB, 1.0); glow(s1matterC, 1.0);
tag(s1matterA, s1); tag(s1matterB, s1); tag(s1matterC, s1);
// Curved tracked links replace hand-computed endpoints and keep following if
// the bodies move. Signed bends bow the two upper links away from the triangle.
link(s1ab, s1A, s1B, 48);
link(s1ac, s1A, s1C, -48);
link(s1bc, s1B, s1C, 55);
stroke(s1ab, 4); stroke(s1ac, 4); stroke(s1bc, 4);
hue(s1ab, teal, 0.72, 0.54); hue(s1ac, teal, 0.72, 0.54); hue(s1bc, teal, 0.72, 0.54);
glow(s1ab, 0.8); glow(s1ac, 0.8); glow(s1bc, 0.8);
untraced(s1ab); untraced(s1ac); untraced(s1bc);
tag(s1ab, s1); tag(s1ac, s1); tag(s1bc, s1); tag(s1ab, s1links); tag(s1ac, s1links); tag(s1bc, s1links);
// Each relation first appears as a faint possibility, then becomes definite as
// its curved connection draws on.
equation(s1eqAB, (315, 365), `A\sim B`, 35);
equation(s1eqBC, (540, 660), `B\sim C`, 35);
equation(s1eqAC, (765, 365), `A\sim C`, 35);
color(s1eqAB, dim); color(s1eqBC, dim); color(s1eqAC, dim);
tag(s1eqAB, s1); tag(s1eqBC, s1); tag(s1eqAC, s1);
tag(s1eqAB, s1relations); tag(s1eqBC, s1relations); tag(s1eqAC, s1relations);
tag(s1A, s1bodyA); tag(s1la, s1bodyA); tag(s1matterA, s1bodyA);
tag(s1B, s1bodyB); tag(s1lb, s1bodyB); tag(s1matterB, s1bodyB);
tag(s1C, s1bodyC); tag(s1lc, s1bodyC); tag(s1matterC, s1bodyC);
hidden(s1A); hidden(s1B); hidden(s1C);
hidden(s1la); hidden(s1lb); hidden(s1lc);
hidden(s1matterA); hidden(s1matterB); hidden(s1matterC);
hidden(s1eqAB); hidden(s1eqBC); hidden(s1eqAC);
// ---------------------------------------------------------------------------
// SCENE 2 · temperature becomes a position on one common scale
// ---------------------------------------------------------------------------
line(s2y, (180, 700), (180, 1390));
line(s2g0, (180, 820), (900, 820));
line(s2g1, (180, 1070), (900, 1070));
line(s2g2, (180, 1320), (900, 1320));
line(s2eqline, (230, 1070), (850, 1070));
color(s2y, blue); color(s2g0, dim); color(s2g1, dim); color(s2g2, dim); hue(s2eqline, teal, 0.65, 0.55);
stroke(s2y, 4); stroke(s2g0, 2); stroke(s2g1, 2); stroke(s2g2, 2); stroke(s2eqline, 5);
glow(s2eqline, 0.9);
tag(s2y, s2); tag(s2g0, s2); tag(s2g1, s2); tag(s2g2, s2); tag(s2eqline, s2);
tag(s2y, s2axes); tag(s2g0, s2axes); tag(s2g1, s2axes); tag(s2g2, s2axes); tag(s2eqline, s2axes);
equation(s2T, (180, 650), `T`, 44); color(s2T, fg); tag(s2T, s2); tag(s2T, s2axes);
dot(s2A, (430, 1070), 10); dot(s2B, (540, 1070), 10); dot(s2C, (650, 1070), 10);
hue(s2A, teal, 0.65, 0.60); hue(s2B, teal, 0.65, 0.60); hue(s2C, teal, 0.65, 0.60);
glow(s2A, 1.1); glow(s2B, 1.1); glow(s2C, 1.1);
tag(s2A, s2); tag(s2B, s2); tag(s2C, s2); tag(s2A, s2points); tag(s2B, s2points); tag(s2C, s2points);
equation(s2la, (430, 1025), `A`, 35); equation(s2lb, (540, 1025), `B`, 35); equation(s2lc, (650, 1025), `C`, 35);
tag(s2la, s2); tag(s2lb, s2); tag(s2lc, s2); tag(s2la, s2points); tag(s2lb, s2points); tag(s2lc, s2points);
equation(s2eq, (540, 1490), `T_A=T_B=T_C`, 48); tag(s2eq, s2); tag(s2eq, s2eqgroup);
line(s2hot, (230, 820), (850, 820)); line(s2cold, (230, 1320), (850, 1320));
hue(s2hot, warm, 0.70, 0.62); hue(s2cold, cool, 0.70, 0.62); stroke(s2hot, 4); stroke(s2cold, 4);
line(s2bracket, (850, 820), (850, 1320)); line(s2cap0, (825, 820), (875, 820)); line(s2cap1, (825, 1320), (875, 1320));
color(s2bracket, fg); color(s2cap0, fg); color(s2cap1, fg); stroke(s2bracket, 3); stroke(s2cap0, 3); stroke(s2cap1, 3);
equation(s2delta, (930, 1070), `\Delta T`, 42);
tag(s2hot, s2deltaGroup); tag(s2cold, s2deltaGroup); tag(s2bracket, s2deltaGroup);
tag(s2cap0, s2deltaGroup); tag(s2cap1, s2deltaGroup); tag(s2delta, s2deltaGroup);
tag(s2hot, s2); tag(s2cold, s2); tag(s2bracket, s2); tag(s2cap0, s2); tag(s2cap1, s2); tag(s2delta, s2);
hidden(s2);
// ---------------------------------------------------------------------------
// SCENE 3 · energy flows while two temperatures converge exponentially
// ---------------------------------------------------------------------------
circle(s3A, (300, 430), 98); circle(s3C, (780, 430), 98);
outline(s3A, orange); outline(s3C, blue);
stroke(s3A, 5); stroke(s3C, 5); glow(s3A, 1.5); glow(s3C, 1.5);
tag(s3A, s3initialrings); tag(s3C, s3initialrings);
link(s3link, s3A, s3C); hue(s3link, teal, 0.65, 0.54); stroke(s3link, 5); glow(s3link, 0.8);
// Crossfade to common teal rings as equilibrium approaches. Keeping the fill
// dark preserves the reservoir/particle texture instead of becoming a disc.
circle(s3Afinal, (300, 430), 98); circle(s3Cfinal, (780, 430), 98);
outline(s3Afinal, cyan); outline(s3Cfinal, cyan); stroke(s3Afinal, 5); stroke(s3Cfinal, 5);
glow(s3Afinal, 1.5); glow(s3Cfinal, 1.5); hidden(s3Afinal); hidden(s3Cfinal);
tag(s3Afinal, s3); tag(s3Cfinal, s3); tag(s3Afinal, s3finalrings); tag(s3Cfinal, s3finalrings);
equation(s3la, (300, 430), `A`, 70); equation(s3lc, (780, 430), `C`, 70);
tag(s3link, s3nodes); tag(s3A, s3nodes); tag(s3C, s3nodes); tag(s3la, s3nodes); tag(s3lc, s3nodes);
tag(s3link, s3); tag(s3A, s3); tag(s3C, s3); tag(s3la, s3); tag(s3lc, s3);
particles(s3hotparticles, s3A, 24, 5, 41);
particles(s3coldparticles, s3C, 24, 5, 73);
hue(s3hotparticles, warm, 0.72, 0.68); hue(s3coldparticles, cool, 0.72, 0.68);
glow(s3hotparticles, 1.0); glow(s3coldparticles, 1.0);
tag(s3hotparticles, s3); tag(s3hotparticles, s3nodes);
tag(s3coldparticles, s3); tag(s3coldparticles, s3nodes);
// Temperature-vs-time graph. The two exact formulas share the same asymptote.
line(s3x, (190, 1340), (900, 1340)); line(s3y, (190, 1340), (190, 760));
color(s3x, dim); color(s3y, dim); stroke(s3x, 3); stroke(s3y, 3);
equation(s3theta, (190, 710), `\theta`, 40); equation(s3time, (930, 1340), `t`, 38);
plot(s3upper, (230, 1340), 155, 190, "1 + exp(-x)", (0, 4));
plot(s3lower, (230, 1340), 155, 190, "1 - exp(-x)", (0, 4));
hue(s3upper, warm, 0.78, 0.62); hue(s3lower, cool, 0.78, 0.62); stroke(s3upper, 5); stroke(s3lower, 5);
glow(s3upper, 0.7); glow(s3lower, 0.7); untraced(s3upper); untraced(s3lower);
tag(s3x, s3); tag(s3y, s3); tag(s3theta, s3); tag(s3time, s3); tag(s3upper, s3); tag(s3lower, s3);
tag(s3x, s3axes); tag(s3y, s3axes); tag(s3theta, s3axes); tag(s3time, s3axes);
tag(s3upper, s3curves); tag(s3lower, s3curves);
equation(s3eq, (540, 1510), `\Delta T(t)=\Delta T_0 e^{-2k t/C}`, 44);
color(s3eq, fg); tag(s3eq, s3); tag(s3eq, s3equation);
hidden(s3);
// ---------------------------------------------------------------------------
// SCENE 4 · reduce the mechanics to one thought
// ---------------------------------------------------------------------------
line(s4line, (420, 850), (660, 850)); hue(s4line, teal, 0.72, 0.58); stroke(s4line, 6); glow(s4line, 1.0);
dot(s4a, (420, 850), 16); dot(s4c, (660, 850), 16); hue(s4a, teal, 0.70, 0.63); hue(s4c, teal, 0.70, 0.63); glow(s4a, 1.3); glow(s4c, 1.3);
text(s4quote, (540, 1130), "Temperature names sameness.\nDifference gives energy direction.");
size(s4quote, 38); color(s4quote, fg); bold(s4quote); untraced(s4quote);
tag(s4line, s4); tag(s4a, s4); tag(s4c, s4); tag(s4quote, s4);
hidden(s4line); hidden(s4a); hidden(s4c);
// ---------------------------------------------------------------------------
// TIMELINE · 39.4 seconds
// ---------------------------------------------------------------------------
par {
wander(s1matterA, 7.5);
wander(s1matterB, 7.5);
wander(s1matterC, 7.5);
seq {
show(s1bodyA, 0.45);
wait(0.20);
show(s1bodyB, 0.45);
show(s1eqAB, 0.25);
par { draw(s1ab, 0.70); recolor(s1eqAB, fg, 0.70); }
flow(s1ab, 0.75);
show(s1bodyC, 0.45);
show(s1eqBC, 0.25);
par { draw(s1bc, 0.70); recolor(s1eqBC, fg, 0.70); }
flow(s1bc, 0.75);
show(s1eqAC, 0.25);
par { draw(s1ac, 0.70); recolor(s1eqAC, fg, 0.70); }
par { flow(s1ab, 1.05); flow(s1bc, 1.05); flow(s1ac, 1.05); }
wait(0.55);
}
}
// The three bodies do not cut to new dots: they visibly shrink, line up on a
// common temperature axis, then hand off to the precise plotted markers.
par {
fade(s1relations, 0.45); fade(s1links, 0.45);
fade(s1matterA, 0.45); fade(s1matterB, 0.45); fade(s1matterC, 0.45);
show(s2axes, 0.70);
move(s1A, (430, 1070), 1.10, smooth); scale(s1A, 0.12, 1.10, smooth);
move(s1B, (540, 1070), 1.10, smooth); scale(s1B, 0.12, 1.10, smooth);
move(s1C, (650, 1070), 1.10, smooth); scale(s1C, 0.12, 1.10, smooth);
move(s1la, (430, 1025), 1.10, smooth); scale(s1la, 0.52, 1.10, smooth);
move(s1lb, (540, 1025), 1.10, smooth); scale(s1lb, 0.52, 1.10, smooth);
move(s1lc, (650, 1025), 1.10, smooth); scale(s1lc, 0.52, 1.10, smooth);
}
par {
fade(s1bodyA, 0.22); fade(s1bodyB, 0.22); fade(s1bodyC, 0.22);
show(s2points, 0.22);
}
show(s2eqgroup, 0.5);
wait(4.0);
par {
move(s2A, (430, 820), 1.3, smooth); move(s2la, (430, 775), 1.3, smooth);
move(s2C, (650, 1320), 1.3, smooth); move(s2lc, (650, 1275), 1.3, smooth);
recolor(s2A, orange, 1.0); recolor(s2la, orange, 1.0);
recolor(s2C, blue, 1.0); recolor(s2lc, blue, 1.0);
fade(s2eqgroup, 0.5);
}
show(s2deltaGroup, 0.5);
wait(3.5);
// Keep visual identity across the scene change: the two temperature points
// first become small bodies on their own level lines, then travel and enlarge
// into the particle-filled reservoirs. The old axis remains behind them until
// the new transfer graph is already visible—there is never an empty frame.
par {
fade(s2B, 0.40); fade(s2lb, 0.40);
scale(s2A, 5.0, 0.70, smooth); move(s2la, (430, 820), 0.70, smooth); scale(s2la, 1.35, 0.70, smooth);
scale(s2C, 5.0, 0.70, smooth); move(s2lc, (650, 1320), 0.70, smooth); scale(s2lc, 1.35, 0.70, smooth);
}
par {
move(s2A, (300, 430), 0.50, smooth); scale(s2A, 9.8, 0.50, smooth);
move(s2la, (300, 430), 0.50, smooth); scale(s2la, 2.0, 0.50, smooth);
move(s2C, (780, 430), 0.50, smooth); scale(s2C, 9.8, 0.50, smooth);
move(s2lc, (780, 430), 0.50, smooth); scale(s2lc, 2.0, 0.50, smooth);
show(s3axes, 0.50);
}
par {
show(s3nodes, 0.70);
fade(s2, 0.70);
}
show(s3curves, 0.01);
par {
draw(s3upper, 10.7, smooth);
draw(s3lower, 10.7, smooth);
wander(s3hotparticles, 10.7);
wander(s3coldparticles, 10.7);
fade(s3initialrings, 10.5); show(s3finalrings, 10.5);
recolor(s3hotparticles, cyan, 10.5); recolor(s3coldparticles, cyan, 10.5);
seq {
for i in 0..6 { seq { flow(s3link, 1.20); wait(0.55); } }
}
}
show(s3equation, 0.6);
wait(1.5);
fade(s3, 0.8);
show(s4line, 0.5); show(s4a, 0.01); show(s4c, 0.01);
type(s4quote, 1.2);
wait(2.55); // keeps the benchmark at 39.4 s including manic's final 1 s tail
pendulum-damped
The same four views with friction on (damping): the swing decays, the phase loop
spirals inward, the well ball settles, and the total-energy line drops — dissipation
told the same way by every panel.
// ============================================================================
// pendulum-damped.manic — the SAME four views, now with friction
// ----------------------------------------------------------------------------
// Identical to `pendulum.manic` but with `damping` (the 6th arg) turned on, so
// the swing loses energy. Watch every panel respond together:
// · sim — the amplitude shrinks each swing (slows toward rest)
// · phase — the closed loop becomes an INWARD SPIRAL
// · well — the ball SETTLES toward the bottom of the bowl
// · energygraph — the TOTAL (gold) line DECAYS (KE↔PE trade, sum drops)
//
// Set damping = 0 (or drop the 6th arg) for the frictionless version, where the
// loop stays closed and the total-energy line stays flat (conservation).
// ============================================================================
title("Damped pendulum — energy bleeds away");
canvas("16:9");
text(hdr, (cx, 40), "Add friction: the swing decays, and every view follows");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the physical sim (left) — 6th arg 0.6 is the damping
pendulum(p, (250, 220), 1.2, 55, 105, 0.6);
untraced(p.path);
// the same four math views of the SAME (now damped) simulation
phase(p, (715, 165), 90); // loop → inward spiral
timegraph(p, (1000, 165), 90); // θ(t), ω(t) — envelope shrinks
well(p, (715, 455), 90); // ball settles to the bottom
energygraph(p, (1000, 455), 90); // total energy decays
text(cap, (cx, h - 30), "damping = 0.6 · the phase spiral, settling ball, and decaying total energy all show the same loss");
size(cap, 16); color(cap, dim); display(cap);
draw(p.path, 0.8);
swing(p, 10);
pendulum-annotated
A guided anatomy lesson proving physics composes with base manic: section chapters,
text / arrow / bracelabel annotations, and show/recolor/flash/pulse all
driving the sim’s parts — no special physics mode.
// ============================================================================
// pendulum-annotated.manic — a guided lesson: physics + base manic compose
// ----------------------------------------------------------------------------
// Proof that a sim's parts (`{id}.pivot/.rod/.bob/.path/…`) are ORDINARY manic
// entities. This whole lesson is built from BASE std vocabulary — `section`
// chapters, `text` labels, leader `arrow`s, a reference `line`, a `bracelabel`,
// and `show`/`fade`/`recolor`/`flash`/`pulse`/`draw`/`say` — wrapped around the
// physics `pendulum` + `energygraph` view + `swing`. No special "physics mode":
// every std verb/modifier/annotation addresses the physics entities directly.
// ============================================================================
title("Anatomy of a Pendulum");
canvas("16:9");
text(hdr, (cx, 44), "Anatomy of a Pendulum");
size(hdr, 30); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// ---- the physics sim + one math view (parts start hidden; path untraced) ----
pendulum(p, (440, 210), 1.6, 50, 110);
hidden(p.pivot); hidden(p.rod); hidden(p.bob); hidden(p.overlays);
untraced(p.path); // trace 0 but keep opacity (reveal later)
energygraph(p, (1030, 250), 105); // built now (its sweep joins `swing`)
hidden(p.energy);
// ---- base annotations, all hidden to reveal in order ----
line(vref, (440, 210), (440, 386)); // vertical reference
color(vref, dim); stroke(vref, 2); hidden(vref);
bracelabel(Lb, (440, 210), (575, 323), "L = 1.6 m", 26); // rod length
color(Lb, gold); hidden(Lb); hidden(Lb.label);
text(pivL, (330, 200), "pivot (fixed)"); size(pivL, 18); color(pivL, dim); display(pivL); hidden(pivL);
arrow(pivA, (388, 205), (428, 209)); color(pivA, dim); stroke(pivA, 2); hidden(pivA);
text(bobL, (600, 355), "bob — mass m"); size(bobL, 18); color(bobL, magenta); display(bobL); hidden(bobL);
arrow(bobA, (628, 345), (585, 330)); color(bobA, dim); stroke(bobA, 2); hidden(bobA);
text(angL, (472, 300), "θ₀ = 50°"); size(angL, 18); color(angL, lime); display(angL); hidden(angL);
text(cap, (cx, h - 42), ""); size(cap, 20); color(cap, dim); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
section("Anatomy");
say(cap, "a fixed pivot …", 0.4);
show(p.pivot, 0.4); pulse(p.pivot); show(pivL, 0.4); draw(pivA, 0.3);
wait(0.5);
say(cap, "… a rigid rod of length L …", 0.4);
show(p.rod, 0.4); show(Lb, 0.4); show(Lb.label, 0.4);
wait(0.6);
say(cap, "… and a bob of mass m at the end", 0.4);
show(p.bob, 0.5); recolor(p.bob, magenta, 0.3); pulse(p.bob); show(bobL, 0.4); draw(bobA, 0.3);
wait(0.5);
say(cap, "all one group — a base broadcast flashes every part at once", 0.4);
flash(p.parts, lime); // std broadcast over the whole sim
wait(0.7);
section("Release");
say(cap, "held at θ₀ from the vertical, then let go", 0.4);
show(vref, 0.4); show(angL, 0.4); flash(p.rod, cyan);
wait(0.9);
// clear the static annotations before the motion
fade(Lb, 0.3); fade(Lb.label, 0.3); fade(pivA, 0.3); fade(bobA, 0.3);
fade(pivL, 0.3); fade(bobL, 0.3); fade(angL, 0.3); fade(vref, 0.3);
wait(0.3);
section("Swing");
say(cap, "gravity pulls it back — velocity arrow + energy bars ride along", 0.4);
show(p.overlays, 0.4); // reveal the velocity arrow + KE/PE bars
draw(p.path, 0.8); // trace the arc it will follow
section("Energy");
say(cap, "and the SAME swing, read as energy over time", 0.4);
show(p.energy, 0.5); // reveal the energy graph, then run it all
swing(p, 10); // every panel + annotation-free scene animates
spring
A mass on a spring (simple harmonic motion) drawn with a real stretching coil — the
same generic views on a different system; note the energy well is a parabola
(½kx²) rather than the pendulum’s cosine (spring + the views + run).
// ============================================================================
// spring.manic — the physics kit's SECOND sim (Layer 1)
// ----------------------------------------------------------------------------
// `spring(id, [center], [stiffness], [x0], [unit], [damping])` — a mass on a
// spring, PRE-SIMULATED with RK4 (deterministic). Only `id` is required. It's a
// different system from the pendulum, yet it inherits the SAME generic views for
// free — note the energy well here is a PARABOLA U(x)=½kx² (the pendulum's is a
// cosine). `run(id, [dur])` replays the motion (alias: `swing`).
//
// TO ADAPT: add damping (6th arg) — the phase ellipse spirals in, the ball
// settles to the bottom of the parabola, and total energy decays:
// `spring(s, (360,320), 10, 1.4, 110, 0.6)`.
// ============================================================================
title("Mass on a spring — four views");
canvas("16:9");
text(hdr, (cx, 44), "A different sim — same four views (well is a parabola)");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the mass–spring on the left
spring(s, (330, 300), 10, 1.4, 110);
// the same generic views of the SAME simulation, 2×2 on the right
phase(s, (760, 175), 90); // x vs v — an ellipse
timegraph(s, (1010, 175), 90); // x(t), v(t) with a sweep line
well(s, (760, 460), 90); // parabolic energy well U(x)=½kx²
energygraph(s, (1010, 460), 90); // KE / PE / total over time
text(cap, (cx, h - 32), "one mass–spring, seen as motion · phase ellipse · time series · parabolic well · energy");
size(cap, 16); color(cap, dim); display(cap);
run(s, 10); // every panel animates in lockstep
spring-damped
The damped spring: the coil’s oscillation decays, the phase ellipse spirals in, the ball settles in the parabola, and total energy bleeds away.
// ============================================================================
// spring-damped.manic — the same four views, now with friction
// ----------------------------------------------------------------------------
// Identical to `spring.manic` but with `damping` (the 6th arg) turned on, so the
// oscillation loses energy. Watch every panel respond together:
// · sim — the coil's swing SHRINKS each cycle (settles to rest)
// · phase — the ellipse becomes an INWARD SPIRAL
// · well — the ball SETTLES to the bottom of the parabola U(x)=½kx²
// · energygraph — the TOTAL (gold) line DECAYS (KE↔PE trade, sum drops)
//
// Set damping = 0 (or drop the 6th arg) for the frictionless version, where the
// ellipse stays closed and the total-energy line stays flat (conservation).
// ============================================================================
title("Damped spring — energy bleeds away");
canvas("16:9");
text(hdr, (cx, 44), "Add friction: the oscillation decays, every view follows");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the mass–spring (left) — 6th arg 0.6 is the damping
spring(s, (330, 300), 10, 1.4, 110, 0.6);
// the same generic views of the SAME (now damped) simulation
phase(s, (760, 175), 90); // ellipse → inward spiral
timegraph(s, (1010, 175), 90); // x(t), v(t) — envelope shrinks
well(s, (760, 460), 90); // ball settles to the bottom of the parabola
energygraph(s, (1010, 460), 90); // total energy decays
text(cap, (cx, h - 32), "damping = 0.6 · the spiral, settling ball, and decaying total energy all show the same loss");
size(cap, 16); color(cap, dim); display(cap);
run(s, 10);
spring-annotated
Elevating the spring with a TYPEWRITER lab-note (type + cursor) and LIVE COUNTERS
(counter + to(_, value, …)) ticking k and the period up — Hooke’s law → parabolic
well → SHM, with no stage-covering section cards. One of three elevation styles.
// ============================================================================
// spring-annotated.manic — ELEVATE a sim: typewriter narration + live data
// ----------------------------------------------------------------------------
// One elevation flavour of several (see pulley-annotated for camera work,
// brachistochrone-annotated for kinetic typography). Here the story is told by
// a TYPEWRITER lab-note (`type` + `cursor`) and LIVE COUNTERS (`counter` +
// `to(_, value, …)`) that tick up — no stage-covering section cards, so the
// motion is never hidden. The spring's parts are ordinary entities the whole
// language drives directly.
// ============================================================================
title("Anatomy of a Spring — Hooke's Law & SHM");
canvas("16:9");
text(hdr, (cx, 40), "Anatomy of a Spring"); size(hdr, 28); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// ---- the sim + views (staged) ----
spring(sp, (340, 320), 10, 1.4, 105);
hidden(sp.wall); hidden(sp.spring); hidden(sp.mass); hidden(sp.overlays);
untraced(sp.path);
well(sp, (1015, 230), 112); hidden(sp.well);
energygraph(sp, (1015, 480), 112); hidden(sp.energy);
// ---- part annotations ----
line(eq, (340, 268), (340, 372)); color(eq, dim); stroke(eq, 2); untraced(eq);
text(coilL, (285, 232), "spring, stiffness k"); size(coilL, 16); color(coilL, lime); display(coilL); hidden(coilL);
text(massL, (520, 272), "mass m"); size(massL, 16); color(massL, cyan); display(massL); hidden(massL);
bracelabel(xb, (340, 372), (487, 372), "x₀", 22); color(xb, gold); hidden(xb); hidden(xb.label);
text(hooke, (340, 168), "F = −k·x"); size(hooke, 24); color(hooke, gold); display(hooke); hidden(hooke);
// ---- a TYPEWRITER lab-note with a live cursor (the narration device) ----
text(note, (cx, h - 44), ""); size(note, 21); color(note, fg); display(note); cursor(note);
text(kick, (96, 92), ""); size(kick, 17); color(kick, magenta); bold(kick); display(kick);
// ---- LIVE COUNTERS (tick up on reveal) ----
counter(kC, (150, 470), 0, 1, "k = ", " N/m"); size(kC, 26); color(kC, lime); display(kC); hidden(kC);
counter(tC, (150, 512), 0, 2, "T = ", " s"); size(tC, 26); color(tC, cyan); display(tC); hidden(tC);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
say(kick, "SETUP", 0.2);
say(note, "a wall, a coil of stiffness k, and a mass", 0.1); type(note, 1.4);
show(sp.wall, 0.4); pulse(sp.wall); show(coilL, 0.4);
show(sp.spring, 0.4); flash(sp.spring, lime); show(sp.mass, 0.4); pulse(sp.mass); show(massL, 0.4);
show(kC, 0.3); to(kC, value, 10, 0.8);
wait(0.5);
say(kick, "HOOKE'S LAW", 0.2);
say(note, "pull it x₀ from rest — it pulls back, F = −k·x", 0.1); type(note, 1.7);
draw(eq, 0.4); show(xb, 0.4); show(xb.label, 0.4); show(hooke, 0.4); flash(sp.spring, gold);
show(tC, 0.3); to(tC, value, 1.99, 0.9); // period 2π√(m/k)
wait(0.6);
fade(coilL, 0.3); fade(massL, 0.3); fade(xb, 0.3); fade(xb.label, 0.3); fade(hooke, 0.3);
wait(0.2);
say(kick, "MOTION", 0.2);
say(note, "release — simple harmonic motion; every panel tells the same swing", 0.1); type(note, 1.9);
show(sp.well, 0.5); show(sp.overlays, 0.4); show(sp.energy, 0.5); draw(sp.path, 0.6);
run(sp, 10);
spring-paper
The SAME spring sim dressed as a textbook figure AND run: template("paper") inks it,
a hatched support wall, a forest-green coil and outlined mass box, Hooke’s law and x₀
revealed, then run plays the SHM — the paper treatment on a LIVE sim (see pulley-paper).
// ============================================================================
// spring-paper.manic — an ANIMATED sim in textbook paper style
// ----------------------------------------------------------------------------
// The `spring` sim (mass on a spring, SHM), dressed as a textbook figure AND run:
// `template("paper")` inks it automatically, a hatched `support` wall, the coil
// in forest green, an outlined mass box, plus a base-manic reveal (Hooke's law,
// equilibrium, x₀) — then `run` plays the oscillation. The companion to
// pulley-paper: the paper/support treatment on another live sim.
// ============================================================================
title("Mass on a spring — textbook style");
canvas("16:9");
template("paper");
text(hdr, (cx, 46), "Mass on a spring — Hooke's law"); color(hdr, fg); size(hdr, 26); bold(hdr); display(hdr); hidden(hdr);
// the sim, restyled to textbook ink (parts hidden, revealed in order)
spring(sp, (380, 300), 10, 1.3, 110);
outlined(sp.mass); outline(sp.mass, fg);
hidden(sp.wall); hidden(sp.spring); hidden(sp.mass); hidden(sp.overlays);
untraced(sp.path);
// a hatched wall stands in for the sim's plain anchor; equilibrium reference
support(wall, (177, 300), 150, "right"); untraced(wall);
line(eq, (380, 246), (380, 354)); color(eq, dim); stroke(eq, 2); untraced(eq);
text(eqL, (380, 232), "equilibrium"); color(eqL, dim); size(eqL, 15); display(eqL); hidden(eqL);
text(coilL, (300, 214), "spring, stiffness k"); color(coilL, fg); size(coilL, 17); display(coilL); hidden(coilL);
text(massL, (548, 258), "mass m"); color(massL, fg); size(massL, 17); display(massL); hidden(massL);
bracelabel(xb, (380, 360), (523, 360), "x₀", 22); color(xb, fg); hidden(xb); hidden(xb.label);
text(hooke, (380, 152), "F = −k·x"); color(hooke, fg); size(hooke, 24); display(hooke); hidden(hooke);
text(cap, (cx, h - 40), ""); color(cap, fg); size(cap, 20); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
say(cap, "a coil of stiffness k fixed to a wall, with a mass on the end", 0.4);
draw(wall, 0.4); show(sp.spring, 0.4); show(coilL, 0.4); show(sp.mass, 0.4); show(massL, 0.3);
wait(0.4);
say(cap, "pull it x₀ from equilibrium — it pulls straight back, F = −k·x", 0.4);
draw(eq, 0.4); show(eqL, 0.3); show(xb, 0.4); show(xb.label, 0.4); show(hooke, 0.5);
wait(0.7);
fade(coilL, 0.3); fade(massL, 0.3); fade(xb, 0.3); fade(xb.label, 0.3); fade(hooke, 0.3);
say(cap, "release — simple harmonic motion", 0.4);
run(sp, 9);
double-pendulum
Deterministic chaos: two arms hinged end-to-end whose outer bob traces a wild,
unrepeatable curve — yet the render is frame-identical every run. A 4-D system, so
it shows phase (θ₁ vs θ₂) and energygraph but has no potential well
(doublependulum + views + run).
// ============================================================================
// double-pendulum.manic — chaos, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `doublependulum(id, [center], [angle1], [angle2], [unit])` — two arms hinged
// end-to-end: deterministic, yet exquisitely sensitive to initial conditions.
// PRE-SIMULATED with RK4 (so the render is frame-identical every run). It's a
// 4-D system, so it inherits `phase` (θ₁ vs θ₂), `timegraph`, and `energygraph`
// — but NOT `well` (there's no single-variable potential). `run(id,[dur])` plays
// it; drawing `{id}.path` in parallel traces the outer bob's chaotic curve.
//
// TO ADAPT: nudge angle2 by one degree and re-run — the trail diverges completely.
// ============================================================================
title("Double pendulum — deterministic chaos");
canvas("16:9");
text(hdr, (cx, 44), "Double pendulum — deterministic, yet unpredictable");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
// the chaotic sim on the left (outer bob traces the wild curve)
doublependulum(dp, (400, 250), 125, 110);
untraced(dp.path);
// the views that DO apply to a 4-D system (no potential well here)
phase(dp, (900, 190), 100); // θ₁ vs θ₂ — the coupled angles
energygraph(dp, (900, 480), 100); // KE / PE / total (total ~conserved)
text(cap, (cx, h - 32), "outer bob's trail is chaotic; total energy stays (nearly) conserved — no friction");
size(cap, 16); color(cap, dim); display(cap);
// trace the chaotic trail AS the pendulum swings (draw + run in parallel)
par {
run(dp, 12);
draw(dp.path, 12);
}
spring-pendulum
An elastic pendulum — a bob on a springy rod (drawn as a stretching coil) that both
swings and bounces, energy sloshing between the two modes (springpendulum).
// spring-pendulum.manic — the elastic pendulum: swings AND bounces.
// springpendulum(id,[center],[angle0],[stretch0],[unit],[damping]) — a bob on a
// springy rod (drawn as a stretching coil). Energy sloshes between the swing and
// the bounce, so the phase portrait and energy graph are richer than a rigid rod.
title("Elastic pendulum — swing meets bounce");
canvas("16:9");
text(hdr,(cx,44),"Spring pendulum — energy sloshes between swing and bounce");
size(hdr,23); color(hdr,cyan); bold(hdr); display(hdr);
springpendulum(sp, (380,230), 35, 0.5, 105);
untraced(sp.path);
phase(sp, (900,190), 95); // θ vs ω
energygraph(sp, (900,470), 95); // KE / PE / total (~conserved, lightly damped)
text(cap,(cx,h-32),"the coil stretches as it swings — a two-mode system"); size(cap,16); color(cap,dim); display(cap);
par { run(sp, 11); draw(sp.path, 11); }
kapitza
The Kapitza pendulum: vibrate the pivot fast enough and the inverted position
becomes stable — the bob hovers near the top instead of falling (kapitza).
// kapitza.manic — vibrate the pivot fast enough and the pendulum stands UP.
// kapitza(id,[center],[angle0deg],[vibeamp],[unit]) — a driven (Kapitza) pendulum;
// with a strong enough vibration the INVERTED position becomes stable.
title("Kapitza pendulum — stable upside-down");
canvas("16:9");
text(hdr,(cx,44),"Kapitza pendulum — fast pivot vibration stabilises 'up'");
size(hdr,23); color(hdr,cyan); bold(hdr); display(hdr);
kapitza(kp, (640,430), 165, 240, 150); // start near inverted, strong vibration
untraced(kp.path);
text(cap,(cx,h-32),"the gold pivot bobs fast; the bob hovers near the top (inverted)"); size(cap,16); color(cap,dim); display(cap);
par { run(kp, 9); draw(kp.path, 9); }
cart-pendulum
A pendulum on a spring-mounted cart rolling on a track — the classic control-theory
system; cart and bob trade momentum and energy (cartpendulum).
// cart-pendulum.manic — a pendulum on a spring-mounted cart (the control classic).
// cartpendulum(id,[center],[angle0deg],[unit]) — cart rolls on a track (spring to a
// wall) while the pendulum swings; the two exchange momentum and energy.
title("Cart-pendulum — coupled motion");
canvas("16:9");
text(hdr,(cx,44),"Cart-pendulum — the cart and bob trade momentum"); size(hdr,23); color(hdr,cyan); bold(hdr); display(hdr);
cartpendulum(cp, (480,330), 55, 105);
phase(cp, (1000,190), 95); // θ vs ω
energygraph(cp, (1000,470), 95); // KE / PE / total (~conserved)
text(cap,(cx,h-32),"cart on a spring + swinging bob — energy stays (nearly) conserved"); size(cap,16); color(cap,dim); display(cap);
run(cp, 11);
compare-pendulum
Sensitive dependence: two identical driven pendulums started 0.001 rad apart drift
onto completely different paths — the butterfly effect, watched in phase/timegraph
(comparependulum).
// compare-pendulum.manic — sensitive dependence: two pendulums 0.001 rad apart.
// comparependulum(id,[center],[angle0deg],[unit]) — identical driven-damped
// physics, a hair-different start — yet they diverge completely (the butterfly effect).
title("Two pendulums, one hair apart");
canvas("16:9");
text(hdr,(cx,44),"Sensitive dependence — a 0.001 rad difference explodes"); size(hdr,23); color(hdr,cyan); bold(hdr); display(hdr);
comparependulum(cm, (380,230), 12, 120);
phase(cm, (900,190), 95); // θ_A vs θ_B — starts on the diagonal, then scatters
timegraph(cm, (900,470), 95); // θ_A(t) & θ_B(t) diverge
text(cap,(cx,h-32),"cyan and magenta start together, then go their separate ways"); size(cap,16); color(cap,dim); display(cap);
run(cm, 13);
vertical-spring
A mass bobbing on a vertical spring under gravity — gravity shifts the equilibrium
but the energy well stays a parabola (verticalspring).
// vertical-spring.manic — a mass bobbing on a vertical spring under gravity.
title("Vertical spring — bobbing under gravity");
canvas("16:9");
text(hdr,(cx,44),"Vertical spring — a mass bobs about its stretched equilibrium"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
verticalspring(vs, (400,170), 0.7);
untraced(vs.path);
phase(vs, (900,190), 95); // d vs ḋ
well(vs, (900,470), 95); // parabolic well (shifted by gravity)
text(cap,(cx,h-32),"gravity shifts the equilibrium down; the well is still a parabola"); size(cap,16); color(cap,dim); display(cap);
par { run(vs, 10); draw(vs.path, 10); }
spring-incline
A mass on a spring on an inclined plane; gravity’s along-ramp component sets a new
stretched rest point it oscillates about (springincline).
// spring-incline.manic — a mass on a spring on an inclined plane.
title("Spring on an incline");
canvas("16:9");
text(hdr,(cx,44),"Spring on an incline — gravity's along-ramp pull shifts equilibrium"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
springincline(si, (360,180), 32);
untraced(si.path);
phase(si, (960,200), 100);
energygraph(si, (960,470), 100);
text(cap,(cx,h-32),"the bob oscillates along the ramp about its stretched rest point"); size(cap,16); color(cap,dim); display(cap);
par { run(si, 10); draw(si.path, 10); }
bungee
A bungee jump: free-fall, then a ONE-SIDED elastic cord (it only pulls) catches and
bounces the jumper — note the lopsided energy well (bungee).
// bungee.manic — free-fall, then a one-sided elastic cord catches the jumper.
title("Bungee jump — free-fall, then bounce");
canvas("16:9");
text(hdr,(cx,44),"Bungee — the cord only PULLS: free-fall, then an elastic bounce"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
bungee(bg, (640,110));
phase(bg, (1000,200), 95); // asymmetric well shows in the phase loop
well(bg, (1000,470), 95); // linear (fall) → parabola (cord): a lopsided well
text(cap,(cx,h-32),"note the lopsided energy well — flat during free-fall, steep once the cord bites"); size(cap,15); color(cap,dim); display(cap);
run(bg, 11);
resonance
A driven spring pushed near its natural frequency √(k/m): the amplitude climbs and
climbs — resonance, watched building up in phase/energygraph (resonance).
// resonance.manic — a driven spring pumped near its natural frequency.
title("Resonance — driving near the natural frequency");
canvas("16:9");
text(hdr,(cx,44),"Resonance — drive near √(k/m) and the amplitude grows and grows"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
resonance(rs, (400,300), 3.8); // natural freq = √(16/1) = 4; drive 3.8 ≈ resonance
untraced(rs.path);
phase(rs, (960,200), 100); // spiral OUT to a big steady-state loop
energygraph(rs, (960,470), 100); // energy climbs, then plateaus
text(cap,(cx,h-32),"drive frequency 3.8 vs natural 4.0 — near resonance, so it builds up big"); size(cap,15); color(cap,dim); display(cap);
par { run(rs, 12); draw(rs.path, 12); }
double-spring
Two masses coupled by springs between walls — push one and the energy sloshes back
and forth (beating); normal modes show as diagonals in phase (doublespring).
// double-spring.manic — two masses coupled by springs: energy sloshes (beating).
title("Coupled springs — energy sloshes back and forth");
canvas("16:9");
text(hdr,(cx,44),"Two coupled masses — push one, and the energy beats between them"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
doublespring(dd, (430,300), 85);
phase(dd, (980,200), 100); // x1 vs x2 — normal modes are diagonals
energygraph(dd, (980,470), 100);
text(cap,(cx,h-32),"block 1 (cyan) starts displaced; watch block 2 (magenta) pick up its swing"); size(cap,15); color(cap,dim); display(cap);
run(dd, 12);
series-parallel-springs
The same mass on springs in series (soft, slow) vs parallel (stiff, fast), side by
side — the timegraph makes the frequency difference obvious (seriesparallel).
// series-parallel-springs.manic — same mass, springs in series vs parallel.
title("Series vs parallel springs");
canvas("16:9");
text(hdr,(cx,44),"Same mass, same springs — series is soft (slow), parallel is stiff (fast)"); size(hdr,21); color(hdr,cyan); bold(hdr); display(hdr);
seriesparallel(sp, (560,150), 68);
timegraph(sp, (1060,320), 95); // y_s(t) vs y_p(t): parallel oscillates faster
text(cap,(cx,h-30),"parallel adds stiffness (k₁+k₂); series divides it (1/k = 1/k₁+1/k₂)"); size(cap,15); color(cap,dim); display(cap);
run(sp, 11);
car-suspension
A quarter-car riding a scrolling road — a speed bump, a washboard stretch, and a
pothole — its spring+damper soaking up the ride (carsuspension).
// car-suspension.manic — a quarter-car riding a road: bump, washboard, pothole.
title("Car suspension — riding the road");
canvas("16:9");
text(hdr,(cx,44),"Quarter-car suspension — the body bobs as the wheel rides the road"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
carsuspension(car, (640,430));
energygraph(car, (1050,170), 100);
text(cap,(cx,h-30),"a speed bump, a washboard stretch, then a pothole — the spring+damper soak it up"); size(cap,15); color(cap,dim); display(cap);
run(car, 12);
car-suspension-annotated
A marketing hero: the quarter-car suspension on a template("paper") brochure page,
elevated with generic base-manic — a live counter (sprung mass), leader-arrow
callouts, and an energygraph of the shock being absorbed — riding a scrolling road.
// ============================================================================
// car-suspension-annotated.manic — a marketing hero, paper (brochure) style
// ----------------------------------------------------------------------------
// The `carsuspension` quarter-car sim, dressed as a clean product diagram on a
// `template("paper")` page and ELEVATED with generic base-manic: a live
// `counter` (the sprung mass ticking up), leader-`arrow` callouts, a
// `bracelabel` for the travel, and a compact `energygraph` showing the shock
// being absorbed — then it rides a scrolling road (bump · washboard · pothole).
// Every part of the sim is an ordinary entity, so it all composes.
// ============================================================================
title("Car suspension — smooth over every bump");
canvas("16:9");
template("paper");
// ---- hero title ----
text(hdr, (cx, 52), "Car suspension: how it soaks up the road"); color(hdr, fg); size(hdr, 30); bold(hdr); display(hdr); hidden(hdr);
text(sub, (cx, 88), "the quarter-car model — sprung mass · spring + damper · wheel"); color(sub, dim); size(sub, 18); display(sub); hidden(sub);
// ---- the sim, recoloured for a bold, clean brochure look ----
carsuspension(car, (430, 430));
color(car.body, blue); // the car body — bold accent
color(car.road, fg); // the road — dark ink
hidden(car); // reveal the whole sim (road, wheel, spring, body, energy) together
// a compact energy panel (top-right) — the suspension absorbing the shock
energygraph(car, (1055, 215), 88);
// ---- generic-kit callouts (leader arrows + labels), hidden to reveal in order ----
text(cb, (640, 296), "car body — the sprung mass"); color(cb, blue); size(cb, 18); display(cb); hidden(cb);
arrow(ab, (636, 302), (478, 320)); color(ab, dim); stroke(ab, 2); hidden(ab);
text(csp, (610, 382), "spring + damper"); color(csp, lime); size(csp, 18); display(csp); hidden(csp);
arrow(asp, (606, 384), (448, 382)); color(asp, dim); stroke(asp, 2); hidden(asp);
text(cw, (620, 472), "wheel — the unsprung mass"); color(cw, gold); size(cw, 18); display(cw); hidden(cw);
arrow(aw, (616, 470), (448, 434)); color(aw, dim); stroke(aw, 2); hidden(aw);
text(cr, (cx, 648), "the road: a bump, a washboard stretch, then a pothole"); color(cr, fg); size(cr, 17); display(cr); hidden(cr);
// a live spec counter (generic kit) — the sprung mass ticking up
counter(mc, (150, 320), 0, 0, "m = ", " kg"); color(mc, fg); size(mc, 30); display(mc); hidden(mc);
text(spec, (cx, h - 28), "quarter-car · k = 20 kN/m · c = 4 kN·s/m · v = 8 m/s"); color(spec, dim); size(spec, 16); display(spec); hidden(spec);
// ============================== SCRIPT ==============================
show(hdr, 0.5); show(sub, 0.4);
wait(0.3);
show(car, 0.6);
wait(0.3);
show(cb, 0.3); draw(ab, 0.3);
show(csp, 0.3); draw(asp, 0.3);
show(cw, 0.3); draw(aw, 0.3);
show(cr, 0.3);
show(mc, 0.3); to(mc, value, 500, 0.9); // the sprung mass counts up
show(spec, 0.4);
wait(0.5);
// the ride — the road scrolls, the body glides while the wheel tracks every bump
run(car, 11);
piston
An engine piston: a spinning crank + connecting rod turn rotation into the piston’s
up-and-down stroke — the slider-crank mechanism (piston).
// piston.manic — an engine piston: a spinning crank drives a slider in a cylinder.
title("Engine piston — the slider-crank");
canvas("16:9");
text(hdr,(cx,46),"Slider-crank — a spinning crank becomes up-and-down motion"); size(hdr,23); color(hdr,cyan); bold(hdr); display(hdr);
piston(eng, (cx, 480), 60);
text(cap,(cx,h-32),"gold crank sweeps the circle; the rod converts it to the piston's stroke"); size(cap,16); color(cap,dim); display(cap);
run(eng, 9);
molecule
A molecule as balls and springs — atoms bonded on every side, vibrating about their
equilibrium shape with the total energy conserved (molecule).
// molecule.manic — atoms bonded by springs, vibrating about their shape.
title("Vibrating molecule");
canvas("16:9");
text(hdr,(cx,46),"A molecule as balls and springs — bonds stretch and the atoms jiggle"); size(hdr,22); color(hdr,cyan); bold(hdr); display(hdr);
molecule(mol, (520, 300), 3);
energygraph(mol, (1000, 300), 110); // total energy is conserved as it vibrates
text(cap,(cx,h-32),"one atom starts pulled out; the bonds pull it back and the whole thing rings"); size(cap,15); color(cap,dim); display(cap);
run(mol, 11);
robot-arm
A two-link robot arm reaching for a target: the joint rates come from the analytic
inverse Jacobian, so the arm drives its end-effector to the goal and settles there —
inverse kinematics as a solved motion (robotarm).
// ============================================================================
// robot-arm.manic — inverse kinematics, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `robotarm(id, [center], [mode], [unit])` — a two-link arm that tracks a target
// by inverse kinematics. Its joint rates are the analytic 2×2 inverse Jacobian
// times a gain on the end-effector error, so the gripper chases the target.
// `mode` 1 = trace a circle (default), 2 = figure-8, 0 = reach a fixed point and
// settle. PRE-SIMULATED with RK4; `run(id,[dur])` replays it, and `{id}.path`
// traces the route the gripper sweeps.
//
// TO ADAPT: set mode 2 for a figure-8, or mode 0 to reach one fixed point.
// ============================================================================
title("Robot arm — tracking by inverse kinematics");
canvas("16:9");
text(hdr, (cx, 46), "Two-link arm — inverse kinematics keeps the gripper on the moving target");
size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
robotarm(rb, (cx, 470), 1);
text(cap, (cx, h - 32), "gold shoulder + cyan forearm chase the lime ring around its circle — the gripper traces the path");
size(cap, 15); color(cap, dim); display(cap);
run(rb, 10);
pulley
The Atwood machine: two masses over one pulley, the heavier one accelerating down at
(m₁−m₂)g/(m₁+m₂). energygraph shows kinetic energy climbing as potential falls
(pulley).
// ============================================================================
// pulley.manic — the Atwood machine, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `pulley(id, [center], [m1], [m2], [unit])` — two masses over one pulley. The
// heavier one accelerates down at a = (m₁−m₂)g/(m₁+m₂). PRE-SIMULATED with RK4;
// `run(id,[dur])` replays it. `energygraph` shows the KE↔PE trade as the system
// speeds up (total is conserved — no friction).
//
// TO ADAPT: set m1 and m2 closer together for a gentler acceleration.
// ============================================================================
title("Atwood machine — unequal masses accelerate");
canvas("16:9");
text(hdr, (cx, 44), "Atwood machine — a = (m₁−m₂)g / (m₁+m₂)");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
pulley(pl, (440, 170), 3, 2);
energygraph(pl, (960, 330), 120);
text(cap, (cx, h - 32), "cyan m₁ (heavier) sinks, magenta m₂ rises; kinetic energy climbs as potential falls");
size(cap, 16); color(cap, dim); display(cap);
run(pl, 5);
pulley-scale
The surprise every physics class remembers: an in-line spring scale on an Atwood
machine reads the rope TENSION 2·m₁·m₂·g/(m₁+m₂) — not the sum of the two weights
(pulleyscale).
// ============================================================================
// pulley-scale.manic — what does the scale read? (physics Layer 1)
// ----------------------------------------------------------------------------
// `pulleyscale(id, [center], [m1], [m2], [unit])` — an Atwood machine over two
// pulleys with a spring scale in the rope between them. The classic surprise:
// the scale reads the rope TENSION 2·m₁·m₂·g/(m₁+m₂), NOT the sum of the two
// weights. PRE-SIMULATED with RK4; `run(id,[dur])` replays it.
//
// TO ADAPT: set m1 = m2 — the system balances and the scale reads exactly m·g.
// ============================================================================
title("Pulley scale — it reads the tension, not the weight");
canvas("16:9");
text(hdr, (cx, 46), "The scale reads the rope tension T = 2·m₁·m₂·g / (m₁+m₂)");
size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
pulleyscale(ps, (cx, 200), 4, 3);
text(cap, (cx, h - 32), "not (m₁+m₂)g, not the heavier weight — the tension sits between the two weights");
size(cap, 16); color(cap, dim); display(cap);
run(ps, 5);
block-tackle
A compound pulley (block & tackle): a load on a movable block held by N rope strands,
pulled by an effort mass. N strands = a mechanical advantage of N — an effort of only
load/N balances the load, but the effort end travels N× as far (blocktackle).
// ============================================================================
// block-tackle.manic — the compound pulley (mechanical advantage), Layer 1
// ----------------------------------------------------------------------------
// `blocktackle(id, [center], [load], [effort], [strands], [unit])` — a load on a
// movable block held by N rope strands and pulled by an effort mass. The N
// strands give a MECHANICAL ADVANTAGE of N: an effort of only load/N balances
// the load, and the effort end travels N× as far as the load rises. PRE-SIMULATED
// with RK4; `run(id,[dur])` replays it. N = 1 is just the Atwood machine.
//
// TO ADAPT: change `strands` (1–4) — more strands lift the same load with less
// effort, but the effort mass has to travel that much further.
// ============================================================================
title("Block & Tackle — a compound pulley's mechanical advantage");
canvas("16:9");
text(hdr, (cx, 40), "Block & tackle — 3 strands lift 8 kg with 3 kg of effort");
size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
blocktackle(bt, (500, 130), 8, 3, 3);
energygraph(bt, (1030, 330), 120);
text(cap, (cx, h - 32), "3 strands ⇒ MA = 3: effort just over load/3 lifts it; the effort end travels 3× as far");
size(cap, 15); color(cap, dim); display(cap);
run(bt, 5);
compound-pulley
A compound pulley with a MOVABLE pulley: a fixed top pulley carries mass A on one side
and a movable lower pulley on the other; that pulley carries B and C. The string
constraints link them (a_A = −a_P, a_B + a_C = 2·a_P); static when mA = mB+mC
(compoundpulley).
// ============================================================================
// compound-pulley.manic — fixed + movable pulley, three masses (physics L1)
// ----------------------------------------------------------------------------
// `compoundpulley(id, [center], [mA], [mB], [mC], [unit])` — a fixed top pulley
// carries mass A on one side and a MOVABLE lower pulley on the other; the movable
// pulley carries masses B and C. The string constraints link them: a_A = −a_P and
// a_B + a_C = 2·a_P (the massless movable pulley gives T₁ = 2·T₂). Static exactly
// when mA = mB + mC. PRE-SIMULATED with RK4; `run(id,[dur])` replays it.
//
// TO ADAPT: set mA = mB + mC and it balances; make A heavier and it hauls B, C up.
// ============================================================================
title("Compound pulley — fixed + movable, masses A, B, C");
canvas("16:9");
text(hdr, (cx, 40), "Compound pulley — heavier A hauls B and C upward");
size(hdr, 23); color(hdr, cyan); bold(hdr); display(hdr);
compoundpulley(cp, (470, 120), 5, 2, 2);
energygraph(cp, (1010, 330), 120);
text(cap, (cx, h - 32), "A (5 kg) > B+C (4 kg): A sinks, the movable pulley rises, B and C are pulled up together");
size(cap, 15); color(cap, dim); display(cap);
run(cp, 4);
incline-pulley
The incline-Atwood: a block on an incline tied over a pulley at the top to a hanging
mass. m₂ outpulls m₁·sinθ, so the block climbs while the mass descends — energygraph
tracks the KE↔PE trade (inclinepulley).
// incline-pulley.manic — the incline-Atwood: a block on an incline tied over a
// pulley at the top to a hanging mass. `inclinepulley(id,[center],[angle],[m1],
// [m2],[unit])`. PRE-SIMULATED (RK4); `run(id)` plays it; `energygraph` shows the
// KE↔PE trade. Matches the classic textbook figure.
title("Incline + pulley — the incline-Atwood");
canvas("16:9");
text(hdr, (cx, 44), "Block on an incline, tied over a pulley to a hanging mass"); size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
inclinepulley(ip, (280, 500), 30, 3, 2);
energygraph(ip, (1010, 320), 120);
text(cap, (cx, h - 32), "m₂ (2 kg) hanging outpulls m₁·sinθ, so m₁ climbs the incline and m₂ descends"); size(cap, 15); color(cap, dim); display(cap);
run(ip, 4);
double-incline
Two blocks on a wedge’s two slopes, tied over a pulley at the apex (right slope rough).
The 70 kg block on the gentle 30° slope beats the 12 kg block on the steep 50° smooth
slope — connected motion on two inclines (doubleincline).
// double-incline.manic — two blocks on a wedge's two slopes, tied over a pulley
// at the apex. `doubleincline(id,[center],[angle1],[angle2],[m1],[m2],[unit])`;
// the right slope is rough. PRE-SIMULATED (RK4); `run(id)` plays it.
title("Double incline — connected blocks on two slopes");
canvas("16:9");
text(hdr, (cx, 42), "Two slopes, one rope over the apex — which way does it slide?"); size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
doubleincline(di, (cx, 520), 50, 30, 12, 70);
text(cap, (cx, h - 30), "M₂ (70 kg) on the gentle rough slope beats M₁ (12 kg) on the steep smooth one"); size(cap, 15); color(cap, dim); display(cap);
run(di, 5);
incline-bumper
A block slides down an incline into a spring bumper at the base, compresses it, and
launches back up — one-sided contact, gravity PE ↔ kinetic ↔ spring PE, energy
conserved (inclinebumper).
// incline-bumper.manic — a block slides down an incline into a spring bumper at
// the base, compresses it, and launches back up (one-sided contact).
// `inclinebumper(id,[center],[angle],[mass],[stiffness],[unit])`. PRE-SIMULATED
// (RK4); `energygraph` shows gravity PE ↔ kinetic ↔ spring PE (conserved).
title("Incline + spring bumper — slide, compress, launch");
canvas("16:9");
text(hdr, (cx, 44), "A block slides down and compresses a spring at the base, then launches back"); size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
inclinebumper(ib, (300, 500), 40, 2, 500);
energygraph(ib, (1010, 320), 120);
text(cap, (cx, h - 32), "gravity PE → kinetic → spring PE → back: one-sided contact, energy conserved"); size(cap, 15); color(cap, dim); display(cap);
run(ib, 6);
collide-blocks
The classic momentum demo: block 1 hangs on a spring to the wall, block 2 slides in and
they collide. A live Σp readout shows momentum conserved at every collision; elastic
(e=1) keeps total energy flat while it sloshs between KE and the spring (collideblocks).
// collide-blocks.manic — the classic momentum demo. Block 1 (left) is attached to
// the wall by a SPRING; block 2 slides in freely and they collide with restitution
// e (1 = elastic → energy conserved; <1 → lost). The live Σp readout (top) shows
// momentum is conserved at every collision. Built on the shared `collide_1d`.
title("Colliding blocks — momentum is conserved");
canvas("16:9");
text(hdr, (cx, 96), "Block 1 on a spring, block 2 sliding in — watch the momentum readout"); size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
collideblocks(cb, (cx, 440), 3, 1, 1);
energygraph(cb, (1040, 210), 90);
text(cap, (cx, h - 36), "elastic (e=1): total energy (KE + spring PE) is conserved; Σp is conserved at each collision"); size(cap, 15); color(cap, dim); display(cap);
run(cb, 10);
collide-blocks-annotated
Conservation of momentum, the MANIC way — not a 1:1 port of the lab sim but a guided lesson: the live Σp readout as the star, the KE↔spring-PE energy view, staged callouts, and honest narration (with a wall-spring, Σp is conserved AT each collision, not constant).
// ============================================================================
// collide-blocks-annotated.manic — conservation of momentum, the MANIC way
// ----------------------------------------------------------------------------
// Not a 1:1 port of the lab sim — a guided lesson that ELEVATES it: the live Σp
// readout, the KE↔spring-PE energy view, staged callouts, and honest narration
// (with a wall-spring, Σp is conserved AT each collision — Newton's 3rd law — not
// constant, since the spring is an external force). All base manic over the sim.
// ============================================================================
title("Conservation of Momentum");
canvas("16:9");
// the sim's Σp readout sits at the very top (y≈46) — the star of the scene
collideblocks(cb, (cx, 460), 3, 1, 1);
hidden(cb); // the bare id hides every part + the readout + energy view
energygraph(cb, (1055, 250), 84); // KE ↔ spring PE (tagged cb, revealed by show(cb))
text(hdr, (cx, 104), "Conservation of Momentum"); size(hdr, 30); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
text(sub, (cx, 140), "block 1 on a spring · block 2 slides in · watch Σp through the collision"); size(sub, 17); color(sub, dim); display(sub); hidden(sub);
// callouts
text(csp, (360, 330), "spring holds block 1 to the wall"); size(csp, 16); color(csp, lime); display(csp); hidden(csp);
arrow(asp, (392, 344), (418, 424)); color(asp, dim); stroke(asp, 2); hidden(asp);
text(cb1, (470, 300), "block 1 · m₁ = 3 kg"); size(cb1, 16); color(cb1, cyan); display(cb1); hidden(cb1);
text(cb2, (800, 300), "block 2 · m₂ = 1 kg →"); size(cb2, 16); color(cb2, magenta); display(cb2); hidden(cb2);
text(cap, (cx, h - 38), ""); size(cap, 19); color(cap, fg); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5); show(sub, 0.4);
wait(0.3);
show(cb, 0.6); // sim + Σp readout + energy view
show(csp, 0.3); draw(asp, 0.3); show(cb1, 0.3); show(cb2, 0.3);
wait(0.4);
say(cap, "block 2 slides toward block 1, which is held by its spring", 0.4);
wait(0.5);
say(cap, "they collide — each pushes the other equal and opposite (Newton's 3rd law)", 0.4);
flash(cb.mom, gold);
wait(0.5);
say(cap, "so Σp doesn't jump at the hit — the spring only shifts it BETWEEN collisions", 0.4);
wait(0.5);
say(cap, "energy sloshes between motion and the spring, but the total stays put — elastic", 0.4);
run(cb, 12);
bullet-block
A bullet fired into a block EMBEDS (perfectly inelastic). The flight is slow-mo so you can
watch it cross, then a live speed readout collapses from 40 m/s to ~1 — momentum survives,
energy does not. Uses collide_1d(e=0) (bulletblock).
// bullet-block.manic — a bullet fired into a block EMBEDS (perfectly inelastic).
// `bulletblock(id,[center],[bulletmass],[speed],[blockmass],[unit])`. The combined
// mass crawls off at m_b·v_b/(m_b+M) — most of the kinetic energy is lost to the
// collision, so `energygraph`'s total STEPS DOWN at impact. Uses `collide_1d(e=0)`.
title("Bullet into a block — an inelastic collision");
canvas("16:9");
text(hdr, (cx, 70), "The bullet embeds: 40 m/s becomes ~1 m/s — most of the energy is gone"); size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
bulletblock(bb, (cx, 430), 0.05, 40, 1.95);
energygraph(bb, (1040, 200), 90);
text(cap, (cx, h - 36), "momentum is conserved, but kinetic energy is NOT — it drops sharply at impact"); size(cap, 15); color(cap, dim); display(cap);
run(bb, 6);
bullet-impact
BEST OF BOTH: the cinematic gun-shot (gun · muzzle flash · a flying cam/zoom · BOOM)
wrapped around the REAL bulletblock physics — the collision is genuinely inelastic, the
live speed readout actually collapses 40 → ~1, and the BOOM is synced to the true impact.
// ============================================================================
// bullet-impact.manic — best of both: the movie AND the physics
// ----------------------------------------------------------------------------
// The cinematic gun-shot (gun · muzzle flash · a flying camera · BOOM) wrapped
// around the REAL `bulletblock` physics sim. The collision is genuinely true —
// a perfectly inelastic impact, momentum conserved, the live speed readout
// actually collapsing 40 → ~1 — and the BOOM is SYNCED to the physical impact.
// A sim's parts are ordinary entities, so the story and the physics compose.
// ============================================================================
title("Impact — the movie meets the physics");
canvas("16:9");
// ---- the REAL physics: bullet + block, inelastic collision + live speed readout ----
// a lighter block, so the hit visibly KNOCKS it and it slides on afterward
bulletblock(bb, (cx, 430), 0.05, 40, 0.9);
hidden(bb);
sticky(bb.vel); // pin the live speed readout through the camera moves
// ---- cinematic dressing (base manic) ----
rect(barrel, (200, 380), 82, 20); color(barrel, dim); filled(barrel);
rect(body, (166, 386), 42, 34); color(body, dim); filled(body);
polygon(grip, (150, 404), (176, 404), (170, 442), (146, 438), dim);
circle(mflash, (242, 380), 28); color(mflash, gold); glow(mflash, 3.5); hidden(mflash);
text(boom, (730, 250), "BOOM!"); size(boom, 84); color(boom, magenta); bold(boom); glow(boom, 2.5); display(boom); hidden(boom);
for i in 0..14 {
let ang = i * tau / 14.0;
line(spark{i}, (730, 380), (730 + 150*cos(ang), 380 + 150*sin(ang)));
color(spark{i}, gold); stroke(spark{i}, 5); glow(spark{i}, 2); untraced(spark{i}); tag(spark{i}, sparks);
}
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 22); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
cam((430, 400), 0.4, smooth);
say(cap, "a scene — and it's really physics underneath", 0.4);
wait(0.5);
say(cap, "FIRE!", 0.2);
par { show(mflash, 0.06); pulse(mflash); show(bb, 0.1); }
fade(mflash, 0.3);
// the bullet flies (REAL physics, slow-mo) · the camera pushes toward the block ·
// the BOOM fires exactly when the sim's bullet embeds (55% of the run) · then it
// KEEPS GOING — the block is knocked, slides on, and the camera follows it
par {
run(bb, 7);
cam((720, 380), 3.6, smooth); // push toward the impact during the flight
zoom(1.2, 3.6, smooth);
seq {
wait(3.85); // the physical impact (55% of the run)
// CONTACT → zoom-punch + BOOM
say(cap, "CONTACT!", 0.12);
par { flash(bb.block, gold); shake(bb.block, 0.6); zoom(1.6, 0.15); show(boom, 0.12); pulse(boom); draw(sparks, 0.3); }
// …and it CONTINUES — sparks clear, the block slides on, camera follows
say(cap, "…knocked back — it slides on", 0.3);
par { fade(sparks, 0.5); fade(boom, 0.7); cam((1010, 380), 2.4, smooth); zoom(1.15, 2.4, smooth); }
}
}
// it comes to rest — and the physics was true the whole way through
par { zoom(1.0, 0.9, smooth); cam((cx, 360), 0.9, smooth); }
say(cap, "…and rest. A movie on top, real physics underneath: 40 m/s → the block's crawl", 0.4);
wait(1.0);
bullet-block-annotated
The bullet’s JOURNEY, the manic way: a gun fires, a muzzle flash, a glowing bullet crosses
the gap in slow-motion and embeds — the speed readout crashing 40 → ~1. A scene, not the
bare lab sim (base-manic staging over bulletblock).
// ============================================================================
// bullet-block-annotated.manic — a bullet's journey (inelastic collision)
// ----------------------------------------------------------------------------
// Not the bare lab sim — a scene: a gun fires, a muzzle flash, a glowing bullet
// crosses the gap in slow-motion, embeds in the block, and the live speed readout
// COLLAPSES from 40 m/s to ~1. The block barely lurches — it soaked up the bullet
// but almost none of the speed. Base-manic staging over the `bulletblock` sim.
// ============================================================================
title("A bullet fired into a block");
canvas("16:9");
text(hdr, (cx, 44), "A bullet fired into a block"); size(hdr, 30); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
text(sub, (cx, 80), "perfectly inelastic: the bullet embeds — momentum survives, speed collapses"); size(sub, 17); color(sub, dim); display(sub); hidden(sub);
// the sim (bullet at the muzzle, block at rest, floor, live speed readout, energy)
bulletblock(bb, (cx, 430), 0.05, 40, 1.95);
hidden(bb);
energygraph(bb, (1055, 250), 82);
// the gun (all base-manic shapes), at the bullet's start height
rect(barrel, (200, 380), 82, 20); color(barrel, dim); filled(barrel);
rect(body, (166, 386), 42, 34); color(body, dim); filled(body);
polygon(grip, (150, 404), (176, 404), (170, 442), (146, 438), dim);
circle(flash, (242, 380), 17); color(flash, gold); glow(flash, 2.4);
hidden(barrel); hidden(body); hidden(grip); hidden(flash);
// callouts
text(cbl, (300, 336), "the bullet — light + fast"); size(cbl, 16); color(cbl, red); display(cbl); hidden(cbl);
text(cblk, (720, 300), "the block — heavy, at rest"); size(cblk, 16); color(cblk, cyan); display(cblk); hidden(cblk);
text(cap, (cx, h - 38), ""); size(cap, 19); color(cap, fg); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5); show(sub, 0.4);
wait(0.3);
show(bb, 0.5); show(barrel, 0.3); show(body, 0.3); show(grip, 0.3);
show(cbl, 0.3); show(cblk, 0.3);
wait(0.4);
say(cap, "a heavy block sits at rest; a fast, light bullet is loaded", 0.4);
wait(0.5);
say(cap, "FIRE — 40 m/s across the gap …", 0.4);
flash(flash, gold); pulse(flash);
run(bb, 6);
say(cap, "… it embeds. Momentum is conserved, but 40 m/s collapses to ~1 — the energy is gone", 0.4);
wait(1.0);
newtons-cradle
Newton’s cradle: pull one ball, one swings out the far side — momentum and energy pass
straight through the chain. An EVENT-DRIVEN sim (free-flight pendulums between elastic
collisions resolved by a shared 1-D impulse), the crowd-pleaser (newtonscradle).
// newtons-cradle.manic — Newton's cradle: pull N balls, N swing out. An EVENT-
// DRIVEN sim — free-flight pendulums between elastic collisions resolved by the
// shared `collide_1d` impulse. `newtonscradle(id,[center],[balls],[pulled])`.
title("Newton's cradle — momentum passes through");
canvas("16:9");
text(hdr, (cx, 70), "Pull one, one swings out — momentum and energy pass straight through"); size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
newtonscradle(nc, (cx, 170), 5, 1);
energygraph(nc, (1040, 470), 90);
text(cap, (cx, h - 40), "five equal balls, elastic collisions (e = 1): the chain conserves momentum and energy"); size(cap, 15); color(cap, dim); display(cap);
run(nc, 8);
string-wave
A wave on a plucked string: 36 masses on springs, both ends fixed (the discretised wave
equation). Pluck it off-centre and the pulse splits, travels, and reflects off the ends —
a rainbow chain that wiggles, pre-simulated with RK4 (stringwave).
// string-wave.manic — a wave on a plucked string: N masses on springs, both ends
// fixed (the discretised wave equation). `stringwave(id,[center],[width],[amp],
// [pluck])`. Pluck it off-centre and the pulse splits into two, travels out, and
// reflects (inverting) off the fixed ends. Drawn as a rainbow chain that wiggles.
title("Wave on a string — pluck it and watch it travel");
canvas("16:9");
text(hdr, (cx, 70), "Pluck a string: the pulse splits, travels, and reflects off the ends"); size(hdr, 23); color(hdr, cyan); bold(hdr); display(hdr);
stringwave(sw, (cx, 380), 900, 110, 0.28);
text(cap, (cx, h - 40), "36 masses on springs, fixed at both ends — the wave equation, pre-simulated with RK4"); size(cap, 16); color(cap, dim); display(cap);
run(sw, 10);
loop-track
A ball rolls down a ramp and around a vertical LOOP-THE-LOOP — the curved-track case.
A bead energy solver (v = √(2g(H−y)) along the arc) so it visibly slows at the top;
release above 2·radius to clear it. energygraph tracks KE↔PE (looptrack).
// loop-track.manic — a ball rolls down a ramp and around a vertical loop-the-loop.
// `looptrack(id,[center],[radius],[height],[unit])`. A curved-track energy solver:
// v = √(2g(H−y)) along the arc, so the ball visibly SLOWS at the top. The release
// height must exceed 2·radius to clear the top. `energygraph` shows KE↔PE.
title("Loop-the-loop — down the ramp, around the loop");
canvas("16:9");
text(hdr, (cx, 44), "Release high enough (H > 2r) and the ball clears the top of the loop"); size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
looptrack(lt, (470, 560), 1, 3);
energygraph(lt, (1010, 330), 120);
text(cap, (cx, h - 32), "energy conserved: it trades kinetic for potential, slowest at the top of the loop"); size(cap, 15); color(cap, dim); display(cap);
run(lt, 5);
loop-cinematic
The loop-the-loop as a MOVIE with real physics inside: the camera pushes in as the ball
climbs, and the tension is genuine — a modest release height means it truly crawls over
the top before rocketing out. cam/zoom beats synced to the looptrack sim.
// ============================================================================
// loop-cinematic.manic — the loop-the-loop as a movie (real physics inside)
// ----------------------------------------------------------------------------
// Same recipe as bullet-impact: build the real `looptrack` sim, aim the camera,
// sync the beats to the physics. A ball is released, races down the ramp, and
// the camera pushes in as it climbs the loop — the tension is REAL (it genuinely
// slows at the top, energy traded for height), then it clears and rockets out.
// ============================================================================
title("Loop the Loop");
canvas("16:9");
// the real curved-track physics — a modest release height, so it BARELY clears
// the top (H just over 2·r) — that's where the real tension lives
looptrack(lt, (500, 560), 1, 2.4, 95);
hidden(lt);
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
cam((420, 400), 0.4, smooth); // frame the whole ramp + loop
say(cap, "release…", 0.4);
show(lt, 0.5);
wait(0.4);
// down the ramp, into the loop — the camera pushes in for the climb
par {
run(lt, 6.0);
seq {
say(cap, "released — down the ramp!", 0.3);
par { cam((500, 455), 3.0, smooth); zoom(1.35, 3.0, smooth); } // slow push through the descent + entry
say(cap, "up and over — will it clear the top?!", 0.3);
wait(1.7); // the climb + the crawl over the top
say(cap, "…MADE IT!", 0.25);
par { cam((470, 410), 1.3, smooth); zoom(1.1, 1.3, smooth); } // ease back as it rockets out
}
}
// the physics was the whole story
say(cap, "…real energy: fastest at the bottom, barely crawling over the top", 0.4);
wait(1.0);
spring-chain
Three blocks joined by two springs on an incline — coupled oscillators. Pull one and the
whole chain rings (normal modes / beating); shown in the incline’s frame since uniform
gravity doesn’t touch the internal motion (springchain).
// spring-chain.manic — three blocks joined by two springs on an incline: coupled
// oscillators / normal modes. `springchain(id,[center],[angle],[unit])`. A uniform
// gravity component doesn't change the internal motion, so it's shown in the
// incline's frame (CM held). `energygraph` shows the energy sloshing (beating).
title("Spring chain on an incline — coupled oscillators");
canvas("16:9");
text(hdr, (cx, 44), "Three masses, two springs — the energy sloshs between the modes"); size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
springchain(sc, (500, 300), 22);
energygraph(sc, (1010, 340), 120);
text(cap, (cx, h - 32), "pull one block and the whole chain rings — normal modes / beating (coupled springs)"); size(cap, 15); color(cap, dim); display(cap);
run(sc, 9);
incline-showcase
One paper page, FOUR live incline problems: a friction ramp, an incline+pulley, a
two-slope wedge, and a spring bumper — revealed one at a time with narration, then all
run in parallel. Real base-manic staging (template("paper") + hidden/show + say
par), not a physics dump.
// ============================================================================
// incline-showcase.manic — four incline problems, one paper page, all live
// ----------------------------------------------------------------------------
// A single annotated lesson that puts ALL FOUR animated incline sims on one
// `template("paper")` page and runs them together — real base-manic composition
// (staged reveal with `hidden`/`show`, `say` narration, per-quadrant labels),
// not a physics dump. Each sim's bare id broadcasts, so `hidden(rp)`/`show(rp)`
// address a whole sim at once. Finishes with all four running in parallel.
// ============================================================================
title("Four inclined-plane problems — one page");
canvas("16:9");
template("paper");
text(hdr, (cx, 34), "Four inclined-plane problems, one page"); color(hdr, fg); size(hdr, 26); bold(hdr); display(hdr); hidden(hdr);
// the four sims (each hidden; the bare id broadcasts to all its parts)
ramp(rp, (110, 340), 28, 5, 0, 45); hidden(rp);
inclinepulley(ip, (690, 340), 30, 3, 2, 42); hidden(ip);
doubleincline(dw, (300, 660), 50, 30, 12, 70, 34); hidden(dw);
inclinebumper(ib, (720, 660), 38, 2, 500, 42); hidden(ib);
// quadrant labels
text(l1, (150, 175), "① friction on a ramp"); color(l1, dim); size(l1, 17); display(l1); hidden(l1);
text(l2, (770, 175), "② incline + pulley"); color(l2, dim); size(l2, 17); display(l2); hidden(l2);
text(l3, (150, 505), "③ two-slope wedge"); color(l3, dim); size(l3, 17); display(l3); hidden(l3);
text(l4, (770, 505), "④ spring bumper"); color(l4, dim); size(l4, 17); display(l4); hidden(l4);
text(cap, (cx, h - 26), ""); color(cap, fg); size(cap, 19); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
say(cap, "one page, four classic incline problems — all real, pre-simulated physics", 0.4);
wait(0.5);
say(cap, "① a block sliding down a rough ramp", 0.4);
show(rp, 0.5); show(l1, 0.3);
wait(0.5);
say(cap, "② a block on an incline, tied over a pulley to a hanging mass", 0.4);
show(ip, 0.5); show(l2, 0.3);
wait(0.5);
say(cap, "③ two blocks on a wedge, connected over the apex", 0.4);
show(dw, 0.5); show(l3, 0.3);
wait(0.5);
say(cap, "④ a block sliding into a spring bumper at the base", 0.4);
show(ib, 0.5); show(l4, 0.3);
wait(0.6);
say(cap, "…and they all run — same page, same physics engine, four different motions", 0.4);
par {
run(rp, 7);
run(ip, 7);
run(dw, 7);
run(ib, 7);
}
textbook-incline-fbd
A block on an incline as a physics-class FREE-BODY DIAGRAM: the reusable forces(id)
view draws gravity/normal/friction/a vectors on the block, a second panel redraws them
from a point, and template("paper") inks it — then run slides the block (ramp).
// ============================================================================
// textbook-incline-fbd.manic — the free-body diagram, textbook style
// ----------------------------------------------------------------------------
// A block on an incline shown the classic physics-class way: the physical
// picture on the left with the force vectors ON the block (via the reusable
// `forces(id)` view on the `ramp` sim — gravity `mg`, normal `N`, friction `f`,
// and the acceleration `a`), and a free-body-diagram panel on the right drawing
// the same forces from a single point. `template("paper")` inks it; then `run`
// plays the slide with the vectors riding the block.
// ============================================================================
title("Block on an incline — the free-body diagram");
canvas("16:9");
template("paper");
text(hdr, (cx, 42), "Block on an incline — the free-body diagram"); color(hdr, fg); size(hdr, 24); bold(hdr); display(hdr);
// left: the incline sim, inked; its force vectors revealed by forces()
ramp(rp, (170, 470), 30);
outlined(rp.block); outline(rp.block, fg);
// right: the free-body diagram — the same four forces from one point
dot(o, (1000, 330), 6); color(o, fg);
text(fbdT, (1000, 205), "free-body diagram"); color(fbdT, dim); size(fbdT, 16); display(fbdT);
arrow(aN, (1000, 330), (955, 252)); color(aN, lime); stroke(aN, 3);
text(lN, (944, 240), "N"); color(lN, lime); size(lN, 18); display(lN);
arrow(af, (1000, 330), (1078, 285)); color(af, magenta); stroke(af, 3);
text(lf, (1086, 280), "f"); color(lf, magenta); size(lf, 18); display(lf);
arrow(ag, (1000, 330), (1000, 440)); color(ag, blue); stroke(ag, 3);
text(lg, (1010, 448), "mg"); color(lg, blue); size(lg, 18); display(lg);
arrow(aa, (1000, 330), (930, 372)); color(aa, red); stroke(aa, 3);
text(la, (908, 380), "a"); color(la, red); size(la, 18); display(la);
text(cap, (cx, h - 34), ""); color(cap, fg); size(cap, 19); display(cap);
// ============================== SCRIPT ==============================
say(cap, "three forces act on the block: gravity, the normal force, and friction", 0.4);
forces(rp, 0.9);
wait(1.0);
say(cap, "drawn from a single point, they make the free-body diagram →", 0.4);
wait(0.9);
say(cap, "their sum points down the slope, so the block accelerates and slides", 0.4);
run(rp, 5);
pulley-annotated
The Atwood machine elevated with CAMERA work: cam + zoom push in on the two masses
for the imbalance beat and glow the heavier one, a counter ticks the acceleration up,
then it pulls back to release — cinematography instead of section cards.
// ============================================================================
// pulley-annotated.manic — ELEVATE a sim: CAMERA work (cam + zoom)
// ----------------------------------------------------------------------------
// A different elevation flavour from spring-annotated (typewriter) — here the
// camera does the storytelling: `cam` + `zoom` push in on the two masses for
// the "imbalance" beat, glow the heavier one, then pull back for the release.
// A live `counter` ticks the acceleration up. No stage-covering section cards,
// so the motion is always visible. All base manic over the `pulley` parts.
// ============================================================================
title("The Atwood Machine — Why the Heavier Side Falls");
canvas("16:9");
text(hdr, (cx, 40), "The Atwood Machine"); size(hdr, 28); color(hdr, cyan); bold(hdr); display(hdr); hidden(hdr);
// ---- the sim, centred so the camera can push in ----
pulley(pl, (cx, 190), 3, 2);
hidden(pl.wheel); hidden(pl.hub); hidden(pl.ropeL); hidden(pl.ropeR); hidden(pl.mass1); hidden(pl.mass2);
text(wheelL, (cx, 118), "frictionless pulley"); size(wheelL, 17); color(wheelL, dim); display(wheelL); hidden(wheelL);
text(m1L, (cx - 150, 320), "m₁ = 3 kg"); size(m1L, 19); color(m1L, cyan); display(m1L); hidden(m1L);
text(m2L, (cx + 150, 320), "m₂ = 2 kg"); size(m2L, 19); color(m2L, magenta); display(m2L); hidden(m2L);
counter(aC, (cx, 250), 0, 2, "a = ", " m/s²"); size(aC, 26); color(aC, gold); display(aC); hidden(aC); sticky(aC);
// caption is `sticky` — it stays pinned to the screen through the camera push-in
text(cap, (cx, h - 44), ""); size(cap, 21); color(cap, dim); display(cap); sticky(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
say(cap, "one rope over one frictionless pulley …", 0.4);
show(pl.wheel, 0.4); show(pl.hub, 0.3); pulse(pl.wheel); show(wheelL, 0.4);
wait(0.3);
say(cap, "… a heavier mass on the left, a lighter one on the right", 0.4);
show(pl.ropeL, 0.3); show(pl.mass1, 0.4); pulse(pl.mass1); show(m1L, 0.4);
show(pl.ropeR, 0.3); show(pl.mass2, 0.4); pulse(pl.mass2); show(m2L, 0.4);
wait(0.5);
// ---- CAMERA: push in on the masses for the imbalance beat ----
// cap + aC are sticky, so they stay put on screen while the world zooms
say(cap, "look closely — the net pull favours the heavier side", 0.4);
par { cam((cx, 330), 1.4, smooth); zoom(1.7, 1.4, smooth); }
glow(pl.mass1, 1.9); flash(pl.mass1, gold); pulse(pl.mass1);
wait(1.0);
par { cam((cx, cy), 1.2, smooth); zoom(1, 1.2, smooth); } // pull back out
wait(0.2);
// the acceleration, as a live readout
show(aC, 0.3); to(aC, value, 1.96, 1.0); // a = (m₁−m₂)g/(m₁+m₂)
fade(wheelL, 0.3); fade(m1L, 0.3); fade(m2L, 0.3);
wait(0.5);
say(cap, "release: m₁ sinks, m₂ rises", 0.4);
run(pl, 4);
pulley-paper
The SAME Atwood sim dressed as a textbook figure AND run: template("paper") inks it
automatically, a hatched support ceiling, a forest-green wheel and outlined mass boxes,
a base-manic reveal, then run plays the motion — the paper treatment on a LIVE sim.
// ============================================================================
// pulley-paper.manic — an ANIMATED sim in textbook paper style
// ----------------------------------------------------------------------------
// The Atwood `pulley` sim, dressed as a physics-textbook figure AND run: the
// `template("paper")` page (its palette remap inks the sim automatically), a
// hatched `support` ceiling, a forest-green wheel, outlined mass boxes, and a
// base-manic reveal (`say`/`show`/`draw`/`fade`) — then `run` plays the motion.
// Proof the paper/support treatment composes with a live, animated simulation.
// ============================================================================
title("The Atwood machine — textbook style");
canvas("16:9");
template("paper"); // white page; the theme remaps neon → ink
text(hdr, (cx, 46), "The Atwood machine"); color(hdr, fg); size(hdr, 27); bold(hdr); display(hdr); hidden(hdr);
// hatched ceiling + suspension rope (traced on)
support(ceil, (cx, 108), 320); untraced(ceil);
line(sus, (cx, 108), (cx, 160)); color(sus, fg); stroke(sus, 2); untraced(sus);
// the sim, restyled to textbook ink (parts hidden, revealed in order)
pulley(pl, (cx, 192), 3, 2);
color(pl.wheel, lime); // lime → forest green on the paper palette
color(pl.hub, fg); color(pl.ropeL, fg); color(pl.ropeR, fg);
outlined(pl.mass1); outline(pl.mass1, fg);
outlined(pl.mass2); outline(pl.mass2, fg);
hidden(pl.wheel); hidden(pl.hub); hidden(pl.ropeL); hidden(pl.ropeR); hidden(pl.mass1); hidden(pl.mass2);
// annotations
text(m1L, (cx - 150, 316), "m₁ = 3 kg"); color(m1L, fg); size(m1L, 20); display(m1L); hidden(m1L);
text(m2L, (cx + 150, 316), "m₂ = 2 kg"); color(m2L, fg); size(m2L, 20); display(m2L); hidden(m2L);
text(acc, (cx, 604), "a = (m₁ − m₂)·g / (m₁ + m₂)"); color(acc, fg); size(acc, 22); display(acc); hidden(acc);
text(cap, (cx, h - 40), ""); color(cap, fg); size(cap, 20); display(cap);
// ============================== SCRIPT ==============================
show(hdr, 0.5);
say(cap, "a rope over one frictionless pulley, hung from the ceiling", 0.4);
draw(ceil, 0.4); draw(sus, 0.4); show(pl.wheel, 0.4); show(pl.hub, 0.3);
wait(0.4);
say(cap, "a heavier mass on the left, a lighter one on the right", 0.4);
show(pl.ropeL, 0.3); show(pl.mass1, 0.4); show(m1L, 0.3);
show(pl.ropeR, 0.3); show(pl.mass2, 0.4); show(m2L, 0.3);
wait(0.5);
say(cap, "the system accelerates toward the heavier side", 0.4);
show(acc, 0.5);
wait(0.7);
fade(m1L, 0.3); fade(m2L, 0.3);
say(cap, "release: m₁ sinks, m₂ rises", 0.4);
run(pl, 4);
ramp
A block sliding down an inclined plane with static/kinetic friction — the full force
model. Friction turns motion into heat, so the total-energy line steadily falls
(ramp + energygraph).
// ============================================================================
// ramp.manic — friction on an incline, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `ramp(id, [center], [angle], [mass], [applied], [unit])` — a block sliding
// down an inclined plane. Full force model: gravity along the slope, the normal
// force, and a static→kinetic friction switch. Friction turns mechanical energy
// into heat, so `energygraph`'s total DECAYS as the block slides. PRE-SIMULATED
// with RK4; `run(id,[dur])` replays it.
//
// TO ADAPT: lower the angle below the friction angle and the block won't budge.
// ============================================================================
title("Inclined plane — friction dissipates energy");
canvas("16:9");
text(hdr, (cx, 44), "Block on a ramp — μ friction bleeds the energy away");
size(hdr, 24); color(hdr, cyan); bold(hdr); display(hdr);
ramp(rp, (300, 470), 30);
energygraph(rp, (960, 320), 120);
text(cap, (cx, h - 32), "as the block slides down, the gold total energy line falls — friction converts it to heat");
size(cap, 16); color(cap, dim); display(cap);
run(rp, 6);
drop-mass
A mass dropped onto a spring-block STICKS — a perfectly inelastic collision. Watch the
total-energy line step down at impact, then the heavier combined mass oscillate about
a lower equilibrium (dropmass + energygraph).
// ============================================================================
// drop-mass.manic — an inelastic collision, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `dropmass(id, [center], [dropheight], [unit])` — a mass is dropped onto a
// block resting on a spring. It free-falls, STICKS (perfectly inelastic), then
// the heavier combined mass oscillates about a lower equilibrium. The collision
// loses kinetic energy — so `energygraph`'s total STEPS DOWN at impact. Then it
// is conserved again. PRE-SIMULATED with RK4; `run(id,[dur])` replays it.
//
// TO ADAPT: raise dropheight for a faster impact and a bigger energy step.
// ============================================================================
title("Drop mass — an inelastic collision loses energy");
canvas("16:9");
text(hdr, (cx, 44), "Dropped mass sticks — energy is lost in the inelastic collision");
size(hdr, 22); color(hdr, cyan); bold(hdr); display(hdr);
dropmass(dm, (440, 150), 1.2);
energygraph(dm, (960, 330), 120);
text(cap, (cx, h - 32), "watch the gold total energy line step DOWN the instant the magenta mass sticks to the block");
size(cap, 15); color(cap, dim); display(cap);
run(dm, 7);
raft-cm
A person walks back and forth on a floating raft; with no external force the centre of
mass stays fixed, so the raft glides the opposite way — momentum conservation you can
see (raft).
// ============================================================================
// raft-cm.manic — centre of mass, on the physics baseline (Layer 1)
// ----------------------------------------------------------------------------
// `raft(id, [center], [personmass], [raftmass], [unit])` — a person walking on
// a floating raft. With no external horizontal force, momentum is conserved and
// the centre of mass stays FIXED: the raft slides the opposite way by
// −m_person/(m_person+m_raft) of each step. PRE-SIMULATED; `run(id,[dur])`
// replays it. The dashed line marks the (unmoving) centre of mass.
//
// TO ADAPT: make the raft much lighter (raftmass) — it slides much further.
// ============================================================================
title("Raft — walk one way, the raft slides the other");
canvas("16:9");
text(hdr, (cx, 46), "Momentum stays zero, so the centre of mass never moves");
size(hdr, 23); color(hdr, cyan); bold(hdr); display(hdr);
raft(rf, (cx, 380), 70, 200);
text(cap, (cx, h - 32), "the person walks; the raft glides back — the dashed centre-of-mass line stays put");
size(cap, 16); color(cap, dim); display(cap);
run(rf, 9);
brachistochrone
Four beads race under gravity from A to B down a straight line, a circular arc, a
parabola, and a cycloid. The cycloid — the curve of fastest descent — wins, even
though it dips lower and travels farther (brachistochrone).
// ============================================================================
// brachistochrone.manic — the curve of fastest descent (physics Layer 1)
// ----------------------------------------------------------------------------
// `brachistochrone(id, [center], [unit])` — four beads race under gravity from
// A down to B along four curves: a straight line, a circular arc, a parabola,
// and a CYCLOID. Each is a full RK4 bead-on-wire integration. The cycloid — the
// brachistochrone — wins, even though it dips below and travels farther. The
// steep early drop buys speed that more than pays back the extra distance.
// `run(id,[dur])` replays the race.
//
// TO ADAPT: raise `dur` to watch the finish order in slow motion.
// ============================================================================
title("Brachistochrone — the fastest slide is a cycloid");
canvas("16:9");
text(hdr, (cx, 44), "Which curve is fastest from A to B? Steepness early beats a short path");
size(hdr, 21); color(hdr, cyan); bold(hdr); display(hdr);
brachistochrone(br, (330, 150), 150);
text(leg, (cx, h - 58), "dim: straight cyan: arc gold: parabola magenta: CYCLOID (winner)");
size(leg, 16); color(leg, fg); display(leg);
text(cap, (cx, h - 30), "same start, same finish — the magenta cycloid bead reaches B first");
size(cap, 16); color(cap, dim); display(cap);
run(br, 5);
brachistochrone-annotated
The elevation recipe on a RACE, told with KINETIC TYPOGRAPHY: wordpop pops the
question in, karaoke sweeps a highlight across the four path names as the curves
sketch on, then flash/glow crown the cycloid — a third, distinct elevation style.
// ============================================================================
// brachistochrone-annotated.manic — ELEVATE a sim: KINETIC TYPOGRAPHY
// ----------------------------------------------------------------------------
// A third elevation flavour (see spring-annotated for typewriter, pulley-
// annotated for camera). Here the text itself performs: `caption` + `wordpop`
// pops the question in word-by-word, `caption` + `karaoke` sweeps a highlight
// across the four path names as the curves are sketched, and `flash`/`glow`/
// `pulse` crown the winner — the same recipe on a multi-body RACE, no section
// cards. All base manic over the `brachistochrone` parts.
// ============================================================================
title("Brachistochrone — Shortest Path, or Fastest?");
canvas("16:9");
// ---- the sim (curves untraced to sketch on; beads + markers hidden) ----
brachistochrone(br, (330, 165), 150);
untraced(br.straight); untraced(br.circle); untraced(br.parabola); untraced(br.cycloid);
hidden(br.bead_straight); hidden(br.bead_circle); hidden(br.bead_parabola); hidden(br.bead_cycloid);
hidden(br.markA); hidden(br.labelA); hidden(br.markB); hidden(br.labelB);
// ---- kinetic-typography captions ----
caption(q, "shortest path — or fastest?", (cx, 62), 34, gold);
caption(leg, "straight arc parabola cycloid", (cx, h - 66), 26, dim);
text(cap, (cx, h - 32), ""); size(cap, 19); color(cap, dim); display(cap);
// ============================== SCRIPT ==============================
say(cap, "a bead slides from A down to B under gravity — same start, same finish", 0.4);
show(br.markA, 0.4); show(br.labelA, 0.3); pulse(br.markA);
show(br.markB, 0.4); show(br.labelB, 0.3); pulse(br.markB);
wait(0.4);
// the question pops in, word by word
wordpop(q, 0.18);
wait(0.6);
// sketch the four curves; the legend highlight sweeps across their names
say(cap, "four paths from A to B — the cycloid dips below the line", 0.4);
draw(br.straight, 0.5); draw(br.circle, 0.5); draw(br.parabola, 0.5); draw(br.cycloid, 0.6);
karaoke(leg, 0.5, cyan);
flash(br.cycloid, magenta);
wait(0.5);
// the race
say(cap, "release all four at once …", 0.4);
show(br.bead_straight, 0.3); show(br.bead_circle, 0.3); show(br.bead_parabola, 0.3); show(br.bead_cycloid, 0.3);
run(br, 6);
// crown the winner
say(cap, "the CYCLOID wins — the steep early drop buys speed that repays the longer path", 0.4);
flash(br.cycloid, lime); glow(br.bead_cycloid, 1.9); pulse(br.bead_cycloid);
wait(1.0);
gradient-fastest-descent
Bernoulli 1696, told THROUGH gradients: four wires race from A to B, each wearing a
3-stop vertical speedometer (v = √(2gΔh) — depth IS speed), then the cycloid’s secret
is revealed by a "curvature" gradient — it bends hardest at the start. A 16:9 3B1B-
style story; companion Short is gradient-fastest-descent-shorts.
// ============================================================================
// gradient-fastest-descent.manic — a 3B1B-style 16:9 story told THROUGH
// gradients (companion: gradient-fastest-descent-shorts.manic)
// ----------------------------------------------------------------------------
// THE PROBLEM (Bernoulli, 1696): a bead slides from A down to B under gravity.
// Which wire shape gets it there fastest? Newton, Leibniz and the Bernoullis
// all answered: the CYCLOID — not the straight line.
//
// THE GRADIENTS DO THE PHYSICS:
// · every ramp wears a 3-stop vertical gradient — for a frictionless bead
// v = √(2gΔh), so DEPTH IS SPEED and the color bar is a real speedometer
// · the winner's secret is revealed by a "curvature" gradient — the cycloid
// bends hardest at the very start, a near-vertical dive that buys speed
// immediately
// · a radial well sinks the stage behind the race
// Every curve, bead and color is computed (RK4 bead-on-wire), not drawn.
// ============================================================================
title("The Fastest Descent");
canvas("16:9");
template("blank");
// ---- backdrop: a radial gravity well behind the race ----
circle(stage, (cx, 380), 300);
filled(stage);
gradient(stage, panel, void, radial);
opacity(stage, 0.4);
// ---- the race: four wires from A to B, beads pre-integrated with RK4 ----
brachistochrone(race, (300, 170), 155);
untraced(race.straight); untraced(race.circle); untraced(race.parabola); untraced(race.cycloid);
hidden(race.bead_straight); hidden(race.bead_circle); hidden(race.bead_parabola); hidden(race.bead_cycloid);
hidden(race.markA); hidden(race.labelA); hidden(race.markB); hidden(race.labelB);
stroke(race.straight, 4); stroke(race.circle, 4); stroke(race.parabola, 4); stroke(race.cycloid, 5);
opacity(race.straight, 0.95); opacity(race.circle, 0.95); opacity(race.parabola, 0.95); opacity(race.cycloid, 1);
// depth IS speed: v = √(2gΔh), so a vertical 3-stop gradient over each wire's
// own bounds is its true speedometer — blue at the top (all potential),
// gold at the bottom (all kinetic). 270 puts the first stop at the bottom.
gradient(race.straight, gold, cyan, blue, 270);
gradient(race.circle, gold, cyan, blue, 270);
gradient(race.parabola, gold, cyan, blue, 270);
gradient(race.cycloid, gold, cyan, blue, 270);
// a second copy of the same sim: only its cycloid is used, re-dressed with a
// CURVATURE gradient for the reveal — same computed curve, different truth
brachistochrone(ghost, (300, 170), 155);
hidden(ghost.straight); hidden(ghost.circle); hidden(ghost.parabola);
hidden(ghost.bead_straight); hidden(ghost.bead_circle); hidden(ghost.bead_parabola); hidden(ghost.bead_cycloid);
hidden(ghost.markA); hidden(ghost.labelA); hidden(ghost.markB); hidden(ghost.labelB);
hidden(ghost.cycloid); untraced(ghost.cycloid);
stroke(ghost.cycloid, 6);
gradient(ghost.cycloid, dim, magenta, "curvature");
glow(ghost.cycloid, 0.6);
// ---- typography ----
caption(q, "shortest path — or fastest?", (cx, 56), 34, gold);
equation(law, (1020, 160), `v=\sqrt{2g\,\Delta h}`, 34);
color(law, cyan); hidden(law);
text(cap, (cx, h - 38), ""); size(cap, 20); color(cap, dim); display(cap);
// ============================== SCRIPT ==============================
// 1 — THE PROBLEM
say(cap, "Bernoulli, 1696 — a bead slides from A down to B under gravity alone", 0.4);
show(race.markA, 0.4); show(race.labelA, 0.3); pulse(race.markA);
show(race.markB, 0.4); show(race.labelB, 0.3); pulse(race.markB);
wait(0.3);
wordpop(q, 0.18);
wait(0.5);
// 2 — THE CANDIDATES, each wearing its speedometer
say(cap, "four wires — colored by depth, and depth IS speed", 0.4);
stagger(0.15) {
draw(race.straight, 0.7);
draw(race.circle, 0.7);
draw(race.parabola, 0.7);
draw(race.cycloid, 0.9);
}
wait(0.3);
show(law, 0.5);
say(cap, "blue = all potential energy · gold = all of it cashed into speed", 0.4);
wait(1.0);
// 3 — THE RACE (real RK4 bead-on-wire integrations)
say(cap, "release all four at once …", 0.4);
par {
show(race.bead_straight, 0.25);
show(race.bead_circle, 0.25);
show(race.bead_parabola, 0.25);
show(race.bead_cycloid, 0.25);
}
run(race, 6);
// 4 — THE WINNER
say(cap, "the CYCLOID wins — the straight line isn't even close", 0.4);
flash(race.cycloid, magenta);
glow(race.bead_cycloid, 1.9);
pulse(race.bead_cycloid);
wait(0.8);
// 5 — THE SECRET, told by a curvature gradient
say(cap, "its secret, colored by curvature: it bends hardest at the very start", 0.4);
par {
fade(race.straight, 0.5);
fade(race.circle, 0.5);
fade(race.parabola, 0.5);
fade(race.cycloid, 0.5);
fade(race.bead_straight, 0.4);
fade(race.bead_circle, 0.4);
fade(race.bead_parabola, 0.4);
fade(race.bead_cycloid, 0.4);
}
show(ghost.cycloid, 0.2);
draw(ghost.cycloid, 1.4);
wait(0.4);
say(cap, "a near-vertical dive buys gold-speed immediately — then spends it on distance", 0.4);
flash(ghost.cycloid, magenta);
wait(1.0);
// 6 — THE POINT
recolor(cap, cyan);
say(cap, "every color here is computed — depth is the speedometer, the bend is the strategy", 0.4);
wait(1.6);
textbook-pulley
A physics-TEXTBOOK figure, manic style: the template("paper") white page, a hatched
support ceiling, a green pulley wheel, and outlined labelled mass boxes — the classic
m over 2m+3m arrangement, all base primitives.
// ============================================================================
// textbook-pulley.manic — a physics-textbook figure, manic style
// ----------------------------------------------------------------------------
// The classic "m over 2m+3m" pulley arrangement, drawn in the flat textbook
// look: the `paper` theme (white page, dark ink), a hatched `support` ceiling,
// a solid green pulley wheel, thin ropes, and outlined labelled mass boxes.
// A STATIC figure — pure base manic (`support`, `circle`, `line`, `rect`, `text`).
// ============================================================================
title("Pulley arrangement — m, 2m, 3m");
canvas("16:9");
template("paper");
// hatched ceiling + the rope suspending the pulley
support(ceil, (cx, 120), 340);
line(rope0, (cx, 120), (cx, 222)); color(rope0, fg); stroke(rope0, 2);
// the green pulley wheel (dark edge + hub)
circle(wheel, (cx, 300), 80); color(wheel, lime); outline(wheel, fg);
circle(hub, (cx, 300), 5); color(hub, fg); filled(hub);
// ropes leave the wheel vertically, left and right
line(ropeL, (560, 300), (560, 456)); color(ropeL, fg); stroke(ropeL, 2);
line(ropeR, (720, 300), (720, 456)); color(ropeR, fg); stroke(ropeR, 2);
// mass on the left: m
rect(mbox, (560, 485), 110, 58); outlined(mbox); outline(mbox, fg); stroke(mbox, 2);
text(ml, (560, 485), "m"); color(ml, fg); size(ml, 26); bold(ml);
// masses on the right: 2m, with 3m hanging below it
rect(m2, (720, 485), 120, 58); outlined(m2); outline(m2, fg); stroke(m2, 2);
text(l2, (720, 485), "2m"); color(l2, fg); size(l2, 26); bold(l2);
line(r23, (720, 514), (720, 596)); color(r23, fg); stroke(r23, 2);
rect(m3, (720, 625), 120, 58); outlined(m3); outline(m3, fg); stroke(m3, 2);
text(l3, (720, 625), "3m"); color(l3, fg); size(l3, 26); bold(l3);
textbook-tension
Another textbook figure: two support ropes at 60°/30° meeting a knot, a string over a
hanging pulley carrying 10 kg with the other end anchored to a hatched floor — support
template("paper")for the flat exam-paper look.
// ============================================================================
// textbook-tension.manic — a physics-textbook figure, manic style
// ----------------------------------------------------------------------------
// Two support ropes at 60° / 30° meet at a knot; a string over a hanging pulley
// carries a 10 kg load on one side and anchors to the floor on the other. Drawn
// in the flat textbook look: `paper` theme, hatched `support` ceiling + floor,
// a green pulley wheel, and outlined labels. A STATIC figure, all base manic.
// ============================================================================
title("Tension in a rope over a pulley (g = 10 m/s²)");
canvas("16:9");
template("paper");
// hatched ceiling
support(ceil, (cx, 110), 700);
// the two support ropes meeting at the knot, with a dashed-style vertical ref
line(t1, (553, 110), (cx, 260)); color(t1, fg); stroke(t1, 2);
line(t2, (900, 110), (cx, 260)); color(t2, fg); stroke(t2, 2);
line(vref, (cx, 110), (cx, 260)); color(vref, dim); stroke(vref, 1);
text(a1, (588, 150), "60°"); color(a1, fg); size(a1, 22);
text(a2, (852, 150), "30°"); color(a2, fg); size(a2, 22);
text(t1l, (556, 210), "T₁"); color(t1l, fg); size(t1l, 24); bold(t1l);
text(t2l, (820, 210), "T₂"); color(t2l, fg); size(t2l, 24); bold(t2l);
// knot → hanging pulley
line(drop, (cx, 260), (cx, 364)); color(drop, fg); stroke(drop, 2);
circle(wheel, (cx, 430), 66); color(wheel, lime); outline(wheel, fg);
circle(hub, (cx, 430), 5); color(hub, fg); filled(hub);
// 10 kg load on the right side of the string
line(ropeM, (706, 430), (706, 520)); color(ropeM, fg); stroke(ropeM, 2);
rect(mbox, (706, 548), 84, 56); outlined(mbox); outline(mbox, fg); stroke(mbox, 2);
text(ml, (792, 548), "10 Kg"); color(ml, fg); size(ml, 22); bold(ml);
// the other side anchored to a hatched floor
line(ropeF, (cx, 496), (cx, 600)); color(ropeF, fg); stroke(ropeF, 2);
support(floor, (cx, 612), 150, "up");
spinning-cylinder-flow
Potential flow around a spinning cylinder — WHY a spun ball curves. Parallel streamlines
(uniform flow) and concentric circles (a free vortex) superpose into the asymmetric flow
around the cylinder: bunched (fast, low-pressure) one side, spread (slow, high-pressure) the
other → a net Magnus force, L=ρUΓ. Every streamline is integrated straight from the
velocity field with trajectory — the asymmetry is computed, not drawn.
// Streamlines of potential flow around a spinning cylinder — the superposition
// that explains the Magnus effect (why a spun ball curves). Uniform flow (parallel
// streamlines) + a free vortex (concentric streamlines) superpose into the
// asymmetric flow around a rotating cylinder: bunched (fast, low-pressure) on one
// side, spread (slow, high-pressure) on the other → a net Magnus force. The combined
// streamlines are integrated straight from the velocity field with `trajectory`.
title("Why a Spinning Ball Curves");
canvas("16:9");
template("neon");
let ox = cx;
let oy = cy + 20;
let sc = 82; // px per field unit
let a = 98; // cylinder radius (px)
// --- stage 1: uniform flow = parallel streamlines ---
for k in 0..11 {
line(uni{k}, (ox-430, oy + (k-5)*44), (ox+430, oy + (k-5)*44));
color(uni{k}, cyan); untraced(uni{k}); tag(uni{k}, uniform);
}
// --- stage 2: a free vortex = concentric circular streamlines ---
for k in 0..5 {
circle(vor{k}, (ox, oy), 55 + k*52); color(vor{k}, magenta); untraced(vor{k}); tag(vor{k}, vortex);
}
// --- stage 3: the superposition — streamlines around the spinning cylinder ---
for i in 0..17 {
let y0 = -4.4 + i*0.55;
trajectory(fl{i},
"1 - 1.44*(x*x-y*y)/((x*x+y*y)^2) - 0.7*y/(x*x+y*y)",
"0 - 2.88*x*y/((x*x+y*y)^2) + 0.7*x/(x*x+y*y)",
(-4.6, y0), (ox, oy), sc, 520);
color(fl{i}, cyan); glow(fl{i}, 0.4); untraced(fl{i}); tag(fl{i}, combo);
}
circle(cyl, (ox, oy), a); color(cyl, gold); filled(cyl); hidden(cyl);
circle(cylo, (ox, oy), a); color(cylo, gold); outline(cylo, gold); hidden(cylo);
// spin arrow (clockwise) + labels + net force + Kutta–Joukowski
arrow(spinT, (ox-34, oy-a-8), (ox+34, oy-a-8)); color(spinT, gold); hidden(spinT);
arrow(spinB, (ox+34, oy+a+8), (ox-34, oy+a+8)); color(spinB, gold); hidden(spinB);
arrow(inflow, (ox-430, oy+230), (ox-330, oy+230)); color(inflow, dim); hidden(inflow);
text(inl, (ox-360, oy+205), "U"); size(inl, 22); color(inl, dim); hidden(inl);
arrow(force, (ox+a+70, oy+30), (ox+a+70, oy-90)); color(force, lime); stroke(force, 5); hidden(force);
text(fl_lab, (ox+a+150, oy-30), "Magnus\nforce"); size(fl_lab, 22); color(fl_lab, lime); hidden(fl_lab);
text(fast, (ox, oy-a-70), "streamlines bunch → faster → LOW pressure"); size(fast, 20); color(fast, cyan); wrap(fast, 520); hidden(fast);
text(slow, (ox, oy+a+70), "spread → slower → HIGH pressure"); size(slow, 20); color(slow, magenta); wrap(slow, 520); hidden(slow);
equation(kj, (cx, h-70), `L=\rho\,U\,\Gamma`, 40); color(kj, lime); hidden(kj);
text(cap, (cx, h-30), ""); size(cap, 22); color(cap, dim); wrap(cap, w*0.8); hidden(cap);
show(cap, 0.3);
// 1 — uniform flow
say(cap, "uniform flow — parallel streamlines");
stagger(0.05) { for k in 0..11 { draw(uni{k}, 0.6); } }
wait(0.6);
// 2 — a free vortex
say(cap, "a free vortex — concentric streamlines (the spin's circulation)");
stagger(0.08) { for k in 0..5 { draw(vor{k}, 0.7); } }
wait(0.7);
// 3 — superpose them
say(cap, "superpose them → the flow around a spinning cylinder");
par {
fade(uniform, 0.5); fade(vortex, 0.5);
show(cyl, 0.4); show(cylo, 0.4);
}
stagger(0.04) { for i in 0..17 { draw(fl{i}, 0.9); } }
wait(0.6);
// 4 — the asymmetry → pressure → force
say(cap, "the flow is faster on top, slower below — Bernoulli does the rest");
par {
show(spinT, 0.4); show(spinB, 0.4);
show(inflow, 0.4); show(inl, 0.4);
show(fast, 0.5); show(slow, 0.5);
}
wait(0.5);
par { show(force, 0.6); show(fl_lab, 0.5); show(kj, 0.5); }
wait(0.6);
// 5 — the payoff
say(cap, "a pressure difference pushes it sideways — the Magnus force that curls a free kick");
pulse(force, 0.8);
wait(1.6);
Optics — light as geometry
Easy builtins with the real physics underneath — Snell’s law and Sellmeier dispersion — so the bending and the colours are earned, not painted. Each is static geometry that animates by a parameter sweep: call run(id).
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
refraction
Snell’s law you can watch: a ray crossing from air into glass bends toward the normal,
and run sweeps the incidence angle so the refracted ray swings — the live in/out
read-outs are the true angles. Start in the denser medium and it shows total internal
reflection past the critical angle (refract).
// ============================================================================
// refraction.manic — Snell's law you can watch (real optics)
// ----------------------------------------------------------------------------
// The `optics` kit's first builtin: `refract` draws a light ray meeting the
// boundary between two media and bending. `run` SWEEPS the incidence angle, so
// the refracted ray genuinely swings — and the "in"/"out" read-outs are the
// true Snell angles, not painted. Here: air (n=1.00) into crown glass (n=1.52).
// ============================================================================
title("Refraction — Snell's Law");
canvas("16:9");
// air on top, glass below; no fixed angle → run(r) sweeps it
refract(r, (640, 380), 1.0, 1.52);
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "a ray of light, crossing from air into glass…", 0.5);
wait(0.5);
par {
run(r, 7);
seq {
say(cap, "shallow angle — it barely bends", 0.3);
wait(2.2);
say(cap, "steeper in → it bends MORE, toward the normal", 0.3);
wait(2.6);
say(cap, "sin(in) / sin(out) is fixed — THAT ratio is the refractive index", 0.3);
}
}
wait(1.2);
lens
A converging lens: a parallel beam bends to meet at the focal point F (ideal thin lens —
every ray passes through F). run sweeps the focal length, so the focus slides in as the
lens gets stronger (lens).
// ============================================================================
// lens.manic — a converging lens, and where it focuses (real optics)
// ----------------------------------------------------------------------------
// The `optics` kit's `lens` builtin: a beam of parallel rays is bent by a
// converging lens to meet at the focal point F. `run` SWEEPS the focal length,
// so you watch the focus slide in toward the lens — a shorter focal length is
// a stronger lens. Ideal thin lens (every parallel ray passes through F).
// ============================================================================
title("Converging Lens");
canvas("16:9");
lens(l, (620, 360));
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "parallel light meets a converging lens…", 0.5);
wait(0.5);
par {
run(l, 7);
seq {
say(cap, "every ray bends to the SAME point — the focus, F", 0.3);
wait(2.6);
say(cap, "shorter focal length = a stronger lens → the focus slides IN", 0.3);
wait(2.4);
say(cap, "past F the rays cross and spread apart again", 0.3);
}
}
wait(1.2);
prism
White light into a prism, out as a RAINBOW — each colour traced through both faces with
its own refractive index (real Sellmeier dispersion), so blue bends more than red because
glass genuinely slows blue more. run sweeps the incidence angle and the fan widens (prism).
// ============================================================================
// prism.manic — white light into a rainbow (real dispersion)
// ----------------------------------------------------------------------------
// The `optics` kit's `prism`: white light enters a triangular prism and splits
// into a spectrum. Each colour is traced through both faces with its OWN
// refractive index (real Sellmeier dispersion) — blue bends more than red
// because glass genuinely slows blue more. `run` sweeps the incidence angle,
// so the fan swings and its spread widens.
// ============================================================================
title("Prism — White Light Splits");
canvas("16:9");
prism(p, (560, 400), "sf11"); // dense flint — a wide, vivid spectrum
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "one beam of white light, into a prism…", 0.5);
wait(0.5);
par {
run(p, 7);
seq {
say(cap, "it comes out as a RAINBOW — the colours were always there", 0.3);
wait(2.6);
say(cap, "blue bends more than red: glass really does slow blue more", 0.3);
wait(2.4);
say(cap, "steeper in → the spectrum fans WIDER (real Sellmeier dispersion)", 0.3);
}
}
wait(1.2);
achromat
The optics capstone — chromatic aberration and its fix: a single lens focuses blue nearer
than red, so white light never comes to one point; run sweeps in the achromatic doublet
and the colours snap back to a single sharp focus (achromat).
// ============================================================================
// achromat.manic — chromatic aberration, and the fix (real optics)
// ----------------------------------------------------------------------------
// The optics kit's capstone. A single lens can't focus all colours to one
// point: it bends blue more than red (its index is higher for blue — real
// dispersion), so blue focuses NEARER and red FARTHER. `run` then sweeps in the
// achromatic doublet (crown + flint) and the two foci slide back together to a
// single sharp point — the trick every camera and telescope lens uses.
// ============================================================================
title("The Achromatic Doublet");
canvas("16:9");
achromat(ac, (540, 360));
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "one simple lens — but the colours don't agree…", 0.5);
wait(0.6);
par {
run(ac, 7);
seq {
say(cap, "blue focuses NEARER, red FARTHER — chromatic aberration", 0.3);
wait(2.6);
say(cap, "now add a flint element behind it — the achromatic doublet", 0.3);
wait(2.2);
say(cap, "…and the colours snap back to ONE sharp focus", 0.3);
}
}
wait(1.2);
refraction-paper
Snell’s law as a template("paper") TEXTBOOK figure: inked media labels, the normal, and
the law itself, with a camera easing in on the bending point as run sweeps the angle —
the geometric builtins suit paper (refract + annotation + camera).
// ============================================================================
// refraction-paper.manic — Snell's law as a textbook figure (annotated)
// ----------------------------------------------------------------------------
// The `refract` sim dressed as a physics-class page: `template("paper")` inks
// it on cream, labelled media + normal + the law itself, and a gentle camera
// push on the bending point while `run` sweeps the incidence angle. Elevation
// lens: textbook annotation + camera. (Paper suits the geometry; the coloured
// dispersion demos stay on a dark bench where light glows.)
// ============================================================================
title("Refraction — Snell's Law");
canvas("16:9");
template("paper");
refract(r, (660, 400), 1.0, 1.52);
color(r.incident, red); // gold washes out on cream — ink the incoming ray red
color(r.thetai, red);
// ---- textbook annotations (screen-fixed) ----
text(mAir, (150, 300), "air n = 1.00"); color(mAir, dim); size(mAir, 22); sticky(mAir);
text(mGla, (150, 500), "glass n = 1.52"); color(mGla, dim); size(mGla, 22); sticky(mGla);
text(nlab, (708, 250), "normal"); color(nlab, dim); size(nlab, 18); sticky(nlab);
text(law, (cx, 92), "n1 · sin(in) = n2 · sin(out)"); color(law, fg); size(law, 30); bold(law); sticky(law);
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "light bends as it crosses into glass — Snell's law", 0.5);
wait(0.5);
par {
run(r, 8);
seq {
par { cam((660, 400), 2.2, smooth); zoom(1.18, 2.2, smooth); } // ease in on the boundary
say(cap, "steeper in → it bends more, toward the normal", 0.3);
wait(3.0);
say(cap, "…but sin(in) / sin(out) never changes — that ratio IS n", 0.3);
}
}
wait(1.2);
lens-paper
The converging lens inked on paper and narrated by a TYPEWRITER caption (a different
elevation lens): labelled parallel rays and focal point F while run slides the focus
(lens + type).
// ============================================================================
// lens-paper.manic — a converging lens as a textbook figure (annotated)
// ----------------------------------------------------------------------------
// The `lens` sim inked on paper, narrated by a TYPEWRITER caption (a different
// elevation lens from refraction-paper's camera), with labelled parallel rays
// and focal point while `run` sweeps the focal length. Ideal thin lens.
// ============================================================================
title("Converging Lens");
canvas("16:9");
template("paper");
lens(l, (640, 360));
// ---- textbook annotations (screen-fixed) ----
text(inlab, (150, 250), "parallel rays"); color(inlab, dim); size(inlab, 22); sticky(inlab);
text(flab, (980, 300), "focal point F"); color(flab, dim); size(flab, 22); sticky(flab);
arrow(farr, (980, 320), (900, 356)); color(farr, dim); sticky(farr);
// a typewriter caption performs the narration (untraced → type reveals it)
text(cap, (cx, h - 48), "a lens bends parallel light to a single point"); color(cap, fg); size(cap, 26); bold(cap); untraced(cap); sticky(cap);
// ================= THE SCENE =================
type(cap, 1.6); // type the line out
wait(0.4);
par {
run(l, 8);
seq {
wait(0.6);
say(cap, "every ray meets at the focus, F", 0.3);
wait(2.8);
say(cap, "a shorter focal length is a stronger lens — F slides in", 0.3);
}
}
wait(1.2);
prism-cinematic
The prism on a dark optics bench where the spectrum GLOWS (a rainbow washes out on paper):
the colour names pop in word-by-word (wordpop) as the fan spreads and the camera flies
toward it — KINETIC-TYPE elevation (prism).
// ============================================================================
// prism-cinematic.manic — white light into a rainbow (annotated, dark bench)
// ----------------------------------------------------------------------------
// The `prism` sim on a dark optics bench, where the spectrum GLOWS (a rainbow
// washes out on paper — light is a dark-background subject). Elevation lens:
// KINETIC TYPE — the colour names pop in word-by-word (`wordpop`) as the fan
// spreads, plus a camera push toward the exit spectrum.
// ============================================================================
title("Prism — White Light Splits");
canvas("16:9");
prism(p, (560, 400), "sf11");
// the colour names, popped in one at a time over the fan
caption(names, "red orange yellow green blue violet", (cx + 40, 150), 26, gold);
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "one beam of white light, into a prism…", 0.5);
wait(0.5);
par {
run(p, 8);
seq {
par { cam((820, 430), 2.6, smooth); zoom(1.22, 2.6, smooth); } // fly toward the spectrum
say(cap, "…out as a rainbow — the colours were always hidden inside", 0.3);
wordpop(names, 0.22); // the colours pop in
wait(1.6);
say(cap, "blue bends most, red least — real Sellmeier dispersion", 0.3);
wait(1.6);
}
}
par { cam((cx, 400), 1.2, smooth); zoom(1.0, 1.2, smooth); }
wait(1.0);
achromat-cinematic
The achromat with the CAMERA magnifying the focal region so the red/blue split is dramatic,
a bracelabel marking the aberration gap that closes as run sweeps in the doublet and
the colours merge (achromat + camera + brace).
// ============================================================================
// achromat-cinematic.manic — chromatic aberration & its fix (annotated, dark)
// ----------------------------------------------------------------------------
// The `achromat` capstone on a dark bench, where the coloured foci glow.
// Elevation lens: the CAMERA magnifies the focal region so the red/blue split
// is dramatic, and a `bracelabel` marks the aberration gap — which then closes
// as `run` sweeps in the doublet and the colours merge to one point.
// ============================================================================
title("The Achromatic Doublet");
canvas("16:9");
achromat(ac, (540, 360));
// bracket the axial gap between the two foci (fades as they merge)
bracelabel(gap, (852, 406), (908, 406), "CA", 20); color(gap, red);
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "one simple lens — but the colours refuse to agree…", 0.5);
wait(0.6);
par {
run(ac, 8);
seq {
par { cam((880, 360), 2.4, smooth); zoom(1.7, 2.4, smooth); } // magnify the focal region
say(cap, "blue focuses NEARER, red FARTHER — chromatic aberration", 0.3);
wait(2.8);
say(cap, "now cement a flint element behind it — the achromat", 0.3);
fade(gap, 0.7); // the gap closes
wait(2.0);
say(cap, "…and the colours snap back to ONE sharp focus", 0.3);
}
}
par { cam((cx, 360), 1.3, smooth); zoom(1.0, 1.3, smooth); }
wait(1.2);
lens-system
A REAL multi-element lens, ray-traced through its actual spherical surfaces (not the ideal
thin lens): the fast singlet reveals SPHERICAL ABERRATION — draw sketches the rays, then
run sweeps a sensor plane and the live spot read-out dips but never reaches a point,
because the outer rays focus short (lenssystem, presets singlet/doublet/triplet).
// ============================================================================
// lens-system.manic — a REAL lens, ray-traced (best of physics + manic)
// ----------------------------------------------------------------------------
// `lenssystem` traces a parallel beam through the ACTUAL spherical surfaces of
// a lens (not the ideal thin lens of `lens`). Here the fast singlet reveals
// SPHERICAL ABERRATION: sketch the rays on with `draw`, then `run` sweeps a
// sensor plane along the axis while the live spot read-out dips to its minimum
// — which never reaches a point, because the outer rays focus short. Then swap
// the caption's promise: that's the flaw a doublet/triplet is built to fix.
// ============================================================================
title("Spherical Aberration — a Real Lens Traced");
canvas("16:9");
lenssystem(ls, (620, 380), "singlet");
text(cap, (cx, h - 42), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "a parallel beam into a real, fast single lens…", 0.5);
draw(ls.rays, 2.2); // the rays sketch through the glass
wait(0.3);
par {
run(ls, 6); // the sensor plane sweeps the axis
seq {
say(cap, "a sensor hunts for focus — watch the spot size", 0.3);
wait(2.4);
say(cap, "the outer rays focus SHORT — it never reaches a point", 0.3);
wait(2.0);
say(cap, "that blur is spherical aberration (a doublet/triplet fixes it)", 0.3);
}
}
wait(1.2);
ray-fan
Reading an aberration: rayfan plots each ray’s error at focus against where it entered the
lens. A flat line is a perfect lens — the singlet’s cubic S-CURVE is textbook spherical
aberration (the edges bend too much), which a doublet flattens (rayfan).
// ============================================================================
// ray-fan.manic — the signature of an aberration (real optics analysis)
// ----------------------------------------------------------------------------
// A ray-fan plot: for each ray, how far off focus does it land (y) against
// where it entered the lens (x, edge → edge)? `rayfan` traces the real
// spherical surfaces. A perfect lens is a flat line at zero; the singlet's
// cubic S-CURVE is textbook spherical aberration — the outer rays bend too
// much. (The doublet/triplet flatten it, drawn to the same scale.)
// ============================================================================
title("Ray-Fan — Reading an Aberration");
canvas("16:9");
rayfan(rf, (640, 340), "singlet");
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "each ray: how far off focus (y) vs where it entered (x)…", 0.5);
draw(rf.curve, 2.2);
wait(0.5);
say(cap, "a flat line would be a perfect lens — this ISN'T flat", 0.3);
wait(2.0);
say(cap, "that S-curve is spherical aberration: the edges bend too much", 0.3);
wait(1.4);
spot-diagram
Lens quality as a picture: spotdiagram plots where a ray bundle actually lands at focus.
A fast single lens smears into a blur disc (the circle of least confusion), while a cemented
doublet collapses to a point — both to the same scale, RMS 4 px → under 1 px (spotdiagram).
// ============================================================================
// spot-diagram.manic — a blur vs a point (lens quality, real optics)
// ----------------------------------------------------------------------------
// Where does a bundle of rays actually LAND at focus? `spotdiagram` traces the
// real spherical surfaces and plots the hits. A fast single lens smears into a
// disc — the circle of least confusion from SPHERICAL ABERRATION — while a
// cemented doublet collapses to a point. The green dot is the ideal (point)
// focus; the RMS read-out is the blur radius. Both drawn to the SAME scale.
// ============================================================================
title("Spot Diagram — Blur vs Point");
canvas("16:9");
spotdiagram(bad, (370, 380), "singlet");
spotdiagram(good, (910, 380), "doublet");
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "where do the rays actually land at focus?", 0.5);
wait(0.4);
say(cap, "a fast SINGLE lens → a blur disc (spherical aberration)", 0.3);
draw(bad.dots, 1.8);
wait(0.6);
say(cap, "a cemented DOUBLET → very nearly a point", 0.3);
draw(good.dots, 1.4);
wait(0.6);
say(cap, "same rays, same scale — RMS 4 px collapses to under 1 px", 0.3);
wait(1.2);
lens-prescription
Type your OWN lens: lenssystem takes a design by name ("plano-convex", "doublet", …)
OR a custom PRESCRIPTION — the designer’s surface table "radius thickness glass [conic] [aperture] | …" — traced through the true surfaces with real Sellmeier glass (lenssystem).
// ============================================================================
// lens-prescription.manic — type your OWN lens (real prescription)
// ----------------------------------------------------------------------------
// `lenssystem` takes a lens two ways: pick a real design by NAME
// ("plano-convex" / "meniscus" / "doublet" / "triplet" / …), or write a CUSTOM
// prescription — the lens designer's surface table — as "radius thickness glass"
// per surface, separated by "|". Here: a cemented crown+flint achromat, traced
// through its actual spherical surfaces with real glass (Sellmeier).
// ============================================================================
title("A Lens, By Prescription");
canvas("16:9");
// surface table: radius thickness glass (| between surfaces; "flat"/"air" allowed)
lenssystem(l, (600, 380), "160 26 bk7 | -140 8 f2 | -420 0 air");
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "a lens written as a prescription: radius · thickness · glass", 0.5);
draw(l.rays, 2.2); // trace the real surfaces
wait(0.4);
par {
run(l, 6); // sweep the sensor to best focus
seq {
say(cap, "crown + flint, cemented — the same table a designer would type", 0.3);
wait(2.6);
say(cap, "traced through the true spherical surfaces (real Sellmeier glass)", 0.3);
wait(2.0);
}
}
wait(1.0);
aspheric-lens
How an ASPHERE kills spherical aberration: a spherical surface can’t focus a wide beam to a
point (a blur, RMS 1.5 px), but reshaping it to the right conic — one constant in the
prescription — collapses every ray to a point (RMS 0.1 px). Two real ray-traced lenses,
spherical vs aspheric, side by side (lenssystem conic + spotdiagram).
// ============================================================================
// aspheric-lens.manic — how an asphere kills spherical aberration
// ----------------------------------------------------------------------------
// A prescription surface has a CONIC constant. A plain spherical lens can't
// focus a wide beam to a point — the edges bend too much (spherical aberration,
// a blur). Reshape that surface into the right ellipse/hyperbola (a conic ≠ 0)
// and every ray meets at ONE point. Two real ray-traced lenses, side by side:
// same shape, one spherical, one aspheric.
// ============================================================================
title("Aspheric Correction");
canvas("16:9");
spotdiagram(sph, (360, 380), "plano-convex"); // spherical surface → a blur
spotdiagram(asp, (912, 380), "aspheric"); // conic surface (K≈−0.55) → a point
text(cap, (cx, h - 46), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "same lens shape — where do the rays land at focus?", 0.5);
wait(0.4);
say(cap, "a SPHERICAL surface can't focus the edges — a blur (RMS 1.5 px)", 0.3);
draw(sph.dots, 1.6);
wait(0.6);
say(cap, "reshape it to an ASPHERE (conic surface) — a point (RMS 0.1 px)", 0.3);
draw(asp.dots, 1.4);
wait(0.6);
say(cap, "one conic constant in the prescription — spherical aberration, gone", 0.3);
wait(1.2);
off-axis
The hard test — light 8° OFF the axis. fieldspot traces a full 2-D pupil in 3-D: a single
lens flares into a COMA comet, while a doublet holds the spot near the Airy disk (the
diffraction limit). Real field aberration only a 3-D trace shows (fieldspot).
// ============================================================================
// off-axis.manic — coma & astigmatism, off the axis (3-D ray-traced)
// ----------------------------------------------------------------------------
// On the axis a lens can be sharp — but light coming in at an ANGLE is a harder
// test. `fieldspot` traces a full 2-D pupil in 3-D at a field angle and plots
// where the rays land. A single lens flares into a COMA comet; a doublet holds
// the spot near the Airy disk (the diffraction limit). Same 8° field, side by
// side — real off-axis aberration only a 3-D trace reveals.
// ============================================================================
title("Off-Axis — Coma vs a Corrected Lens");
canvas("16:9");
fieldspot(bad, (350, 380), "singlet", 8); // one element → a coma comet
fieldspot(good, (930, 380), "doublet", 8); // doublet → tight, near diffraction
text(cap, (cx, h - 44), ""); color(cap, fg); size(cap, 24); bold(cap); display(cap); sticky(cap);
// ================= THE SCENE =================
say(cap, "now aim the light 8° OFF the axis — the hard test", 0.5);
wait(0.4);
say(cap, "a single lens smears into a COMA comet (RMS ~7 px)", 0.3);
draw(bad.dots, 1.6);
wait(0.6);
say(cap, "the doublet holds it near the Airy disk — the diffraction limit", 0.3);
draw(good.dots, 1.4);
wait(0.6);
say(cap, "the circle is Airy: shrink the blur to it and you're diffraction-limited", 0.3);
wait(1.2);
Vectors, fields & coordinates
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
vector_field
A magnitude-coloured vector field.
// Vector Field — a grid of arrows sampling a named field, coloured by
// magnitude (cyan → lime → magenta), à la Manim's ArrowVectorField.
//
// manic examples/vector_field.manic
// manic examples/vector_field.manic --record out --fps 60
//
// Named fields: radial, sink, swirl, saddle, wave, shear, uniform, spiral.
title("Vector Field");
canvas(1280, 720);
text(head, (640, 118), ""); display(head); color(head, cyan); size(head, 34); hidden(head);
text(cap, (640, 668), ""); color(cap, dim); size(cap, 22);
// two fields, revealed in turn
arrowfield(swirl, (640, 384), 520, 250, swirl, 15);
untraced(swirl);
arrowfield(rad, (640, 384), 520, 250, radial, 15);
untraced(rad); hidden(rad);
show(head, 0.4);
say(head, "swirl");
say(cap, "a rotational field: (-y, x)");
draw(swirl, 1.2);
wait(1.0);
section("Radial");
say(head, "radial");
say(cap, "an outward source: (x, y) — arrows grow with distance");
par { fade(swirl, 0.5); show(rad, 0.01); }
draw(rad, 1.2);
wait(1.2);
parameterized-field-advection
One parameter smoothly deforms a persistent vector field from steady wind into a vortex.
The generic advect verb then moves real seeded tracers through that same settled field.
// PARAMETERIZED VECTOR FIELD — one value changes the whole field.
// The same settled field then moves a persistent tracer collection. No frame
// callbacks and no manually authored particle paths are involved.
title("From Wind to Vortex");
canvas("16:9");
template("mono");
watermark(mark, (170, 55), "Made With Manic");
text(kicker, (640, 48), "MANIC · PARAMETERIZED FIELD");
text(headline, (640, 94), "One parameter reshapes every arrow");
size(kicker, 18); color(kicker, dim); bold(kicker);
size(headline, 34); bold(headline);
rect(chamber, (640, 365), 780, 410);
outlined(chamber); outline(chamber, dim); stroke(chamber, 3);
// The initial named field supplies a clear first state. `bind` then gives it
// formula components where x,y are field coordinates and p is the parameter.
vectorfield(flow, (640, 365), 375, 190, uniform, 17);
parameter(mix, (640, 610), 0, 0, 1, "vortex mix", 2);
bind(mix, flow, formula, "(1-p)*1.15-p*y", "p*x");
circle(seed, (640, 365), 128);
outlined(seed); outline(seed, dim); dashed(seed, 10, 9);
particles(tracers, seed, 32, 5, 31, "random");
color(tracers, fg); glow(tracers, 0.55);
text(caption, (640, 678), "p = 0: steady wind");
size(caption, 20); color(caption, dim);
untraced(flow); hidden(seed); hidden(tracers);
step("reveal-the-field") {
par {
draw(flow, 0.9);
show(seed, 0.4);
show(tracers, 0.55);
}
}
wait(0.5);
step("wind-becomes-vortex") {
par {
to(mix, value, 1, 2.5, smooth);
say(caption, "p = 1: the same field turns into a vortex", 0.4);
}
}
wait(0.55);
step("release-tracers") {
par {
advect(tracers, flow, 4.4, 0.78);
say(caption, "The dots follow the settled field — their paths were never drawn.", 0.4);
}
}
wait(1.2);
gradient-descent-field
Twelve imperfect guesses begin without the answer. A formula-authored negative-gradient field gives each one local corrections until their different journeys discover one shared minimum—then a creator CTA turns optimization into a complete learning story.
// GRADIENT DESCENT AS A LEARNING STORY
// Twelve guesses begin without the answer. Every arrow supplies one local
// correction, and the same authored field carries all of them to one minimum.
title("How a Wrong Guess Learns");
canvas("9:16");
template("blank");
watermark(mark,(w*0.16,h*0.045),"Made With Manic");
text(kicker,(cx,h*0.075),"GRADIENT DESCENT · LEARNING AS MOTION");
text(headline,(cx,h*0.125),"How does a wrong guess find the minimum?");
text(chapter,(cx,h*0.265),"1 · START ANYWHERE");
text(caption,(cx,h*0.78),"Twelve guesses begin with no map of the whole landscape.");
text(result,(cx,h*0.715),"12 STARTING POINTS → 1 SHARED MINIMUM");
text(cta,(cx,h*0.91),"MAKE LEARNING VISIBLE → 8gwifi.org/manic");
size(kicker,18); color(kicker,dim); bold(kicker); hidden(kicker);
size(headline,32); 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(result,19); color(result,lime); bold(result); hidden(result);
size(cta,21); color(cta,cyan); bold(cta); hidden(cta);
equation(lossRule,(cx,h*0.205),`L(x,y)=\frac{1}{2}x^2+y^2`);
size(lossRule,34); color(lossRule,fg); hidden(lossRule);
let fieldY = h*0.49;
rect(domain,(cx,fieldY),w*0.82,h*0.40);
outlined(domain); outline(domain,dim); stroke(domain,3);
hidden(domain);
// -grad L = (-x,-2y). The gentler vertical coefficient keeps the motion
// readable while preserving the elliptical convergence of the loss bowl.
vectorfield(descent,(cx,fieldY),w*0.37,h*0.175,"-x","-1.6*y",17);
untraced(descent);
circle(starts,(cx,fieldY),w*0.255);
outlined(starts); outline(starts,dim); dashed(starts,12,10);
hidden(starts);
particles(guesses,starts,12,8,73,"ring");
color(guesses,magenta); glow(guesses,0.78);
hidden(guesses);
circle(minimum,(cx,fieldY),14);
filled(minimum); color(minimum,lime); glow(minimum,1.0);
hidden(minimum);
circle(targetRing,(cx,fieldY),44);
outlined(targetRing); outline(targetRing,lime); stroke(targetRing,3);
hidden(targetRing);
text(minLabel,(cx,fieldY+74),"minimum");
size(minLabel,19); color(minLabel,lime); bold(minLabel); hidden(minLabel);
step("begin with imperfect guesses") {
par {
show(kicker,0.30);
show(headline,0.45);
show(lossRule,0.45);
show(chapter,0.35);
show(caption,0.40);
show(domain,0.45);
show(starts,0.45);
show(guesses,0.60);
}
}
wait(0.65);
step("reveal one local clue everywhere") {
par {
draw(descent,1.10);
fade(starts,0.45);
say(chapter,"2 · READ THE LOCAL SLOPE",0.35);
say(caption,"Every arrow gives one instruction: move opposite the local gradient.",0.42);
}
}
wait(0.55);
step("improve one small step at a time") {
par {
advect(guesses,descent,6.80,0.32);
seq {
say(chapter,"3 · FOLLOW THE CORRECTIONS",0.35);
say(caption,"Far away, the correction is large. Near the bottom, each adjustment becomes smaller.",0.42);
wait(3.05);
say(caption,"Different starting points take different journeys—but obey the same rule.",0.42);
}
}
}
wait(0.55);
step("discover the shared destination") {
par {
show(minimum,0.35);
show(targetRing,0.40);
show(minLabel,0.35);
show(result,0.45);
say(chapter,"4 · ARRIVE WITHOUT TELEPORTING",0.35);
say(caption,"Gradient descent is not a jump to the answer. It is repeated, measured improvement.",0.42);
seq {
pulse(targetRing,0.70);
pulse(guesses,0.70);
}
}
}
wait(0.75);
step("create with Manic") {
par {
pulse(minimum,0.75);
show(cta,0.45);
say(caption,"Describe the rule once. Manic keeps every guess, arrow, correction, and learning step connected.",0.45);
}
}
wait(1.45);
coordinates
Axes, planes, number lines, polar & complex planes.
// Coordinate Systems — a guided tour of manic's four coordinate frames:
// Axes (ticks + labels), NumberPlane, PolarPlane, and ComplexPlane. Each frame
// fades in, holds, then clears before the next — one centre, four lenses.
//
// manic examples/coordinates.manic
// manic examples/coordinates.manic --record out --fps 60
title("Coordinate Systems");
canvas(1280, 720);
text(head, (640, 120), "four ways to draw a plane");
display(head); color(head, cyan); size(head, 28); hidden(head);
text(cap, (640, 640), ""); color(cap, dim); size(cap, 24);
// --- the four systems, all centred; each starts hidden ---
axes(ax, (640, 384), 540, 210, 45); // + tick marks and integer labels
plot(wave, (640, 384), 45, 45, sin, 7); // y = sin(x) drawn on the axes
color(wave, magenta); untraced(wave); hidden(ax);
plane(pl, (640, 384), 560, 230, 56); hidden(pl);
polarplane(pp, (640, 384), 230, 5, 16); hidden(pp);
complexplane(cp, (640, 384), 560, 230, 56); hidden(cp);
// --- 1. Axes ---
show(head, 0.5);
section("Axes");
say(cap, "a numbered cross — tick marks every unit");
show(ax, 0.7);
say(cap, "plot y = sin(x) on it");
draw(wave, 1.1);
wait(0.9);
par { fade(ax, 0.4); fade(wave, 0.4); }
// --- 2. NumberPlane ---
section("Number Plane");
say(cap, "a full cartesian grid");
show(pl, 0.7);
wait(1.0);
fade(pl, 0.4);
// --- 3. PolarPlane ---
section("Polar Plane");
say(cap, "concentric rings and radial spokes — angle and radius");
show(pp, 0.7);
wait(1.0);
fade(pp, 0.4);
// --- 4. ComplexPlane ---
section("Complex Plane");
say(cap, "the same grid, read as real and imaginary parts");
show(cp, 0.7);
wait(1.4);
pie
A pie chart built from sectors.
// Equal Slices — a circle cut into equal *sectors* (real filled pieces, not
// just lines) with the math-kit `pie(id, center, r, n)` builtin. Each slice is
// addressable as p0 … p5, so we can trace them on, then pull two out.
//
// manic examples/pie.manic
// manic examples/pie.manic --record out --fps 60
title("Equal Slices");
canvas(1280, 720);
// six equal sectors centred at (560, 400), radius 230 → p0 … p5, tag `p`
pie(p, (560, 400), 230, 6);
untraced(p0); untraced(p1); untraced(p2);
untraced(p3); untraced(p4); untraced(p5);
text(head, (560, 120), "six equal slices");
display(head); color(head, cyan); size(head, 38); hidden(head);
text(cap, (560, 690), ""); color(cap, dim); size(cap, 22);
// --- cut the circle equally, one slice at a time ---
show(head, 0.5);
say(cap, "cut the circle into six equal sectors");
stagger(0.12) {
draw(p0, 0.4);
draw(p1, 0.4);
draw(p2, 0.4);
draw(p3, 0.4);
draw(p4, 0.4);
draw(p5, 0.4);
}
wait(0.6);
// --- each sector is a real piece: pull two out and recolour them ---
say(cap, "each sector is a real piece — pull two out");
par {
move(p0, (621, 435), 0.6, overshoot);
move(p3, (499, 365), 0.6, overshoot);
recolor(p0, magenta, 0.5);
recolor(p3, lime, 0.5);
}
wait(1.0);
Geometry (olympiad)
Every construction is live — the derived points recompute as the inputs move.
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
equilateral
Euclid I.1 — an equilateral triangle from two circles.
// Equilateral Triangle — Euclid, Elements Book I, Proposition 1. Given a segment
// AB: draw the circle centred at A through B and the circle centred at B through
// A; where they cross is the apex of an equilateral triangle. Every side then
// equals AB. It's a fully DYNAMIC construction — drag B at the end and the
// circles, the intersection, and the triangle all stay correct.
//
// New geo builtins: circle2 (circle by centre + a point on it) and circlecircle
// (the two intersection points of two circles).
//
// Note: each compass circle has radius |AB|, so keep A and B close enough that
// the circles fit the canvas (here |AB| ~ 200 px).
//
// manic examples/equilateral.manic
title("Equilateral Triangle");
canvas("16:9");
text(head, (cx, 96), "Euclid I.1 -- two circles give an equilateral triangle");
color(head, cyan); size(head, 24); hidden(head);
point(A, (540, 470), "A");
point(B, (740, 470), "B");
segment(ab, A, B); color(ab, fg); stroke(ab, 3); untraced(ab);
// the two compass circles (each of radius AB)
circle2(cA, A, B); color(cA, dim); stroke(cA, 1.5); untraced(cA);
circle2(cB, B, A); color(cB, dim); stroke(cB, 1.5); untraced(cB);
// where they meet: C0 (below AB) and C1 (above AB) — take the apex above
circlecircle(C, A, B, B, A);
hidden(C0); hidden(C1);
label(C1, "C", (16, -16)); color(C1.label, lime);
segment(ac, A, C1); color(ac, lime); stroke(ac, 3); untraced(ac);
segment(bc, B, C1); color(bc, lime); stroke(bc, 3); untraced(bc);
// --- construct it ---
show(head, 0.5);
show(A, 0.3); show(B, 0.3);
draw(ab, 0.6);
section("Two circles");
par { draw(cA, 0.9); draw(cB, 0.9); }
show(C1, 0.4);
section("The triangle");
par { draw(ac, 0.7); draw(bc, 0.7); }
par { pulse(ac); pulse(bc); pulse(ab); }
wait(0.6);
// --- drag a vertex: it stays equilateral (circles stay on-canvas) ---
section("Drag a vertex");
move(B, (700, 360), 1.6, smooth);
wait(0.3);
move(B, (660, 560), 1.6, smooth);
wait(0.3);
move(B, (740, 470), 1.2, smooth);
wait(1.0);
triangle
A triangle with its centres and cevians.
// Triangle Geometry — the geo kit (olympiad helpers à la olympiad.asy/cse5.asy).
// Points drive everything: circumcircle, incircle, centroid, circumcenter,
// angle mark, and the foot of an altitude are all *constructed* from A, B, C.
//
// manic examples/triangle.manic
// manic examples/triangle.manic --record out --fps 60
title("Triangle Geometry");
canvas(1280, 720);
text(head, (640, 118), "constructed from three points");
display(head); color(head, cyan); size(head, 32); hidden(head);
text(cap, (640, 668), ""); color(cap, dim); size(cap, 22);
// the three free points
point(A, (380, 560), "A");
point(B, (900, 560), "B");
point(C, (640, 190), "C");
hidden(A); hidden(B); hidden(C);
// sides
segment(ab, A, B); segment(bc, B, C); segment(ca, C, A);
untraced(ab); untraced(bc); untraced(ca);
// constructions
circumcircle(cc, A, B, C); untraced(cc);
circumcenter(O, A, B, C); hidden(O);
incircle(ic, A, B, C); untraced(ic);
centroid(G, A, B, C); hidden(G);
anglemark(angC, A, C, B); untraced(angC);
foot(F, C, A, B); hidden(F);
segment(alt, C, F); untraced(alt);
// --- script ---
show(head, 0.5);
say(cap, "three points make a triangle");
par { show(A); show(B); show(C); }
par { draw(ab, 0.5); draw(bc, 0.5); draw(ca, 0.5); }
draw(angC, 0.4);
wait(0.5);
section("Circumcircle");
say(cap, "the unique circle through all three vertices");
par { show(O); draw(cc, 0.9); }
wait(0.6);
section("Incircle & Centroid");
say(cap, "incircle (tangent to all sides) and centroid");
par { draw(ic, 0.9); show(G); }
wait(0.6);
section("Altitude");
say(cap, "drop a perpendicular from C to AB — its foot F");
par { show(F); draw(alt, 0.6); }
flash(F, magenta);
wait(1.0);
// the payoff: constructions are dynamic — drag a vertex and everything
// (circumcircle, incircle, centroid, foot, angle mark, sides) recomputes.
section("Drag a vertex");
say(cap, "move C — every construction follows");
move(C, (430, 230), 1.2, smooth);
move(C, (850, 210), 1.2, smooth);
move(C, (640, 190), 1.0, smooth);
say(cap, "and drag A");
move(A, (300, 520), 0.9, smooth);
move(A, (380, 560), 0.8, smooth);
wait(1.2);
incircle_tangents
The incircle and its tangent points.
// The Incircle & Contact Triangle — the incircle touches each side at the foot
// of the perpendicular from the incenter, and each radius meets the side at a
// right angle. The three touch points form the contact triangle.
//
// manic examples/incircle_tangents.manic
// manic examples/incircle_tangents.manic --record out --fps 60
title("The Incircle");
canvas(1280, 720);
text(head, (640, 120), "tangent to all three sides");
display(head); color(head, cyan); size(head, 28); hidden(head);
text(cap, (640, 668), ""); color(cap, dim); size(cap, 22);
point(A, (300, 560), "A");
point(B, (1000, 560), "B");
point(C, (640, 160), "C");
hidden(A); hidden(B); hidden(C);
segment(ab, A, B); segment(bc, B, C); segment(ca, C, A);
untraced(ab); untraced(bc); untraced(ca);
incenter(I, A, B, C); color(I, cyan); label(I, "I", (16, -14)); hidden(I);
incircle(ic, A, B, C); untraced(ic);
// touch points = feet of perpendiculars from I to each side
foot(tBC, I, B, C); foot(tCA, I, C, A); foot(tAB, I, A, B);
color(tBC, magenta); color(tCA, magenta); color(tAB, magenta);
hidden(tBC); hidden(tCA); hidden(tAB);
// radii to the touch points, with right-angle marks
segment(rBC, I, tBC); segment(rCA, I, tCA); segment(rAB, I, tAB);
color(rBC, lime); color(rCA, lime); color(rAB, lime);
untraced(rBC); untraced(rCA); untraced(rAB);
rightangle(qBC, I, tBC, B); rightangle(qCA, I, tCA, C); rightangle(qAB, I, tAB, A);
untraced(qBC); untraced(qCA); untraced(qAB);
// the contact triangle
segment(k1, tBC, tCA); segment(k2, tCA, tAB); segment(k3, tAB, tBC);
untraced(k1); untraced(k2); untraced(k3);
show(head, 0.5);
say(cap, "a triangle and its incentre I");
par { show(A); show(B); show(C); }
par { draw(ab, 0.5); draw(bc, 0.5); draw(ca, 0.5); }
show(I, 0.3);
wait(0.3);
section("Inscribed circle");
say(cap, "the incircle touches each side once");
draw(ic, 1.0);
stagger(0.15) { show(tBC); show(tCA); show(tAB); }
wait(0.3);
section("Radii ⟂ sides");
say(cap, "each radius meets its side at a right angle");
par { draw(rBC, 0.5); draw(rCA, 0.5); draw(rAB, 0.5); }
par { draw(qBC, 0.4); draw(qCA, 0.4); draw(qAB, 0.4); }
wait(0.4);
section("Contact triangle");
say(cap, "the three touch points form the contact triangle");
par { draw(k1, 0.5); draw(k2, 0.5); draw(k3, 0.5); }
wait(1.2);
tangents
Tangent lines from a point to a circle.
// Tangent Lines — the two tangents from an external point P to a circle, and
// the theorem that each tangent is perpendicular to the radius at its touch
// point. Everything is a DYNAMIC construction: move P and the touch points,
// tangent lines, radii, and right-angle marks all recompute live.
//
// New geo builtins: circle2 (circle by centre + a point on it), tangent
// (touch points from an external point), plus segment/rightangle tracking them.
//
// manic examples/tangents.manic
title("Tangent Lines");
canvas("16:9");
text(head, (cx, 96), "two tangents from a point -- each meets the radius at 90 degrees");
color(head, cyan); size(head, 24); hidden(head);
point(O, (520, 400), "O");
point(A, (520, 200)); // a point on the circle -> radius 200
point(P, (940, 380), "P");
hidden(A); // A just defines the radius; don't show it
circle2(circ, O, A); color(circ, dim); stroke(circ, 2); untraced(circ);
// the two touch points t0 / t1, and the tangent lines to them
tangent(t, P, O, A);
segment(l0, P, t0); color(l0, cyan); stroke(l0, 3); untraced(l0);
segment(l1, P, t1); color(l1, cyan); stroke(l1, 3); untraced(l1);
// radius to each touch point + the right-angle marks
segment(r0, O, t0); color(r0, dim); untraced(r0);
segment(r1, O, t1); color(r1, dim); untraced(r1);
rightangle(ra0, O, t0, P); color(ra0, lime); hidden(ra0);
rightangle(ra1, O, t1, P); color(ra1, lime); hidden(ra1);
// --- reveal ---
show(head, 0.5);
draw(circ, 0.8);
show(O, 0.3); show(P, 0.3);
section("The tangents");
par { draw(l0, 0.7); draw(l1, 0.7); }
show(t0, 0.3); show(t1, 0.3);
section("Radius meets tangent");
par { draw(r0, 0.5); draw(r1, 0.5); }
par { show(ra0, 0.4); show(ra1, 0.4); }
wait(0.6);
// --- prove it's dynamic: move P, everything tracks ---
section("Move the point");
move(P, (820, 230), 1.6, smooth);
wait(0.4);
move(P, (980, 470), 1.6, smooth);
wait(0.8);
move(P, (940, 380), 1.2, smooth);
wait(1.0);
orthocenter
The orthocentre from the three altitudes.
// Altitudes & Orthocenter — the three altitudes of a triangle meet at one
// point, the orthocenter H. Each altitude drops perpendicular to a side.
// Dynamic: drag a vertex and the altitudes still concur.
//
// manic examples/orthocenter.manic
// manic examples/orthocenter.manic --record out --fps 60
title("Altitudes & Orthocenter");
canvas(1280, 720);
text(head, (640, 120), "the three altitudes concur");
display(head); color(head, cyan); size(head, 28); hidden(head);
text(cap, (640, 668), ""); color(cap, dim); size(cap, 22);
point(A, (330, 540), "A");
point(B, (980, 560), "B");
point(C, (700, 190), "C");
hidden(A); hidden(B); hidden(C);
segment(ab, A, B); segment(bc, B, C); segment(ca, C, A);
untraced(ab); untraced(bc); untraced(ca);
// feet of the three altitudes
foot(fA, A, B, C); foot(fB, B, C, A); foot(fC, C, A, B);
color(fA, magenta); color(fB, magenta); color(fC, magenta);
hidden(fA); hidden(fB); hidden(fC);
// the altitudes themselves
segment(hA, A, fA); segment(hB, B, fB); segment(hC, C, fC);
color(hA, lime); color(hB, lime); color(hC, lime);
untraced(hA); untraced(hB); untraced(hC);
rightangle(qA, A, fA, B); rightangle(qB, B, fB, C); rightangle(qC, C, fC, A);
untraced(qA); untraced(qB); untraced(qC);
orthocenter(H, A, B, C); color(H, cyan); label(H, "H", (16, -14)); hidden(H);
show(head, 0.5);
say(cap, "a triangle");
par { show(A); show(B); show(C); }
par { draw(ab, 0.5); draw(bc, 0.5); draw(ca, 0.5); }
wait(0.3);
section("Drop the altitudes");
say(cap, "from each vertex, perpendicular to the opposite side");
seq {
par { draw(hA, 0.5); draw(qA, 0.4); show(fA); }
par { draw(hB, 0.5); draw(qB, 0.4); show(fB); }
par { draw(hC, 0.5); draw(qC, 0.4); show(fC); }
}
wait(0.3);
section("Orthocenter");
say(cap, "all three meet at the orthocenter H");
show(H, 0.4);
flash(H, magenta);
wait(0.6);
section("Drag a vertex");
say(cap, "move C — the altitudes still concur");
move(C, (520, 230), 1.2, smooth);
move(C, (820, 250), 1.2, smooth);
move(C, (700, 190), 1.0, smooth);
wait(1.0);
euler_line
The Euler line through centroid, circumcentre, orthocentre.
// The Euler Line — in any triangle, the circumcenter O, centroid G, and
// orthocenter H are collinear (and OG : GH = 1 : 2). Constructions are
// dynamic: drag C and the three centres stay on one line.
//
// manic examples/euler_line.manic
// manic examples/euler_line.manic --record out --fps 60
title("The Euler Line");
canvas(1280, 720);
text(head, (640, 120), "circumcenter, centroid, orthocenter — collinear");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (640, 668), ""); color(cap, dim); size(cap, 22);
point(A, (300, 560), "A");
point(B, (1000, 540), "B");
point(C, (560, 190), "C");
hidden(A); hidden(B); hidden(C);
segment(ab, A, B); segment(bc, B, C); segment(ca, C, A);
untraced(ab); untraced(bc); untraced(ca);
circumcircle(cc, A, B, C); untraced(cc);
circumcenter(O, A, B, C); color(O, magenta); label(O, "O", (18, -14)); hidden(O);
centroid(G, A, B, C); color(G, lime); label(G, "G", (18, -14)); hidden(G);
orthocenter(H, A, B, C); color(H, cyan); label(H, "H", (-30, -14)); hidden(H);
segment(euler, O, H); color(euler, magenta); stroke(euler, 3); untraced(euler);
show(head, 0.5);
say(cap, "any triangle, with its circumcircle");
par { show(A); show(B); show(C); }
par { draw(ab, 0.5); draw(bc, 0.5); draw(ca, 0.5); }
draw(cc, 0.9);
wait(0.4);
section("Three centres");
say(cap, "circumcenter O, centroid G, orthocenter H");
stagger(0.3) { show(O); show(G); show(H); }
wait(0.4);
section("The Euler line");
say(cap, "they always lie on a single line");
draw(euler, 0.9);
wait(0.6);
section("Drag a vertex");
say(cap, "move C — O, G, H stay collinear");
move(C, (770, 220), 1.2, smooth);
move(C, (420, 250), 1.2, smooth);
move(C, (560, 190), 1.0, smooth);
wait(1.0);
nine_point
The nine-point circle.
// The Nine-Point Circle — one circle through the three side-midpoints AND the
// three altitude feet. (It's the circumcircle of the medial triangle.)
// Dynamic: drag C and the circle still catches all six points.
//
// manic examples/nine_point.manic
// manic examples/nine_point.manic --record out --fps 60
title("The Nine-Point Circle");
canvas(1280, 720);
text(head, (640, 120), "three midpoints + three feet, one circle");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (640, 668), ""); color(cap, dim); size(cap, 22);
point(A, (320, 560), "A");
point(B, (1000, 560), "B");
point(C, (620, 175), "C");
hidden(A); hidden(B); hidden(C);
segment(ab, A, B); segment(bc, B, C); segment(ca, C, A);
untraced(ab); untraced(bc); untraced(ca);
// side midpoints
midpoint(mAB, A, B); midpoint(mBC, B, C); midpoint(mCA, C, A);
color(mAB, lime); color(mBC, lime); color(mCA, lime);
hidden(mAB); hidden(mBC); hidden(mCA);
// altitude feet
foot(fA, A, B, C); foot(fB, B, C, A); foot(fC, C, A, B);
color(fA, magenta); color(fB, magenta); color(fC, magenta);
hidden(fA); hidden(fB); hidden(fC);
// the nine-point circle = circumcircle of the medial triangle
circumcircle(npc, mAB, mBC, mCA); outline(npc, cyan); untraced(npc);
show(head, 0.5);
say(cap, "start with a triangle");
par { show(A); show(B); show(C); }
par { draw(ab, 0.5); draw(bc, 0.5); draw(ca, 0.5); }
wait(0.3);
section("Six points");
say(cap, "the three side-midpoints (lime)");
stagger(0.2) { show(mAB); show(mBC); show(mCA); }
say(cap, "and the three altitude feet (magenta)");
stagger(0.2) { show(fA); show(fB); show(fC); }
wait(0.4);
section("One circle");
say(cap, "a single circle passes through all six");
draw(npc, 1.0);
wait(0.6);
section("Drag a vertex");
say(cap, "move C — the circle still catches all six");
move(C, (820, 220), 1.3, smooth);
move(C, (440, 240), 1.3, smooth);
move(C, (620, 175), 1.0, smooth);
wait(1.0);
conics
Ellipse, parabola, hyperbola.
// The Conic Sections — the three curves you get by slicing a cone: the ellipse,
// the parabola, and the hyperbola. Each is a geo-kit primitive; they reveal one
// at a time with the defining property.
//
// manic examples/conics.manic
// manic examples/conics.manic --template blueprint
title("The Conic Sections");
canvas("16:9");
text(head, (cx, 80), "three curves from slicing a cone");
display(head); color(head, cyan); size(head, 26); hidden(head);
text(cap, (cx, 662), ""); color(cap, dim); size(cap, 23);
// --- ellipse (left) ---
ellipse(el, (300, 400), 165, 100); color(el, cyan); stroke(el, 3); untraced(el);
text(ell, (300, 250), "ellipse"); color(ell, cyan); size(ell, 26); hidden(ell);
// --- parabola (centre) ---
parabola(pa, (660, 540), 150, 270); color(pa, lime); stroke(pa, 3); untraced(pa);
text(pal, (660, 235), "parabola"); color(pal, lime); size(pal, 26); hidden(pal);
// --- hyperbola (right) — two branches, tagged `hy` ---
hyperbola(hy, (1010, 400), 55, 120); color(hy, magenta); stroke(hy, 3); untraced(hy);
text(hyl, (1010, 205), "hyperbola"); color(hyl, magenta); size(hyl, 26); hidden(hyl);
// --- reveal ---
show(head, 0.5);
section("Ellipse");
say(cap, "ellipse -- the sum of distances to two foci stays constant");
draw(el, 0.9);
show(ell, 0.4);
wait(0.5);
section("Parabola");
say(cap, "parabola -- every point is equidistant from a focus and a line");
draw(pa, 0.9);
show(pal, 0.4);
wait(0.5);
section("Hyperbola");
say(cap, "hyperbola -- two branches; the difference of distances stays constant");
draw(hy, 0.9);
show(hyl, 0.4);
wait(1.4);
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);
}
}
Text & UI
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
typewriter
Text revealed character by character.
// Typewriter — text revealed letter by letter (Manim's AddTextLetterByLetter),
// then removed letter by letter (RemoveTextLetterByLetter). `type` animates the
// text's `trace` 0->1 (a fraction of characters shown); `erase` runs it back.
// Declare the text `untraced` so it starts hidden, then `type` reveals it.
//
// manic examples/typewriter.manic
title("Typewriter");
canvas("16:9");
text(head, (cx, 150), "text, one letter at a time");
display(head); color(head, cyan); size(head, 26); hidden(head);
// untraced = trace 0 = no characters shown yet
text(line1, (cx, 320), "the quick brown fox"); color(line1, lime); size(line1, 44); untraced(line1);
text(line2, (cx, 400), "jumps over the lazy dog"); color(line2, cyan); size(line2, 44); untraced(line2);
cursor(line2); // this line types with a trailing cursor
// --- script ---
show(head, 0.5);
type(line1, 1.6); // AddTextLetterByLetter
type(line2, 1.6); // AddTextLetterByLetter, with a cursor
wait(1.0);
section("...and back");
erase(line2, 1.0); // RemoveTextLetterByLetter
erase(line1, 1.0);
wait(0.8);
captions
Karaoke / word-pop caption modes.
// Captions — karaoke word highlighting and TikTok-style word pop-in. `caption`
// lays out a phrase's words in a centred row (as {id}.w0, {id}.w1, ... tagged
// {id}.words); `karaoke` highlights them in sequence; `wordpop` pops them in one
// at a time.
//
// manic examples/captions.manic
// manic examples/captions.manic --record out --fps 60
title("Captions");
canvas("16:9");
text(head, (cx, 110), "word-by-word: karaoke + pop-in");
display(head); color(head, cyan); size(head, 26); hidden(head);
// karaoke: starts dim, words light up in sequence
caption(kara, "follow the bouncing highlight", (cx, 280), 46, dim);
// word-pop: hidden first, then each word pops in
caption(pop, "each word pops right in", (cx, 440), 50, lime);
hidden(pop.words);
// --- script ---
show(head, 0.5);
section("Karaoke");
karaoke(kara, 0.34, cyan);
wait(0.8);
section("Word pop");
wordpop(pop, 0.14);
wait(1.6);
terminal_boot
The neon terminal template booting up.
// Terminal Boot — a fake boot sequence typed out line by line, ending at a live
// prompt with a blinking-style cursor. Shows off the `cursor` modifier, `type`
// typewriter reveal, an author-set `masthead`, and the `terminal` template.
//
// manic examples/terminal_boot.manic
// manic examples/terminal_boot.manic --record out --fps 60
title("manic");
canvas("16:9");
template("terminal");
masthead("manic ~ %", "READY"); // your own header text (no engine branding)
text(l1, (cx, 210), ""); color(l1, lime); size(l1, 24);
text(l2, (cx, 260), ""); color(l2, cyan); size(l2, 24); hidden(l2);
text(l3, (cx, 310), ""); color(l3, cyan); size(l3, 24); hidden(l3);
text(l4, (cx, 360), ""); color(l4, lime); size(l4, 24); hidden(l4);
text(prompt, (cx, 440), ""); color(prompt, fg); size(prompt, 28); display(prompt);
hidden(prompt); cursor(prompt); // only the live prompt gets the cursor
// --- boot log ---
say(l1, "> initializing manic engine", 0.1);
type(l1, 1.0);
show(l2, 0.2);
say(l2, " loaded kits: std math geo algo brand", 0.1);
type(l2, 1.3);
show(l3, 0.2);
say(l3, " timeline: deterministic @ 60fps", 0.1);
type(l3, 1.0);
show(l4, 0.2);
say(l4, " ready.", 0.1);
type(l4, 0.5);
// --- the prompt, awaiting input ---
show(prompt, 0.2);
say(prompt, "manic ~ % render my_idea", 0.1);
type(prompt, 1.2);
wait(1.6);
brace
The curly-brace family.
// Braces — label spans and parts with curly braces, manic's Brace / BraceLabel
// / BraceBetweenPoints. A length is split into two parts a and b; a brace under
// each names it, and a brace over the whole names the sum. Every brace here is
// a BraceBetweenPoints (two points + a depth); bracelabel adds the text.
//
// manic examples/brace.manic
// manic examples/brace.manic --record out --fps 60
title("Braces");
canvas(1280, 720);
text(head, (640, 120), "label a span, or its parts");
display(head); color(head, cyan); size(head, 28); hidden(head);
text(cap, (640, 620), ""); color(cap, dim); size(cap, 24);
// the length, split at x = 680
line(seg, (300, 360), (980, 360)); color(seg, fg); stroke(seg, 3); untraced(seg);
dot(dl, (300, 360)); dot(dm, (680, 360)); dot(dr, (980, 360));
color(dl, magenta); color(dm, lime); color(dr, cyan);
hidden(dl); hidden(dm); hidden(dr);
// braces under the two parts, and over the whole (bulges up: points go R->L)
bracelabel(ba, (300, 392), (680, 392), "a", 30); color(ba, magenta);
bracelabel(bb, (680, 392), (980, 392), "b", 30); color(bb, cyan);
bracelabel(bt, (980, 320), (300, 320), "a + b", 34); color(bt, lime);
hidden(ba); hidden(bb); hidden(bt);
// --- reveal ---
show(head, 0.5);
say(cap, "here is a length");
draw(seg, 0.7);
par { show(dl, 0.3); show(dr, 0.3); }
wait(0.3);
section("Two parts");
say(cap, "split it at a point into parts a and b");
show(dm, 0.3);
show(ba, 0.5);
show(bb, 0.5);
wait(0.5);
section("The whole");
say(cap, "the whole span is a + b");
show(bt, 0.6);
par { pulse(ba.label); pulse(bb.label); pulse(bt.label); }
wait(1.4);
banner
The manic logo / banner reveal.
// The manic banner & watermark (à la ManimBanner). "create" draws the icon
// trio on; "expand" reveals the wordmark; the watermark persists in the corner.
//
// manic examples/banner.manic
// manic examples/banner.manic --record out --fps 60
title("manic");
canvas(1280, 720);
banner(logo, (600, 360), 1.1);
untraced(logo.icon); // icon shapes drawn on
hidden(logo.word); // wordmark revealed on "expand"
// a persistent, screen-fixed watermark, bottom-right
watermark(wm, (1120, 690), "manic // synthwave");
text(cap, (640, 560), ""); color(cap, dim); size(cap, 22);
// --- create: trace the icon trio on (staggered) ---
say(cap, "create");
stagger(0.2) {
draw(logo.dot, 0.6);
draw(logo.sq, 0.6);
draw(logo.tri, 0.6);
}
par { pulse(logo.dot); pulse(logo.sq); pulse(logo.tri); }
wait(0.4);
// --- expand: reveal the wordmark ---
say(cap, "expand");
show(logo.word, 0.6);
wait(1.2);
// --- unwrite: fade the whole banner ---
say(cap, "");
par { fade(logo.icon, 0.5); fade(logo.word, 0.5); }
wait(0.8);
equation
Display-quality LaTeX for fractions, roots, sums, powers and integrals, with semantic colour and template tinting.
// LaTeX math typesetting via `equation(...)`. Put the LaTeX in BACKTICKS so the
// backslashes survive. Rendered white-on-transparent and tinted by the entity
// colour, so `color(...)` and `template(...)` both work.
title("Equations");
canvas("16:9");
equation(quad, (cx, 150), `x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}`, 68);
color(quad, cyan);
equation(sum, (cx, 340), `\sum_{k=1}^{n} k = \frac{n(n+1)}{2}`, 60);
equation(euler, (cx, 500), `e^{i\pi} + 1 = 0`, 64);
color(euler, gold);
equation(intg, (cx, 640), `\int_0^1 x^2\,dx = \tfrac{1}{3}`, 56);
color(intg, lime);
inline-math
Inline LaTeX mixed with ordinary prose, including wrapped explanatory text and a standalone display equation.
// Inline LaTeX everywhere: wrap math in `$…$` (backtick raw string) inside ANY
// text/caption/kit label. Plain text (no `$`) is unchanged. Mixed lines wrap.
title("Inline math");
canvas("16:9");
text(t1, (cx, 110), `The area of a circle is $A = \pi r^2$`);
color(t1, cyan);
// mixed text + math that WRAPS across lines
text(t2, (cx, 300), `By the Pythagorean theorem, if $a^2 + b^2 = c^2$ then the triangle with sides a, b, c is right-angled`);
wrap(t2, 760);
color(t2, lime);
// a whole-formula label (auto-typeset, centred)
equation(eq, (cx, 470), `\int_0^{1} x^2 \, dx = \tfrac{1}{3}`, 60);
color(eq, gold);
image
Embed a raster image (PNG/JPG) with image(id, (x,y), "asset:name.png"|"path", w, h) —
a bundled or user-provisioned file animated like any entity (shown, spun, pulsed, moved).
Unlocks logos, avatars and photo backdrops (e.g. a creator’s brand in a template).
// ============================================================================
// image.manic — embed a raster image (PNG/JPG) in a scene
// ----------------------------------------------------------------------------
// `image(id, (x,y), "asset:name.png"|"path", [w], [h])` loads a bundled or
// user-provisioned image file and draws it
// centred at (x,y), w×h px — and it's an ordinary entity, so every verb
// (`show`/`move`/`fade`/`pulse`/`spin`/…) animates it. Here a bundled logo
// slides + fades in, pulses, then a caption types beside it. A missing ordinary
// path draws a crossed placeholder; a missing bundled asset is an error.
//
// (This unlocks real logos, avatars and photo backdrops for creator templates —
// a creator drops their brand image into a slot.)
// ============================================================================
title("Raster Images");
canvas("16:9");
image(logo, (cx, 340), "asset:manic-logo.png", 300, 300); hidden(logo);
text(cap, (cx, 600), "any PNG/JPG — and it animates like anything else");
color(cap, fg); size(cap, 34); bold(cap); untraced(cap);
// ================= THE SCENE =================
show(logo, 0.6); pulse(logo);
wait(0.4);
spin(logo, 1.0);
type(cap, 2.0);
wait(0.6);
par { move(logo, (cx, 320), 0.8, smooth); pulse(logo); }
wait(1.0);
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);
Creator formats — responsive social video
Creator Kit v2 turns a question, answers, media and a reusable creator profile into a polished timed social clip. The same source adapts to 9:16, 4:5, 1:1 and 16:9 with platform-safe regions. studio plus a balanced ring is the restrained default; timing controls the beat independently from timerstyle, whose native ring, bar, number, segments, ticks and pulse looks remain crisp at every size. Explicit layout, density, labels, motion, safe and accent controls customise the rest. Responsive native social icons, optional explanations and final end cards share the same brand profile.
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
perfect-reel
The gold-path production starter: phone-safe composition, real LaTeX, exact pacing, professional mono styling, creator identity, timeline markers and a focused end card.
// A production-ready Creator v2 Reel: safe layout, exact pacing, one focal
// equation, restrained motion, reusable branding, markers, and an end card.
title("Perfect Reel — Angle Ratio");
canvas("9:16");
template("mono");
watermark(manicMark, (w*0.895-100, h*0.075+24), "Made With Manic");
creator(me, "@anish2good name=Proof_Daily tagline=Think_then_prove yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Save_and_share safe=reels");
quiz(q, "Triangle angles: 2:3:4. What is the largest?",
"studio layout=media-first reveal=fade density=comfortable motion=calm safe=reels accent=cyan");
option(q, `$60^\circ$`);
option(q, `$80^\circ$`, correct);
option(q, `$90^\circ$`);
option(q, `$100^\circ$`);
explain(q, `The parts total $9$, so $x=20^\circ$ and $4x=80^\circ$.`, "Angle sum");
equation(prompt, (cx, 650), `2x+3x+4x=180^\circ`, 70);
color(prompt, cyan);
figure(prompt);
hidden(prompt);
// Exact 10.5-second quiz beat. The timer look can change independently.
timing(q, "calm ask=1.1 options=1 think=5.5 reveal=0.75 hold=2.15 stagger=0.06");
timerstyle(q, "look=ring position=below number=inside direction=drain size=medium thickness=1.1 color=cyan track=dim label=THINK font=mono finish=pulse");
socials(me);
endcard(me, "cta=Save_and_share");
mark("hook");
par {
run(q);
show(prompt, 0.5);
}
mark("endcard");
par {
fade(q.parts, 0.45);
fade(prompt, 0.45);
fade(me.footer, 0.45);
}
show(me.endcard, 0.6);
wait(1.4);
reactive-multiformat
One named reactive story rendered as portrait, 4:5 feed, square, or landscape with the
--canvas override. Responsive variables and layout branches reflow before construction,
while the same steps, timing, equation continuity and creator identity remain intact.
// ============================================================================
// reactive-multiformat.manic — one semantic story, four output formats
// ----------------------------------------------------------------------------
// Keep this source unchanged and render it with:
// manic examples/reactive-multiformat.manic --canvas portrait
// manic examples/reactive-multiformat.manic --canvas 4:5
// manic examples/reactive-multiformat.manic --canvas square
// manic examples/reactive-multiformat.manic --canvas 16:9
// The override is applied before w/h/cx/cy and the build-time layout branch.
// Every format keeps the same named steps, continuity, duration and identity.
// ============================================================================
title("One Story, Every Screen");
canvas("9:16");
template("shorts");
creator(me, "@anish2good name=Visual_Proofs tagline=One_idea_every_screen yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Keep_the_story safe=clean");
let u = (w+h-abs(w-h)) / 2160; // min(w,h) / 1080 without a layout-specific constant
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
// Portrait Reel / Short: story reads from top to bottom.
if h > 1.45*w {
text(kicker, (cx, 125*u), "ONE SOURCE · PORTRAIT");
text(headline, (cx, 215*u), "A derivative is a world of slopes");
equation(work, (cx, 355*u), `f(x)=0.35x^2`, 54*u);
rect(stage, (cx, 900*u), 930*u, 940*u);
axes(ax, (cx, 1030*u), 410*u, 350*u, 1);
plot(curve, (cx, 1030*u), 115*u, 62*u, "0.35*x*x", (-3.5,3.5));
deriv(derivative, curve);
tangent(tan, curve, -2.7, 235*u);
slope(rate, curve, -2.7, (24*u,-30*u));
equation(curveLabel, (cx+310*u, 690*u), `f(x)`, 31*u);
equation(derivativeLabel, (cx+300*u, 1290*u), `f'(x)`, 31*u);
text(caption, (cx, 1450*u), "Move along the curve and every local slope tells part of a second story.");
wrap(caption, w*0.72);
}
// Landscape lesson: explanation on the left, visual stage on the right.
else if w > 1.25*h {
text(kicker, (w*0.25, h*0.13), "ONE SOURCE · LANDSCAPE");
text(headline, (w*0.25, h*0.24), "A derivative is a world of slopes");
equation(work, (w*0.25, h*0.38), `f(x)=0.35x^2`, 54*u);
rect(stage, (w*0.72, h*0.48), w*0.49, h*0.70);
axes(ax, (w*0.72, h*0.55), 350*u, 285*u, 1);
plot(curve, (w*0.72, h*0.55), 90*u, 48*u, "0.35*x*x", (-3.5,3.5));
deriv(derivative, curve);
tangent(tan, curve, -2.7, 190*u);
slope(rate, curve, -2.7, (20*u,-25*u));
equation(curveLabel, (w*0.88, h*0.27), `f(x)`, 29*u);
equation(derivativeLabel, (w*0.88, h*0.76), `f'(x)`, 29*u);
text(caption, (w*0.25, h*0.60), "Move along the curve and every local slope tells part of a second story.");
wrap(caption, w*0.38);
}
// Square post / 4:5 feed: compact stacked composition.
else {
text(kicker, (cx, h*0.09), "ONE SOURCE · FEED");
text(headline, (cx, h*0.17), "A derivative is a world of slopes");
equation(work, (cx, h*0.27), `f(x)=0.35x^2`, 52*u);
rect(stage, (cx, h*0.55), w*0.86, h*0.48);
axes(ax, (cx, h*0.59), 360*u, 270*u, 1);
plot(curve, (cx, h*0.59), 92*u, 48*u, "0.35*x*x", (-3.5,3.5));
deriv(derivative, curve);
tangent(tan, curve, -2.7, 195*u);
slope(rate, curve, -2.7, (20*u,-25*u));
equation(curveLabel, (cx+270*u, h*0.42), `f(x)`, 29*u);
equation(derivativeLabel, (cx+270*u, h*0.73), `f'(x)`, 29*u);
text(caption, (cx, h*0.84), "Move along the curve and every local slope tells part of a second story.");
wrap(caption, w*0.72);
}
size(kicker, 22*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 39*u); color(headline, fg); bold(headline); hidden(headline);
size(caption, 25*u); color(caption, dim); hidden(caption);
color(stage, panel); outline(stage, dim); opacity(stage, 0.72); hidden(stage);
color(ax, dim); opacity(ax, 0.52); untraced(ax);
color(curve, cyan); stroke(curve, 6*u); glow(curve, 0.75); untraced(curve);
color(derivative, magenta); stroke(derivative, 5*u); dashed(derivative, 18*u, 11*u); untraced(derivative);
color(tan, gold); stroke(tan, 4*u); hidden(tan);
color(rate, gold); hidden(rate);
color(work, cyan); hidden(work);
color(curveLabel, cyan); hidden(curveLabel);
color(derivativeLabel, magenta); hidden(derivativeLabel);
socials(me);
step("question") {
show(stage, 0.35);
show(kicker, 0.35);
show(headline, 0.45);
show(work, 0.50);
draw(ax, 0.80);
draw(curve, 1.30);
show(curveLabel, 0.40);
show(caption, 0.45);
}
wait(0.65);
step("measure") {
rewrite(work, `f'(x)=0.70x`, 0.90, smooth);
show(tan, 0.40);
show(rate, 0.40);
to(tan, x, 2.7, 2.80, smooth);
to(rate, x, 2.7, 2.80, smooth);
say(caption, "The tangent and its live slope move as one explanation.", 0.40);
}
wait(0.55);
step("collect-the-slopes") {
rewrite(work, `\textcolor{magenta}{f'(x)}=0.70x`, 0.90, smooth);
fade(tan, 0.35);
fade(rate, 0.35);
draw(derivative, 1.55);
show(derivativeLabel, 0.40);
say(caption, "Collect those local slopes and the derivative curve appears.", 0.40);
}
wait(0.65);
step("takeaway") {
rewrite(work, `\text{slope of }f=\textcolor{magenta}{f'}`, 0.95, smooth);
pulse(curve, 0.70);
pulse(derivative, 0.70);
say(caption, "The layout can change. The idea, timing and identity stay together.", 0.40);
}
wait(1.60);
parameter-journeys
One visible parameter drives a quadratic plot, its live tangent and slope, a geometric
position, scale and a derived numeric readout. Named steps animate only the value; bind
keeps every representation continuous and the source reflows across all four formats.
// ============================================================================
// parameter-journeys.manic — one value, several continuous representations
// ----------------------------------------------------------------------------
// `parameter` creates the visible control. `bind` connects it once to the
// changing plot, tangent/slope, a geometric position, scale, and p² readout.
// The named steps only animate the parameter; the visual world reacts smoothly.
// Try every format with: manic check examples/parameter-journeys.manic --canvas all
// ============================================================================
title("Parameter Journeys");
canvas("9:16");
template("mono");
creator(me, "@anish2good name=Manic_Creator_Lab tagline=One_value_many_worlds yt=zarigatongy x=@anish2good web=8gwifi.org/manic footer=none accent=cyan safe=clean");
let u = (w+h-abs(w-h)) / 2160;
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
if h > 1.45*w {
text(kicker, (cx, 120*u), "PARAMETER JOURNEY");
text(headline, (cx, 200*u), "Change one idea. Keep the world.");
equation(rule, (cx, 315*u), `y=\textcolor{cyan}{a}x^2`, 58*u);
rect(stage, (cx, 770*u), 920*u, 720*u);
axes(ax, (cx, 790*u), 400*u, 270*u, 1);
plot(curve, (cx, 790*u), 112*u, 61*u, "0.22*x*x", (-3.3,3.3));
tangent(tan, curve, 1.15, 230*u);
slope(rate, curve, 1.15, (24*u,-34*u));
parameter(a, (cx, 1215*u), -1.2, -1.5, 1.5, "a", 2);
text(positionLabel, (cx, 1365*u), "THE SAME VALUE ALSO MOVES A POINT");
line(path, (w*0.18, 1450*u), (w*0.82, 1450*u));
dot(leftEnd, (w*0.18, 1450*u), 5*u);
dot(rightEnd, (w*0.82, 1450*u), 5*u);
circle(mover, (cx, 1450*u), 22*u);
bind(a, mover, x, w*0.18, w*0.82);
counter(magnitude, (cx, 1570*u), 0, 2, "a² = ", "");
text(caption, (cx, 1690*u), "Negative, zero, positive — every connected view stays in sync.");
wrap(caption, w*0.76);
}
else if w > 1.25*h {
text(kicker, (w*0.24, h*0.12), "PARAMETER JOURNEY");
text(headline, (w*0.25, h*0.22), "Change one idea. Keep the world.");
equation(rule, (w*0.25, h*0.34), `y=\textcolor{cyan}{a}x^2`, 55*u);
parameter(a, (w*0.25, h*0.49), -1.2, -1.5, 1.5, "a", 2);
text(positionLabel, (w*0.25, h*0.64), "THE SAME VALUE MOVES A POINT");
line(path, (w*0.09, h*0.72), (w*0.41, h*0.72));
dot(leftEnd, (w*0.09, h*0.72), 5*u);
dot(rightEnd, (w*0.41, h*0.72), 5*u);
circle(mover, (w*0.25, h*0.72), 22*u);
bind(a, mover, x, w*0.09, w*0.41);
counter(magnitude, (w*0.25, h*0.82), 0, 2, "a² = ", "");
rect(stage, (w*0.72, h*0.48), w*0.49, h*0.72);
axes(ax, (w*0.72, h*0.53), 345*u, 250*u, 1);
plot(curve, (w*0.72, h*0.53), 92*u, 51*u, "0.22*x*x", (-3.3,3.3));
tangent(tan, curve, 1.15, 195*u);
slope(rate, curve, 1.15, (20*u,-28*u));
text(caption, (w*0.72, h*0.88), "Every connected view stays in sync.");
wrap(caption, w*0.42);
}
else {
text(kicker, (cx, h*0.08), "PARAMETER JOURNEY");
text(headline, (cx, h*0.15), "Change one idea. Keep the world.");
equation(rule, (cx, h*0.24), `y=\textcolor{cyan}{a}x^2`, 53*u);
rect(stage, (cx, h*0.50), w*0.86, h*0.40);
axes(ax, (cx, h*0.52), 350*u, 225*u, 1);
plot(curve, (cx, h*0.52), 94*u, 46*u, "0.22*x*x", (-3.3,3.3));
tangent(tan, curve, 1.15, 190*u);
slope(rate, curve, 1.15, (20*u,-26*u));
parameter(a, (cx, h*0.75), -1.2, -1.5, 1.5, "a", 2);
text(positionLabel, (cx, h*0.80), "ONE VALUE · PLOT · POSITION · MAGNITUDE");
line(path, (w*0.20, h*0.85), (w*0.80, h*0.85));
dot(leftEnd, (w*0.20, h*0.85), 5*u);
dot(rightEnd, (w*0.80, h*0.85), 5*u);
circle(mover, (cx, h*0.85), 20*u);
bind(a, mover, x, w*0.20, w*0.80);
counter(magnitude, (cx, h*0.875), 0, 2, "a² = ", "");
text(caption, (cx, h*0.925), "Every view stays in sync.");
wrap(caption, w*0.78);
}
// One declaration per relationship. `p` is the live parameter and `x` is the
// plot coordinate only inside a plot-formula binding.
bind(a, curve, formula, "0.22*p*x*x");
bind(a, mover, scale, "0.78+0.22*abs(p)");
bind(a, magnitude, value, "p*p");
size(kicker, 21*u); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 37*u); bold(headline); hidden(headline);
size(positionLabel, 22*u); color(positionLabel, dim); bold(positionLabel); hidden(positionLabel);
size(caption, 23*u); color(caption, dim); hidden(caption);
color(rule, cyan); hidden(rule);
color(stage, panel); outline(stage, dim); opacity(stage, 0.68); hidden(stage);
color(ax, dim); opacity(ax, 0.48); untraced(ax);
color(curve, cyan); stroke(curve, 6*u); glow(curve, 0.75); untraced(curve);
color(tan, gold); stroke(tan, 4*u); hidden(tan);
color(rate, gold); hidden(rate);
color(path, dim); stroke(path, 3*u); untraced(path);
color(leftEnd, dim); color(rightEnd, dim); hidden(leftEnd); hidden(rightEnd);
color(mover, cyan); outline(mover, fg); hidden(mover);
color(magnitude, fg); bold(magnitude); hidden(magnitude);
hidden(a.widget);
socials(me);
step("opens-down") {
show(kicker, 0.30);
show(headline, 0.40);
show(rule, 0.45);
show(stage, 0.35);
draw(ax, 0.70);
draw(curve, 1.10);
show(tan, 0.35);
show(rate, 0.35);
show(a.widget, 0.45);
show(positionLabel, 0.35);
draw(path, 0.55);
show(leftEnd, 0.25);
show(rightEnd, 0.25);
show(mover, 0.35);
show(magnitude, 0.35);
show(caption, 0.40);
}
wait(0.65);
step("flatten") {
to(a, value, 0, 2.20, smooth);
say(caption, "At zero the quadratic, tangent slope, position and magnitude settle together.", 0.40);
}
wait(0.55);
step("opens-up") {
to(a, value, 1.25, 2.40, smooth);
say(caption, "One authored parameter now carries the whole visual family upward.", 0.40);
}
wait(0.60);
step("compare") {
to(a, value, -0.65, 2.10, smooth);
say(caption, "Try another case without rebuilding the plot, diagram or readouts.", 0.40);
}
wait(0.55);
step("takeaway") {
to(a, value, 1.5, 2.30, smooth);
pulse(rule, 0.70);
pulse(mover, 0.70);
say(caption, "Declare the relationship once. Animate only what the idea means.", 0.40);
}
wait(1.60);
pascal-triangle
A non-quiz Creator v2 Short built entirely from the computation layer: each cell’s
binomial coefficient is a prod reduction, the triangle reveals row by row, the
sum-of-two-parents rule is highlighted, and colouring the odd cells uncovers Sierpinski’s
triangle — all inside a branded 9:16 shorts frame with creator identity, socials and an
end card (no grid kit needed: the triangle is triangular).
// Pascal's Triangle — a real Creator v2 Short that shows off Manic's range: the
// numbers are COMPUTED live (a `prod` reduction per cell), a hidden fractal is
// revealed, and the close reframes it as "one script — Manic does math, physics,
// algorithms & more." 9:16 shorts template, branded identity + socials + end card.
title("Pascal's Triangle");
canvas("9:16");
template("shorts");
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_math_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Try_it_free safe=reels");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
let N = 8;
let cw = w*0.112;
let rh = w*0.112;
let top = h*0.21;
let rad = w*0.048;
// Build the triangle from the computation layer: each cell's binomial coefficient
// C(n,k) is a `prod` reduction on a filled disc. Tag by row (for the reveal) and
// by parity (for the fractal finale).
for n in 0..N {
for k in 0..n+1 {
let px = w*0.5 + (k - n*0.5)*cw;
let py = top + n*rh;
let val = prod(i in 1..k+1 : (n-k+i)/i);
dot(d{n}_{k}, (px, py), rad); color(d{n}_{k}, panel); tag(d{n}_{k}, discs); tag(d{n}_{k}, prow{n});
counter(v{n}_{k}, (px, py), val, 0); tag(v{n}_{k}, nums); tag(v{n}_{k}, prow{n});
let odd = round(val - 2*floor(val/2));
if odd > 0.5 { tag(d{n}_{k}, oddcells); } else { tag(d{n}_{k}, evencells); }
}
}
text(kicker, (cx, h*0.135), "This triangle of numbers hides a fractal");
size(kicker, 32); color(kicker, dim); bold(kicker); wrap(kicker, w*0.82);
text(cap, (cx, h*0.775), "every number here is computed, not typed");
size(cap, 33); color(cap, dim); wrap(cap, w*0.82);
socials(me);
endcard(me, "title=Try_Manic cta=8gwifi.org/manic");
hidden(discs); hidden(nums); hidden(cap);
untraced(kicker); cursor(kicker); // opening hook types on, letter by letter
// 1 — HOOK: type the question, then build the triangle. The caption's promise —
// "computed, not typed" — is Manic's real pitch: the numbers come from a formula.
mark("hook");
type(kicker, 1.4);
show(cap, 0.4);
for n in 0..N { show(prow{n}, 0.26); wait(0.09); }
wait(0.4);
// 2 — THE RULE: 3 + 3 = 6, every cell is the sum of its two parents.
step("rule") {
seq {
say(cap, "each cell = the two above it (3 + 3 = 6)");
par { flash(d3_1, gold); flash(d3_2, gold); }
flash(d4_2, cyan);
}
}
wait(0.5);
// 3 — THE TWIST: colour the odd numbers and Sierpinski's triangle appears.
step("fractal") {
par {
say(cap, "colour the ODD numbers — and a fractal appears");
recolor(oddcells, magenta);
}
}
wait(0.9);
// 4 — THE POINT: this whole thing was one short script — and Manic's range is
// the real story. Reframe the demo as evidence of what Manic can do.
step("power") {
seq {
say(cap, "one short script did all of this");
par { pulse(discs, 0.7); recolor(cap, cyan); }
say(cap, "Manic animates math, physics, algorithms & more — from plain text");
}
}
wait(1.3);
// 5 — END CARD: the brand CTA.
mark("endcard");
par {
fade(kicker, 0.4);
fade(cap, 0.4);
fade(me.footer, 0.4);
fade(discs, 0.4);
fade(nums, 0.4);
}
show(me.endcard, 0.6);
wait(1.6);
creator-lattice-paths
The rectangular cousin of Pascal, on a real grid-kit lattice: ‘how many ways from corner
to corner moving only right and down?’ Every cell’s path-count is a prod reduction
(C(i+j,i)), the same above-plus-left rule is highlighted, the far corner holds the total,
and one actual monotone path is traced with a spline. Blueprint template, and a
different typewriter beat — the question erases and retypes itself into the answer.
// Lattice-path counting — "how many ways from corner to corner moving only right
// and down?" The SAME recurrence as Pascal (cell = the one above + the one to the
// left) but on a RECTANGLE — so it uses the grid kit. Each cell's count is a `prod`
// reduction (C(i+j,i)); afterwards one actual path is traced with `spline`.
// Different template (blueprint) + different typewriter (a question that erases and
// retypes into its answer).
title("Lattice Paths");
canvas("9:16");
template("blueprint");
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_math_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=gold footer=signature cta=Try_it_free safe=reels");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
let C = 5; // columns of cells
let R = 4; // rows of cells
let s = w*0.162; // cell size
let gy = h*0.455; // grid centre y
let ox = cx - C*s*0.5;
let oy = gy - R*s*0.5;
// The rectangular lattice IS a grid-kit grid. Overlay a path-count on every cell:
// value(i,j) = C(i+j, i), the number of monotone right/down paths reaching it.
grid(lat, (cx, gy), C, R, s);
opacity(lat.cells, 0); // a lattice is its lines — clear the cell fills so the counts read
for i in 0..R {
for j in 0..C {
let val = prod(t in 1..i+1 : (j+t)/t); // C(i+j, i)
counter(pc{i}_{j}, (ox+(j+0.5)*s, oy+(i+0.5)*s), val, 0);
tag(pc{i}_{j}, pathnums); tag(pc{i}_{j}, prow{i});
}
}
// One monotone path (right/right/down/right/down/right/down) traced afterward.
spline(route,
(ox+0.5*s, oy+0.5*s), (ox+1.5*s, oy+0.5*s), (ox+2.5*s, oy+0.5*s),
(ox+2.5*s, oy+1.5*s), (ox+3.5*s, oy+1.5*s), (ox+3.5*s, oy+2.5*s),
(ox+4.5*s, oy+2.5*s), (ox+4.5*s, oy+3.5*s));
color(route, gold); stroke(route, 7); glow(route, 0.8); untraced(route); hidden(route);
// --- text: a question that later erases and retypes into the answer ---
text(kicker, (cx, h*0.115), "How many paths, corner to corner?");
size(kicker, 33); color(kicker, cyan); bold(kicker); wrap(kicker, w*0.86);
text(sub, (cx, h*0.165), "moving only RIGHT and DOWN");
size(sub, 27); color(sub, dim);
text(answer, (cx, h*0.13), "35 paths — every one of them counted.");
size(answer, 34); color(answer, gold); bold(answer); wrap(answer, w*0.86);
text(cap, (cx, h*0.80), "each cell counts the ways to reach it");
size(cap, 31); color(cap, dim); wrap(cap, w*0.84);
socials(me);
endcard(me, "title=Try_Manic cta=8gwifi.org/manic");
hidden(pathnums); hidden(cap);
untraced(kicker); cursor(kicker);
untraced(sub);
untraced(answer); hidden(answer);
// 1 — HOOK: the question types on (two lines), then the lattice fills in.
mark("hook");
type(kicker, 1.2);
type(sub, 0.9);
show(cap, 0.4);
for i in 0..R { show(prow{i}, 0.3); wait(0.12); }
wait(0.4);
// 2 — THE RULE: 4 + 6 = 10 — every cell is (the one above) + (the one to the left).
step("rule") {
seq {
say(cap, "= the cell above + the cell to its left (4 + 6 = 10)");
par { flash(pc1_3, cyan); flash(pc2_2, cyan); }
flash(pc2_3, gold);
}
}
wait(0.5);
// 3 — THE ANSWER: the far corner holds the total. The question erases & retypes.
step("answer") {
seq {
par { erase(kicker, 0.5); erase(sub, 0.4); }
fade(kicker, 0.15); // clear the residual typewriter cursor left after erase
par {
pulse(pc3_4, 0.8);
flash(pc3_4, gold);
show(answer, 0.1);
type(answer, 1.1);
}
say(cap, "and every path is a right/down staircase");
}
}
wait(0.5);
// 4 — TRACE ONE: draw an actual corner-to-corner path with a spline.
step("trace") {
par {
show(route, 0.2);
draw(route, 1.6);
}
}
wait(0.7);
// 5 — THE POINT: one script did the grid, the counting AND the path.
step("power") {
seq {
say(cap, "one short script: the grid, the counting, and the path");
par { pulse(pathnums, 0.7); recolor(cap, cyan); }
say(cap, "Manic animates math, physics, algorithms & more — from plain text");
}
}
wait(1.3);
// 6 — END CARD.
mark("endcard");
par {
fade(answer, 0.4); fade(cap, 0.4); fade(me.footer, 0.4);
fade(pathnums, 0.4); fade(lat.cells, 0.4); fade(lat.lines, 0.4); fade(route, 0.4);
}
show(me.endcard, 0.6);
wait(1.6);
creator-rule90-sierpinski
A QUIZ-style Short: Rule 90 (each new cell = its two upper neighbours XOR’d) draws
Sierpinski’s triangle from a single dot — because XOR of two parents is exactly Pascal’s
triangle mod 2 (a cell is lit iff C(n,k) is odd). The gasket is the quiz’s media (fit with
figure), building as you’re asked to predict it, then the correct card and the reason
reveal. Full Creator v2 quiz: question, options, think timer, explanation and end card.
// Rule 90 → Sierpinski — a QUIZ-style Creator v2 Short. Rule 90 is the elementary
// cellular automaton where each new cell = its two upper neighbours, XOR'd. From a
// single seed it draws Sierpinski's triangle — because XOR of two parents is exactly
// Pascal's triangle mod 2 (a cell is lit iff C(n,k) is ODD). Two birds, one stone:
// the CA, the binomial parity, and the fractal are the same picture. The gasket is
// the quiz's media (fit with `figure`), building as you're asked to predict it.
title("Rule 90 → Sierpinski");
canvas("9:16");
template("shorts");
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_math_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Try_it_free safe=reels");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
// The gasket: only the LIT cells (odd binomials) are drawn, so it reads as the
// clean Sierpinski triangle. `figure` fits the whole group into the media region.
let N = 16;
let cw = 30;
let rh = 30;
for n in 0..N {
for k in 0..n+1 {
let val = prod(i in 1..k+1 : (n-k+i)/i);
let odd = round(val - 2*floor(val/2));
if odd > 0.5 {
dot(sc{n}_{k}, (cx + (k - n*0.5)*cw, cy - N*rh*0.5 + n*rh), 11);
color(sc{n}_{k}, magenta); tag(sc{n}_{k}, sier); tag(sc{n}_{k}, srow{n});
}
}
}
figure(sier);
hidden(sier); // hidden at first, then builds row by row while you predict
quiz(q, "Rule 90: start with one dot. Each new cell = its two upper neighbours, XOR'd. What shape appears?",
"studio layout=media-first reveal=fade density=comfortable motion=calm safe=reels accent=cyan");
option(q, "A checkerboard");
option(q, "Sierpinski's triangle", correct);
option(q, "Random static");
option(q, "A solid pyramid");
explain(q, "XOR of two parents = Pascal's triangle mod 2 — the ODD cells are Sierpinski.", "Why");
timing(q, "calm ask=1.2 options=1.1 think=5.2 reveal=0.8 hold=2.4 stagger=0.06");
timerstyle(q, "look=ring position=below number=inside direction=drain size=medium thickness=1.1 color=cyan track=dim label=PREDICT font=mono finish=pulse");
socials(me);
endcard(me, "title=Try_Manic cta=8gwifi.org/manic");
// Play the quiz; in parallel, Rule 90 builds the gasket row by row so it finishes
// right as the answer is revealed.
mark("hook");
par {
run(q);
seq {
wait(1.1);
for n in 0..N { show(srow{n}, 0.16); wait(0.26); }
}
}
mark("endcard");
par {
fade(q.parts, 0.45);
fade(sier, 0.45);
fade(me.footer, 0.45);
}
show(me.endcard, 0.6);
wait(1.6);
creator-heightmap-world
The Grid→3D bridge as a narrated Short: a grid-kit WFC map settles in 2D, then the SAME grid
rises into 3D terrain via heightmap3 — camera pull-back, orbit, and a low ridge flyover.
speak carries the story VO; say keeps short on-screen captions; Manic promo + end card close
the piece. One grid, two dimensions, inside a 9:16 creator frame.
// Your Map Is a 3D World — Manic promo Short.
// A grid settles cell by cell (Wave Function Collapse), then the SAME grid lifts
// into 3D terrain. The camera pulls back, orbits, and flies low. Story narration
// (`speak`) is separate from short on-screen captions (`say`). Outro names Manic.
title("Your Map Is a 3D World");
canvas("9:16");
template("shorts");
voice("elevenlabs"); // story narration
// voice("elevenlabs", "alice");
// voice("elevenlabs", "jessica");
// voice("cartesia");
// voice("gtts");
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_math_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Try_it_free safe=reels");
watermark(mk, (w*0.955-100, h*0.045+24), "Made With Manic");
// The 2D map (grid kit) and the 3D terrain from the SAME grid.
grid(world, (cx, h*0.44), 16, 16, 52);
collapse(world, "islands", 8);
camera3((0, -1.6, 22), (0, 0, 0), 26);
heightmap3(land, world, "h*1.7 + fbm(x*1.2, y*1.2)*0.85 + 0.1", 8);
color(land, cyan);
hidden(land);
text(kicker, (cx, h*0.135), "your flat map is hiding a 3D world");
size(kicker, 33); color(kicker, cyan); bold(kicker); wrap(kicker, w*0.82);
text(cap, (cx, h*0.785), "");
size(cap, 31); color(cap, dim); wrap(cap, w*0.84);
text(promo, (cx, h*0.42), "Manic"); size(promo, 72); color(promo, cyan); bold(promo); hidden(promo);
text(promoSub, (cx, h*0.52), "plain text → animated explainers"); size(promoSub, 28); color(promoSub, magenta); wrap(promoSub, w*0.82); hidden(promoSub);
text(promoUrl, (cx, h*0.62), "8gwifi.org/manic"); size(promoUrl, 30); color(promoUrl, fg); bold(promoUrl); hidden(promoUrl);
socials(me);
endcard(me, "title=Try_Manic cta=8gwifi.org/manic");
untraced(kicker); cursor(kicker);
// Narration tells the story. Captions stay short punch-lines on screen.
// speak("…") = voice only; say(cap, "…") = text only; par both when needed.
// 1 — HOOK: open the idea, then the 2D map settles (Wave Function Collapse).
mark("hook");
par {
type(kicker, 1.4);
speak("Every world starts flat. Watch a map generate itself — cell by cell — until the islands lock.");
}
step("map") {
par {
say(cap, "cell by cell…");
speak("This is Wave Function Collapse. Constraints push the grid toward a coherent coast.");
run(world, 16, 3.0);
}
}
wait(0.25);
// 2 — the same grid lifts into 3D (still near top-down, so it reads flat at first).
mark("lift");
step("height") {
par {
say(cap, "same grid → 3D");
speak("Now lift the same grid into height. Nothing was redrawn — the cells became terrain.");
fade(world.cells, 0.5); fade(world.lines, 0.5);
show(land, 0.6);
}
}
// 3 — pull back to see the WHOLE world.
mark("reveal");
step("pullback") {
par {
say(cap, "pull back");
speak("Pull the camera back. The flat map was hiding a whole landscape.");
orbit3(25, 36, 30, 3.4, smooth);
}
}
wait(0.2);
// 4 — rotate around it.
mark("rotate");
step("orbit") {
par {
say(cap, "one grid, two worlds");
speak("One grid. Two readings — a 2D map for paths, a 3D world for place. Manic bridges them.");
recolor(cap, cyan);
orbit3(185, 32, 30, 4.4, smooth);
}
}
// 5 — fly in low over the peaks.
mark("zoom");
step("fly") {
par {
say(cap, "fly the ridges");
speak("Fly in low over the ridges. Every peak is computed from the map you just watched settle.");
orbit3(255, 15, 12, 3.8, smooth);
}
}
wait(0.4);
// 6 — MANIC PROMO: this video is the product.
mark("manic");
par {
fade(kicker, 0.35); fade(cap, 0.35); fade(me.footer, 0.35); fade(land, 0.45);
}
par {
say(cap, "made with Manic");
speak("And this whole Short was generated with Manic — a language for animated explainers.");
show(promo, 0.6);
}
wait(0.2);
par {
say(cap, "plain text → motion");
speak("You write plain text. Manic turns it into grids, 3D, camera moves, and voice — no timeline editor.");
show(promoSub, 0.5);
}
wait(0.2);
par {
say(cap, "8gwifi.org/manic");
speak("Learn more, try it free, and build your own explainers at eight g wifi dot org slash manic.");
show(promoUrl, 0.5); pulse(promoUrl, 0.8);
}
wait(0.5);
// 7 — END CARD.
mark("endcard");
par {
fade(cap, 0.4); fade(promo, 0.4); fade(promoSub, 0.4); fade(promoUrl, 0.4);
}
show(me.endcard, 0.6);
wait(1.8);
creator-noise-story
How Noise Builds Worlds — a Short walking procedural noise from 1D to fractal: raw rand(x)
(jagged) vs smooth noise(x) (Perlin), then noise(x,y) tilting from a flat field into a 3D
surface, then fbm stacking octaves into fractal terrain. Every visual is one formula in the
shared expression engine — the arc that motivated adding rand/noise/fbm.
// How Noise Builds Worlds — a Creator v2 Short on procedural noise, 1D → 2D → 3D →
// fractal. Raw RANDOM values (jagged) vs smooth PERLIN noise(x); then noise(x,y) as
// a surface; then stacking octaves (fbm) for fractal detail. Every visual is one
// formula — `rand`, `noise`, `fbm`. Captions use the word-pop (karaoke) variant;
// the final scene pulls the camera back for a full view of the fractal world.
title("How Noise Builds Worlds");
canvas("9:16");
template("shorts");
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_math_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Try_it_free safe=reels");
watermark(mk, (w*0.955-100, h*0.045+24), "Made With Manic");
// --- 1D: a coordinate frame with two curves, raw random vs smooth noise ---
axes(ax, (cx, h*0.33), w*0.4, 150);
color(ax, dim); untraced(ax);
plot(rnd, (cx, h*0.33), 62, 130, "rand(x)", (-6.5, 6.5));
color(rnd, magenta); untraced(rnd);
plot(perlin, (cx, h*0.33), 62, 130, "noise(x, 0)", (-6.5, 6.5));
color(perlin, cyan); glow(perlin, 0.7); untraced(perlin);
// --- 2D/3D: the same idea one dimension up. Near top-down so it reads flat first. ---
camera3((0, -1.5, 20), (0, 0, 0), 24);
surface3(hills, "noise(x*0.9, y*0.9)*2.0", (-4, 4), (-4, 4), 50);
color(hills, cyan); hidden(hills);
surface3(frac, "fbm(x*0.95, y*0.95)*2.4", (-4, 4), (-4, 4), 72);
color(frac, cyan); hidden(frac);
text(kicker, (cx, h*0.135), "random is not the same as noise");
size(kicker, 32); color(kicker, cyan); bold(kicker); wrap(kicker, w*0.82);
untraced(kicker); cursor(kicker);
// Word-pop captions — one per beat, popped in a word at a time, faded before the next.
caption(cR, "raw random values", (cx, h*0.75), 33, magenta); hidden(cR.words);
caption(cP, "smooth it into noise", (cx, h*0.75), 33, cyan); hidden(cP.words);
caption(cD, "add a dimension", (cx, h*0.75), 33, dim); hidden(cD.words);
caption(cL, "values become heights", (cx, h*0.75), 33, dim); hidden(cL.words);
caption(cF, "octaves build fractals", (cx, h*0.75), 33, cyan); hidden(cF.words);
caption(cE, "1D 2D 3D fractal", (cx, h*0.75), 34, cyan); hidden(cE.words);
caption(cM, "one formula. That's Manic.", (cx, h*0.75), 32, cyan); hidden(cM.words);
socials(me);
endcard(me, "title=Try_Manic cta=8gwifi.org/manic");
// 1 — HOOK + raw random values (jagged).
mark("hook");
type(kicker, 1.3);
step("random") {
par {
draw(ax, 0.6);
draw(rnd, 1.3);
seq { wait(0.5); wordpop(cR, 0.14); }
}
}
wait(0.5);
// 2 — smooth it into Perlin noise(x).
step("perlin") {
par {
fade(cR.words, 0.3);
fade(rnd, 0.5);
draw(perlin, 1.4);
seq { wait(0.4); wordpop(cP, 0.13); }
}
}
wait(0.6);
// 3 — one dimension up: noise(x,y) as a field (top-down reads flat).
step("to2d") {
par {
fade(cP.words, 0.3);
fade(ax, 0.5); fade(perlin, 0.5);
show(hills, 0.7);
seq { wait(0.4); wordpop(cD, 0.14); }
}
}
// 4 — tilt: the field IS a 3D surface (a heightmap).
step("lift") {
par {
fade(cD.words, 0.3);
orbit3(25, 34, 16, 3.6, smooth);
seq { wait(0.5); wordpop(cL, 0.13); }
}
}
wait(0.5);
// 5 — stack octaves into fbm; pull the camera WAY back for the full world.
step("fbm") {
par {
fade(cL.words, 0.3);
fade(hills, 0.6);
show(frac, 0.7);
orbit3(120, 40, 30, 3.4, smooth);
seq { wait(0.5); wordpop(cF, 0.13); }
}
}
// 6 — a slow tour of the whole fractal world at full distance.
step("tour") {
par {
fade(cF.words, 0.3);
orbit3(240, 36, 30, 4.6, smooth);
seq { wait(0.4); wordpop(cE, 0.16); }
}
}
wait(0.4);
// 7 — THE POINT.
step("point") {
seq {
fade(cE.words, 0.3);
wordpop(cM, 0.16);
}
}
wait(1.1);
// 8 — END CARD.
mark("endcard");
par {
fade(kicker, 0.4); fade(cM.words, 0.4); fade(me.footer, 0.4); fade(frac, 0.5);
}
show(me.endcard, 0.6);
wait(1.6);
creator-free-kicks
Roberto Carlos’ impossible free kick as a narrated Short: walk-in, Magnus with values,
the RK4 freekick curls in for a GOAL while the zero-spin twin sails wide, then a cylinder
flow explains the pressure difference. speak / say carry story VO vs captions; Manic promo
outro. Physics proven, then explained, in one branded 9:16 story.
// Roberto Carlos' Banana Kick — Manic promo cut. The physics story sells the
// product: every path and streamline is computed from a plain `.manic` script,
// then the outro names Manic and points to 8gwifi.org/manic.
title("Roberto Carlos' Banana Kick");
canvas("9:16");
template("shorts");
voice("cartesia"); // story narration
// voice("elevenlabs");
// voice("gtts");
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_math_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=gold footer=signature cta=Try_it_free safe=reels");
watermark(mk, (w*0.955-100, h*0.045+24), "Made With Manic");
// --- schematic top-down pitch ---
let kx = cx;
let ky = h*0.66;
rect(gbox, (cx, h*0.20), w*0.66, h*0.24); outline(gbox, dim);
rect(goal, (cx, h*0.085), w*0.34, h*0.035); outline(goal, fg);
line(gline, (cx-w*0.5, h*0.105), (cx+w*0.5, h*0.105)); color(gline, dim);
for j in 0..5 { dot(walld{j}, (cx + (j-2)*w*0.052, h*0.40), 15); color(walld{j}, dim); tag(walld{j}, wall); }
// --- Roberto Carlos: a stick figure (points + reflowing segments), gait by hand ---
let fY = ky + 48;
let hipY = ky - 4;
let neckY= ky - 96;
let handY= ky - 46;
let headY= ky - 114;
let sx0 = kx - 260;
point(neck, (sx0, neckY)); point(hip, (sx0, hipY));
point(handL,(sx0-24, handY)); point(handR,(sx0+24, handY));
point(footL,(sx0-16, fY)); point(footR,(sx0+16, fY));
hidden(neck); hidden(hip); hidden(handL); hidden(handR); hidden(footL); hidden(footR);
circle(head, (sx0, headY), 14); color(head, fg);
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, fg); color(armR, fg); color(legL, fg); color(legR, fg);
stroke(spine, 4); stroke(armL, 4); stroke(armR, 4); stroke(legL, 4); stroke(legR, 4);
tag(head, pl); tag(spine, pl); tag(armL, pl); tag(armR, pl); tag(legL, pl); tag(legR, pl);
// --- the kick: RK4 Magnus banana; the ball rides at the feet until placed ---
freekick(rc, (kx, ky), 30, 22, 3.1, 13, 175);
color(rc, gold); glow(rc, 0.8); untraced(rc); color(rc.ball, fg);
to(rc.ball, x, sx0+52, 0.01); to(rc.ball, y, ky, 0.01);
// the counterfactual: same strike, no spin → no Magnus → sails wide (red dashed)
freekick(ns, (kx, ky), 30, 22, 0, 13, 175);
color(ns, red); glow(ns, 0.4); dashed(ns); untraced(ns); hidden(ns.ball);
equation(calc, (cx, h*0.74), `\mathbf{a}_M=\kappa\,(\hat{\boldsymbol\omega}\times\mathbf{v})`, 42);
color(calc, gold); hidden(calc);
// --- Act 2 physics inset: the ball as a spinning cylinder (built now, revealed later) ---
let mpy = h*0.42;
circle(mcyl, (cx, mpy), 60); color(mcyl, gold); filled(mcyl); hidden(mcyl);
for i in 0..15 {
let y0 = -4.4 + i*0.62;
trajectory(mfl{i},
"1 - 1.44*(x*x-y*y)/((x*x+y*y)^2) - 0.7*y/(x*x+y*y)",
"0 - 2.88*x*y/((x*x+y*y)^2) + 0.7*x/(x*x+y*y)",
(-4.6, y0), (cx, mpy), 50, 460);
color(mfl{i}, cyan); glow(mfl{i}, 0.35); untraced(mfl{i}); tag(mfl{i}, flow);
}
arrow(spinT, (cx-30, mpy-72), (cx+30, mpy-72)); color(spinT, gold); hidden(spinT);
arrow(spinB, (cx+30, mpy+72), (cx-30, mpy+72)); color(spinB, gold); hidden(spinB);
arrow(force, (cx+140, mpy+30), (cx+140, mpy-70)); color(force, lime); stroke(force, 6); hidden(force);
text(flab, (cx+200, mpy-34), "Magnus\nforce"); size(flab, 24); color(flab, lime); hidden(flab);
text(fastl, (cx, mpy-115), "faster → low pressure"); size(fastl, 24); color(fastl, cyan); hidden(fastl);
text(slowl, (cx, mpy+115), "slower → high pressure"); size(slowl, 24); color(slowl, magenta); hidden(slowl);
text(kicker, (cx, h*0.13), "Roberto Carlos' impossible free kick");
size(kicker, 32); color(kicker, gold); bold(kicker); wrap(kicker, w*0.82);
text(cap, (cx, h*0.80), ""); size(cap, 30); color(cap, dim); wrap(cap, w*0.84);
text(promo, (cx, h*0.42), "Manic"); size(promo, 72); color(promo, gold); bold(promo); hidden(promo);
text(promoSub, (cx, h*0.52), "plain text → animated explainers"); size(promoSub, 28); color(promoSub, cyan); wrap(promoSub, w*0.82); hidden(promoSub);
text(promoUrl, (cx, h*0.62), "8gwifi.org/manic"); size(promoUrl, 30); color(promoUrl, fg); bold(promoUrl); hidden(promoUrl);
socials(me);
endcard(me, "title=Try_Manic cta=8gwifi.org/manic");
untraced(kicker); cursor(kicker);
// Narration tells the story. Captions stay short punch-lines on screen.
// Configure once: voice("gtts"|"cartesia"). speak("…") = voice only;
// say(cap, "…") = text only; par both when needed.
// 1 — HOOK: open the legend.
mark("hook");
par {
type(kicker, 1.3);
speak("France, nineteen ninety-seven. Roberto Carlos stands thirty-five metres out — and takes a free kick the world still cannot believe.");
}
// 2 — WALK the ball in and place it (sin-phased gait).
show(cap, 0.3);
par {
say(cap, "walk it in");
speak("He walks the ball in himself and sets it down. No rush. The wall is waiting.");
}
for i in 1..11 {
let bx = sx0 + i*20; let ph = i*90; let sw = 22*sin(ph*pi/180); let lift = 8*abs(sin(ph*pi/180));
par {
move(neck,(bx, neckY-lift*0.4),0.11,smooth); move(hip,(bx, hipY),0.11,smooth);
move(handL,(bx-sw, handY),0.11,smooth); move(handR,(bx+sw, handY),0.11,smooth);
move(footL,(bx+sw, fY-lift),0.11,smooth); move(footR,(bx-sw, fY-lift),0.11,smooth);
move(head,(bx, headY-lift*0.4),0.11,smooth); move(rc.ball,(bx+52, ky),0.11,smooth);
}
}
wait(0.3);
// 3 — PACE BACK; the calculation begins.
fade(kicker, 0.4); show(calc, 0.4);
par {
say(cap, "pace back");
speak("He paces back. Eyes on the posts. Somewhere in that run-up is a curve no keeper expects.");
}
for i in 1..6 {
let bx = (kx-60) - i*18; let ph = i*90; let sw = 16*sin(ph*pi/180); let lift = 6*abs(sin(ph*pi/180));
par {
move(neck,(bx, neckY-lift*0.4),0.12,smooth); move(hip,(bx, hipY),0.12,smooth);
move(handL,(bx-sw, handY),0.12,smooth); move(handR,(bx+sw, handY),0.12,smooth);
move(footL,(bx+sw, fY-lift),0.12,smooth); move(footR,(bx-sw, fY-lift),0.12,smooth);
move(head,(bx, headY-lift*0.4),0.12,smooth);
}
}
// 4 — COMPUTE the Magnus law WITH VALUES, then lock it.
rewrite(calc, `\mathbf{a}_M=(0.05)(3.1)(30)`, 0.7, smooth);
rewrite(calc, `\mathbf{a}_M\approx 4.6\ \mathrm{m/s^2}`, 0.7, smooth);
par {
say(cap, "Magnus locks");
recolor(cap, lime);
speak("Physics names the cheat code: Magnus acceleration. Spin crossed with velocity. About four point six metres per second squared of sideways push.");
}
rewrite(calc, `\text{curl}\approx 3\ \text{m}`, 0.6, smooth);
wait(0.3);
// 5 — RAN FAST up to the ball.
recolor(cap, dim); say(cap, ""); fade(calc, 0.4);
speak("Then he runs.");
for i in 1..9 {
let bx = (kx-150) + i*15; let ph = i*120; let sw = 30*sin(ph*pi/180); let lift = 12*abs(sin(ph*pi/180));
par {
move(neck,(bx, neckY-lift*0.4),0.05,smooth); move(hip,(bx, hipY),0.05,smooth);
move(handL,(bx-sw, handY),0.05,smooth); move(handR,(bx+sw, handY),0.05,smooth);
move(footL,(bx+sw, fY-lift),0.05,smooth); move(footR,(bx-sw, fY-lift),0.05,smooth);
move(head,(bx, headY-lift*0.4),0.05,smooth);
}
}
// 6 — STRIKE → the banana swerves in by the post.
seq {
par { move(footR,(kx-4, ky+34),0.13,smooth); move(hip,(kx-30, hipY),0.13,smooth); }
par {
draw(rc, 2.0); run(rc, 2.0);
seq {
wait(1.1);
recolor(cap, gold);
par {
say(cap, "curl!");
speak("The shot starts metres wide of the post — then the air bends it in.");
}
}
}
}
wait(0.4);
// 7 — GOAL.
recolor(cap, gold);
par {
say(cap, "GOAL");
speak("Goal. Without that spin, the ball never finds the net.");
flash(goal, gold); pulse(goal, 0.7);
}
wait(0.5);
// 7.5 — proof: same strike, no spin → sails wide.
recolor(cap, red);
par {
say(cap, "no spin → wide");
speak("Same strike. Zero spin. No air pushing sideways. Watch it sail wide — the proof.");
draw(ns, 1.3);
}
wait(0.5);
// 8 — REWIND.
recolor(cap, dim);
par {
say(cap, "rewind");
speak("So why does spin bend a football? Rewind.");
erase(rc, 0.9); erase(ns, 0.9);
to(rc.ball, x, kx, 0.9, smooth); to(rc.ball, y, ky, 0.9, smooth);
}
wait(0.3);
// 9 — spinning cylinder + flow field.
par {
say(cap, "spinning cylinder");
speak("Zoom into the ball. In flight it is a spinning cylinder in the air stream.");
fade(pl, 0.5); fade(head, 0.5);
fade(gbox, 0.5); fade(goal, 0.5); fade(gline, 0.5); fade(wall, 0.5);
to(rc.ball, x, cx, 0.6, smooth); to(rc.ball, y, mpy, 0.6, smooth);
show(mcyl, 0.5);
}
stagger(0.04) { for i in 0..15 { draw(mfl{i}, 0.9); } }
wait(0.3);
// 10 — asymmetry → Magnus force.
par {
say(cap, "faster / slower");
speak("Spin drags the air: faster on top, slower below. Fast air means low pressure.");
show(spinT, 0.4); show(spinB, 0.4); show(fastl, 0.5); show(slowl, 0.5);
}
wait(0.3);
par {
say(cap, "Magnus force");
speak("The pressure difference pushes the ball sideways. That sideways push is the Magnus force.");
show(force, 0.6); show(flab, 0.5); pulse(force, 0.8);
}
wait(0.4);
// 11 — the point.
recolor(cap, cyan);
par {
say(cap, "computed, not drawn");
speak("That force is what curled Carlos' free kick. Every path you saw was computed — not drawn by hand.");
}
wait(0.5);
// 12 — MANIC PROMO: this video is the product.
mark("manic");
par {
fade(cap, 0.35); fade(me.footer, 0.35);
fade(mcyl, 0.4); fade(flow, 0.4); fade(rc.ball, 0.4);
fade(spinT, 0.4); fade(spinB, 0.4); fade(force, 0.4); fade(flab, 0.4); fade(fastl, 0.4); fade(slowl, 0.4);
fade(kicker, 0.35);
}
par {
say(cap, "made with Manic");
speak("And this whole video was generated with Manic — a language for animated explainers.");
show(promo, 0.6);
}
wait(0.25);
par {
say(cap, "plain text → motion");
speak("You write plain text. Manic turns it into motion, math, physics, and voice — no timeline editor, no keyframes by hand.");
show(promoSub, 0.5);
}
wait(0.25);
par {
say(cap, "8gwifi.org/manic");
speak("Learn more, try it free, and build your own explainers at eight g wifi dot org slash manic.");
show(promoUrl, 0.5); pulse(promoUrl, 0.8);
}
wait(0.6);
// 13 — END CARD.
mark("endcard");
par {
fade(cap, 0.4); fade(promo, 0.4); fade(promoSub, 0.4); fade(promoUrl, 0.4);
}
show(me.endcard, 0.6);
wait(1.8);
creator-pathfinding
The Shortest Path — a Short where imported VECTOR art poses the problem and a Manic
algorithm solves it: an svg() robot and pin (crisp, faithful colours) mark the start and
goal of a maze, a naive straight line cuts through the walls, then A* (gridastar) searches
the grid cell-by-cell and traces the true shortest path around them. Vectors imported, path
computed — Manic does both, in one branded 9:16 file with ElevenLabs problem→solve→CTA VO.
// The Shortest Path — a Short where VECTOR art poses the problem and a Manic
// ALGORITHM solves it. An imported robot and pin (crisp `svg()` vectors) sit at the
// start and goal of a maze; a naive straight line cuts through the walls; then A*
// (`gridastar`) searches the space cell-by-cell and traces the true shortest route.
// Vectors imported, path computed — Manic does both, in one branded 9:16 file.
title("The Shortest Path");
canvas("9:16");
template("shorts");
voice("elevenlabs"); // promo narration (ELEVENLABS_API_KEY)
// voice("elevenlabs", "alice");
// voice("cartesia"); // voice("gtts"); // free fallback
// speak("…") = voice only; say(cap, "…") = on-screen caption.
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_math_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=gold footer=signature cta=Try_it_free safe=reels");
watermark(mk, (w*0.955-100, h*0.045+24), "Made With Manic");
// --- the maze grid + the cell geometry (so the vector icons land on cells) ---
let cs = w*0.11;
let gx = cx;
let gy = h*0.43;
let sx = gx - 2.5*cs; let sy = gy - 3.5*cs; // start cell (0,0) centre
let px = gx + 2.5*cs; let py = gy + 3.5*cs; // goal cell (5,7) centre
grid(g, "@ . . . . . ; # # # # # . ; . . . . . . ; . # # # # # ; . . . . . . ; # # # # # . ; . . . . . . ; . # # # # *", (gx, gy), 6, 8, cs);
neighbors(g, "4");
// --- imported vector characters (faithful colours, crisp at any size) ---
svg(bot, (sx, sy), "asset:svg/robot.svg", cs*0.8); z(bot, 6); hidden(bot);
svg(dest, (px, py), "asset:svg/pin.svg", cs*0.8); z(dest, 6); hidden(dest);
// the naive "just go straight" line — cuts across the walls
line(naive, (sx, sy), (px, py)); color(naive, red); dashed(naive); z(naive, 4); hidden(naive);
text(hook, (cx, h*0.12), "Can the robot reach the pin?");
size(hook, 34); color(hook, gold); bold(hook); wrap(hook, w*0.82);
text(cap, (cx, h*0.78), ""); size(cap, 30); color(cap, dim); wrap(cap, w*0.84);
socials(me);
endcard(me, "title=Try_Manic cta=8gwifi.org/manic");
untraced(hook); cursor(hook);
// 1 — HOOK
mark("hook");
type(hook, 1.2);
speak("How does a robot find its way through a maze?");
// 2 — BUILD: the maze is the world; the imported vectors are its characters
show(cap, 0.3); say(cap, "a maze — the robot and pin are imported SVG vectors");
speak("Drop in a robot and its goal — both imported as crisp SVG vectors.");
par { show(bot, 0.5); show(dest, 0.5); }
wait(0.6);
// 3 — PROBLEM: the straight line fails
fade(hook, 0.4); recolor(cap, red); say(cap, "a straight line? it cuts right through the walls");
speak("The obvious answer — a straight line — slams right into the walls.");
show(naive, 0.5); flash(naive, red);
wait(0.9);
// 4 — SOLVE: A* searches, then traces the real route
fade(naive, 0.4); recolor(cap, dim); say(cap, "A* searches the space, cell by cell…");
speak("So Manic runs A-star — searching the grid, cell by cell,");
gridastar(g, (0,0), (5,7), manhattan);
recolor(cap, gold); say(cap, "…and traces the true shortest path");
speak("until it traces the true shortest path around every wall.");
draw(g.path, 1.6);
wait(0.6);
// 5 — POINT
recolor(cap, cyan); say(cap, "vectors imported, path computed — Manic does both");
speak("Vectors imported, path computed — Manic does both. Turn plain text into animated explainers, free, at eight g wifi dot org slash manic.");
wait(1.2);
// 6 — END CARD — clear the maze, path, and characters so the CTA stands alone
mark("endcard");
par {
fade(cap, 0.4); fade(me.footer, 0.4);
fade(g.cells, 0.4); fade(g.lines, 0.4); fade(g.path, 0.4);
fade(g.frontier, 0.4); fade(g.visited, 0.4);
fade(bot, 0.4); fade(dest, 0.4);
}
show(me.endcard, 0.6);
wait(1.6);
creator-monty-hall
The Monty Hall Problem — the most-argued puzzle in probability, as an ElevenLabs-narrated 9:16
Short where imported SVG carries the whole story: three door emoji, a car, goats, a party
popper. You pick a door, the host lifts another to a goat — and the teaching beat is VISUAL:
the opened door’s 1/3 chance visibly SLIDES onto the other door, counting it up to 2/3, so
switching DOUBLES your odds. The payoff morphs a goat into the car with a confetti burst,
then a reveal-all finale (goat · car · goat) and a Manic CTA card. speak VO carries the
story, say the captions; every actor is one svg() line. Problem → solution → CTA.
// creator-monty-hall.manic — a Creator v2 Short on the Monty Hall problem, one of
// the most-searched (and most-argued) puzzles in probability, narrated with an
// ElevenLabs voice story. The whole cast is imported SVG — three door emoji, a car,
// goats, a party popper — and the teaching beat is visual: when the host opens a
// goat door, its 1/3 chance visibly SLIDES onto the other door, making it 2/3. The
// payoff `morph`s a goat into the car with a confetti `burst`. Problem → the
// counter-intuitive solution → CTA, 9:16 shorts template, branded.
//
// ELEVENLABS_API_KEY=... manic examples/creator-monty-hall.manic --record out.mp4
// speak("…") = voice only; say(cap,"…") = on-screen caption.
title("The Monty Hall Problem");
canvas("9:16");
template("shorts");
voice("elevenlabs");
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_math_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Try_it_free safe=reels");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
// --- header ---
text(kick, (cx, h*0.10), "The Monty Hall Problem"); size(kick, 48); color(kick, fg); bold(kick); wrap(kick, w*0.86); hidden(kick);
text(sub, (cx, h*0.155), "The puzzle that fooled even the pros"); size(sub, 29); color(sub, dim); wrap(sub, w*0.82); hidden(sub);
// --- three doors (imported emoji), numbered, with a 1/3 odds chip each ---
let dy = h*0.375;
let dsz = w*0.185;
let xL = w*0.22; let xM = w*0.50; let xR = w*0.78;
svg(door1, (xL, dy), "asset:svg/emoji/door.svg", dsz); hidden(door1);
svg(door2, (xM, dy), "asset:svg/emoji/door.svg", dsz); hidden(door2);
svg(door3, (xR, dy), "asset:svg/emoji/door.svg", dsz); hidden(door3);
text(t1, (xL, dy-dsz*0.66), "1"); size(t1, 32); color(t1, dim); bold(t1); hidden(t1);
text(t2, (xM, dy-dsz*0.66), "2"); size(t2, 32); color(t2, dim); bold(t2); hidden(t2);
text(t3, (xR, dy-dsz*0.66), "3"); size(t3, 32); color(t3, dim); bold(t3); hidden(t3);
counter(chip1, (xL, dy+dsz*1.02), 33, 0, "", "%"); size(chip1, 44); color(chip1, dim); bold(chip1); hidden(chip1);
counter(chip2, (xM, dy+dsz*1.02), 33, 0, "", "%"); size(chip2, 44); color(chip2, dim); bold(chip2); hidden(chip2);
counter(chip3, (xR, dy+dsz*1.02), 33, 0, "", "%"); size(chip3, 44); color(chip3, dim); bold(chip3); hidden(chip3);
// a selection ring for "your pick"
circle(pick, (xL, dy), dsz*0.62); outlined(pick); outline(pick, cyan); stroke(pick, 5); glow(pick, 0.8); hidden(pick);
// prizes hidden BEHIND each door (revealed when a door lifts)
svg(goat1, (xL, dy), "asset:svg/emoji/goat.svg", dsz*0.86); z(goat1, -1); hidden(goat1);
svg(goat3, (xR, dy), "asset:svg/emoji/goat.svg", dsz*0.86); z(goat3, -1); hidden(goat3);
// behind door 2: a goat that MORPHS into the car (the payoff flourish)
svg(goatH, (xM, dy), "asset:svg/emoji/goat.svg", dsz*0.92); z(goatH, -1); hidden(goatH);
svg(carH, (xM, dy), "asset:svg/emoji/car.svg", dsz*1.02); z(carH, -1); hidden(carH);
morph(goatH, carH);
svg(party, (xM+dsz*0.9, dy-dsz*0.8), "asset:svg/emoji/party.svg", dsz*0.7); z(party, 6); hidden(party);
circle(bcore, (xM, dy), 30); hidden(bcore);
particles(sparks, bcore, 26, 8, 11, "random"); color(sparks, gold); glow(sparks, 0.9); z(sparks, 7); hidden(sparks);
// one caption line, retexted through the story
text(cap, (cx, h*0.66), ""); size(cap, 35); color(cap, dim); bold(cap); wrap(cap, w*0.86); hidden(cap);
socials(me);
endcard(me, "title=Always_Switch cta=8gwifi.org/manic");
// ---------------- the story (voice leads; visuals punctuate) ----------------
par { show(kick); show(sub); }
stagger(0.16) { show(door1); show(door2); show(door3); }
par { show(t1); show(t2); show(t3); show(chip1); show(chip2); show(chip3); }
show(cap, 0.3); say(cap, "1 car, 2 goats — 1/3 each.");
speak("Three doors. One car, two goats. You pick door one.");
par { show(pick); pulse(door1); }
say(cap, "You pick Door 1.");
speak("Now the host, who knows, opens door three. A goat.");
recolor(cap, dim); say(cap, "Host opens Door 3 — a goat.");
show(goat3);
par { move(door3, (xR, dy-h*0.30), 0.7, smooth); fade(door3); }
speak("The twist: door three's odds don't vanish. They slide onto door two.");
recolor(cap, cyan); say(cap, "Its 1/3 slides to Door 2.");
par { move(chip3, (xM, dy+dsz*1.02), 0.8, smooth); fade(chip3); to(chip2, value, 67, 0.8); }
par { recolor(chip2, cyan); recolor(chip1, magenta); }
speak("Most people say fifty-fifty. It isn't.");
recolor(cap, red); say(cap, "Feels like 50/50. It's not."); shake(cap);
speak("Your door stays one-in-three. Door two? Two-in-three. Switching doubles it.");
recolor(cap, gold); say(cap, "STAY 33% · SWITCH 67%");
par { pulse(chip2); pulse(door2); }
speak("So you switch...");
recolor(cap, dim); say(cap, "So you switch…");
show(goatH);
par { move(door2, (xM, dy-h*0.30), 0.7, smooth); fade(door2); fade(chip1); fade(chip2); }
speak("...and the goat becomes a car.");
to(goatH, morph, 1, 0.9, smooth);
par { fade(goatH); show(carH); }
recolor(cap, gold); say(cap, "…and win the car!");
par { show(sparks, 0.05); burst(sparks, 1.1); show(party); pulse(carH); }
wait(0.6);
speak("Door one? A goat. Switching wins two out of three.");
recolor(cap, dim); say(cap, "Door 1 was a goat.");
show(goat1);
par { move(door1, (xL, dy-h*0.30), 0.7, smooth); fade(door1); fade(pick); fade(party); }
wait(0.4);
speak("Always switch. One Manic script made this — turn any idea into a video at eight g wifi dot org slash manic.");
par { fade(kick); fade(sub); fade(cap); fade(carH); fade(goat1); fade(goat3); fade(t1); fade(t2); fade(t3); fade(chip3); }
show(me.endcard);
wait(2.6);
gradient-fastest-descent-shorts
The brachistochrone as a branded 9:16 Short: four height-colored wires race under gravity,
the cycloid wins, then a "curvature" gradient reveals why — it bends hardest at the start.
Creator chrome (socials + end card) mirrors creator-free-kicks; companion 16:9 cut is
gradient-fastest-descent.
// ============================================================================
// gradient-fastest-descent-shorts.manic — Bernoulli's brachistochrone as a
// 9:16 cinematic short, told THROUGH gradients
// (companion: gradient-fastest-descent.manic, the 16:9 cut)
// ----------------------------------------------------------------------------
// THE PROBLEM (Bernoulli, 1696): which wire gets a bead from A to B fastest?
// THE GRADIENTS: every ramp is a vertical 3-stop speedometer
// (v = √(2gΔh) — depth IS speed); the winner's secret is a curvature gradient.
//
// Pattern mirrors creator-free-kicks.manic: hook → setup → race → secret →
// endcard. Every curve and bead is RK4, not drawn.
// ============================================================================
title("The Fastest Descent");
canvas("9:16");
template("shorts");
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_math_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=gold footer=signature cta=Try_it_free safe=reels");
watermark(mk, (w*0.955-100, h*0.045+24), "Made With Manic");
// ---- backdrop ----
circle(stage, (cx, h*0.42), w*0.55);
filled(stage);
gradient(stage, panel, void, radial);
opacity(stage, 0.55);
// ---- the race, sized for portrait: A upper-left, B lower-right ----
let rx = w*0.14;
let ry = h*0.30;
let ru = w*0.22;
brachistochrone(race, (rx, ry), ru);
untraced(race.straight); untraced(race.circle); untraced(race.parabola); untraced(race.cycloid);
hidden(race.bead_straight); hidden(race.bead_circle); hidden(race.bead_parabola); hidden(race.bead_cycloid);
hidden(race.markA); hidden(race.labelA); hidden(race.markB); hidden(race.labelB);
stroke(race.straight, 4); stroke(race.circle, 4); stroke(race.parabola, 4); stroke(race.cycloid, 6);
opacity(race.straight, 0.95); opacity(race.circle, 0.95); opacity(race.parabola, 0.95); opacity(race.cycloid, 1);
// depth IS speed — blue high (potential), gold low (kinetic)
gradient(race.straight, gold, cyan, blue, 270);
gradient(race.circle, gold, cyan, blue, 270);
gradient(race.parabola, gold, cyan, blue, 270);
gradient(race.cycloid, gold, cyan, blue, 270);
// ghost cycloid for the curvature reveal
brachistochrone(ghost, (rx, ry), ru);
hidden(ghost.straight); hidden(ghost.circle); hidden(ghost.parabola);
hidden(ghost.bead_straight); hidden(ghost.bead_circle); hidden(ghost.bead_parabola); hidden(ghost.bead_cycloid);
hidden(ghost.markA); hidden(ghost.labelA); hidden(ghost.markB); hidden(ghost.labelB);
hidden(ghost.cycloid); untraced(ghost.cycloid);
stroke(ghost.cycloid, 7);
gradient(ghost.cycloid, dim, magenta, "curvature");
glow(ghost.cycloid, 0.7);
// ---- typography ----
text(kicker, (cx, h*0.11), "shortest path — or fastest?");
size(kicker, 32); color(kicker, gold); bold(kicker); wrap(kicker, w*0.84);
untraced(kicker); cursor(kicker);
equation(law, (cx, h*0.62), `v=\sqrt{2g\,\Delta h}`, 38);
color(law, cyan); hidden(law);
text(cap, (cx, h*0.74), ""); size(cap, 28); color(cap, dim); wrap(cap, w*0.86);
socials(me);
endcard(me, "title=Try_Manic cta=8gwifi.org/manic");
// ============================== SCRIPT ==============================
// 1 — HOOK.
mark("hook");
type(kicker, 1.2);
// 2 — THE PROBLEM.
show(cap, 0.3);
say(cap, "Bernoulli, 1696 — a bead slides from A down to B. Gravity only.");
par {
show(race.markA, 0.35); show(race.labelA, 0.25);
show(race.markB, 0.35); show(race.labelB, 0.25);
}
pulse(race.markA); pulse(race.markB);
wait(0.4);
// 3 — FOUR WIRES, each a height→speed color bar.
say(cap, "four wires — colored by depth, and depth IS speed");
stagger(0.12) {
draw(race.straight, 0.65);
draw(race.circle, 0.65);
draw(race.parabola, 0.65);
draw(race.cycloid, 0.85);
}
show(law, 0.45);
wait(0.5);
say(cap, "blue = potential · gold = all of it cashed into speed");
wait(0.7);
// 4 — THE RACE.
say(cap, "release all four at once …");
par {
show(race.bead_straight, 0.2);
show(race.bead_circle, 0.2);
show(race.bead_parabola, 0.2);
show(race.bead_cycloid, 0.2);
}
run(race, 5.5);
// 5 — THE WINNER.
recolor(cap, gold);
say(cap, "the CYCLOID wins — the straight line isn't even close");
flash(race.cycloid, magenta);
glow(race.bead_cycloid, 1.9);
pulse(race.bead_cycloid);
wait(0.7);
// 6 — THE SECRET: curvature gradient.
recolor(cap, dim);
say(cap, "its secret, colored by curvature — it bends hardest at the start");
par {
fade(race.straight, 0.4);
fade(race.circle, 0.4);
fade(race.parabola, 0.4);
fade(race.cycloid, 0.4);
fade(race.bead_straight, 0.35);
fade(race.bead_circle, 0.35);
fade(race.bead_parabola, 0.35);
fade(race.bead_cycloid, 0.35);
fade(law, 0.35);
}
show(ghost.cycloid, 0.15);
draw(ghost.cycloid, 1.3);
wait(0.35);
say(cap, "a near-vertical dive buys gold-speed immediately");
flash(ghost.cycloid, magenta);
wait(0.8);
// 7 — POINT + ENDCARD.
recolor(cap, cyan);
say(cap, "every color is computed — depth is the speedometer, the bend is the strategy");
wait(0.9);
mark("endcard");
par {
fade(cap, 0.4);
fade(me.footer, 0.4);
fade(kicker, 0.4);
fade(ghost.cycloid, 0.4);
fade(race.cycloid, 0.4);
fade(race.bead_cycloid, 0.4);
fade(race.markA, 0.3); fade(race.markB, 0.3);
fade(race.labelA, 0.3); fade(race.labelB, 0.3);
fade(stage, 0.4);
}
show(me.endcard, 0.6);
wait(1.6);
gradient-pendulum-shorts
Galileo’s chandelier as a Short told THROUGH a "speed" gradient: an RK4 pendulum swings,
then its arc is colored by true local speed — gold floods the bottom, blue pools at the tips.
The color IS the simulation (gradient(p.path, blue, cyan, gold, "speed")), not a painted
trail. Hook → reveal → energy law → prove-on-the-arc → end card.
// ============================================================================
// gradient-pendulum-shorts.manic — a 9:16 cinematic short told THROUGH a
// "speed" gradient. Sister piece to the brachistochrone pair
// (gradient-fastest-descent.manic / gradient-fastest-descent-shorts.manic).
// ----------------------------------------------------------------------------
// THE PROBLEM (Galileo, 1583): watching a chandelier swing in the Pisa
// cathedral, Galileo timed it with his own pulse. But WHERE along the arc is
// the bob fastest — and how would you ever see that?
//
// THE GRADIENT DOES THE PHYSICS: the swing arc is a pre-simulated RK4
// trajectory, uniformly sampled in time — so gradient(p.path, ..., "speed")
// colors it by its TRUE local speed. Gold floods the bottom (all kinetic),
// blue pools at the ends (the bob stands still for an instant). Nothing is
// keyframed; the color IS the simulation.
//
// Pattern mirrors creator-free-kicks.manic: hook → problem → reveal → law →
// prove on the painted arc → endcard.
// ============================================================================
title("Galileo's Chandelier");
canvas("9:16");
template("shorts");
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_math_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=gold footer=signature cta=Try_it_free safe=reels");
watermark(mk, (w*0.955-100, h*0.045+24), "Made With Manic");
// ---- backdrop: a radial pool of light under the chandelier ----
circle(halo, (cx, h*0.42), w*0.52);
filled(halo);
gradient(halo, panel, void, radial);
opacity(halo, 0.8);
// ---- the chandelier: a real RK4 pendulum, arc uniformly sampled in time ----
pendulum(p, (cx, h*0.26), 1, 55, w*0.36);
hidden(p.overlays);
untraced(p.path);
stroke(p.path, 7);
opacity(p.path, 0.95);
// the reveal: 3 stops by TRUE local speed — slowest first, fastest last
gradient(p.path, blue, cyan, gold, "speed");
color(p.bob, gold); glow(p.bob, 0.7);
stroke(p.rod, 3);
// ---- typography ----
text(kicker, (cx, h*0.11), "where is a pendulum FASTEST?");
size(kicker, 34); color(kicker, gold); bold(kicker); wrap(kicker, w*0.82);
untraced(kicker); cursor(kicker);
equation(law, (cx, h*0.64), `mgh \;=\; \tfrac{1}{2}mv^2`, 40);
color(law, cyan); hidden(law);
text(cap, (cx, h*0.76), ""); size(cap, 30); color(cap, dim); wrap(cap, w*0.84);
socials(me);
endcard(me, "title=Try_Manic cta=8gwifi.org/manic");
// ============================== SCRIPT ==============================
// 1 — HOOK.
mark("hook");
type(kicker, 1.2);
// 2 — GALILEO'S PROBLEM.
show(cap, 0.3);
say(cap, "1583 — Galileo times a swinging chandelier with his own pulse");
par {
show(p.pivot, 0.4);
show(p.rod, 0.4);
show(p.bob, 0.4);
}
pulse(p.bob);
wait(0.4);
// 3 — WATCH IT SWING. Your eye can't catch where it's quickest.
say(cap, "watch it — can you SEE where it moves fastest?");
swing(p, 4);
wait(0.3);
// 4 — THE REVEAL: paint the arc with its own speed.
say(cap, "freeze — and color the arc by its true speed");
draw(p.path, 1.8);
wait(0.5);
recolor(cap, gold);
say(cap, "gold floods the bottom — blue pools at the tips, where it stops for an instant");
flash(p.path, gold);
wait(0.8);
// 5 — WHY: energy. Height traded for speed, every swing, exactly.
recolor(cap, dim);
say(cap, "height falls, speed rises — energy just changes clothes");
show(law, 0.5);
wait(0.5);
rewrite(law, `v=\sqrt{2gh}`, 0.8, smooth);
say(cap, "at the lowest point ALL the height has become speed — that's the gold");
wait(0.8);
// 6 — PROVE IT: run the bob on its own painted speedometer.
recolor(cap, cyan);
say(cap, "the color is computed, not painted — a real RK4 swing on its own speedometer");
swing(p, 4);
wait(0.5);
// 7 — END CARD.
mark("endcard");
par {
fade(cap, 0.4);
fade(me.footer, 0.4);
fade(law, 0.4);
fade(kicker, 0.4);
fade(p.path, 0.4);
fade(p.rod, 0.4);
fade(p.bob, 0.4);
fade(p.pivot, 0.4);
fade(halo, 0.4);
}
show(me.endcard, 0.6);
wait(1.6);
creator-why-rockets-use-stages
Why rockets discard their own hardware, told as a vertical Creator Short. Three reusable physics SVG stages lift together, separate independently, and trigger three continuous velocity-curve segments. The curve never jumps at separation—the changing slope explains the real advantage: later engines accelerate less dead mass. Story → science → Manic CTA.
// WHY ROCKETS USE STAGES — a 9:16 creator short built from three independently
// movable physics SVGs. The velocity curve stays continuous at separation:
// dropping an empty stage changes the following acceleration, not past speed.
title("Why Do Rockets Use Stages?");
canvas("9:16");
template("shorts");
voice("gtts");
creator(me, "@anish2good name=Manic_Physics tagline=Physics_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=gold footer=signature cta=Try_it_free safe=reels");
socials(me);
watermark(mark, (w*0.82, h*0.91), "Made With Manic");
endcard(me, "title=Build_Physics_Stories cta=8gwifi.org/manic");
// ---- headline and story caption ----
text(kicker, (340, 122), "ROCKET SCIENCE · IN 30 SECONDS");
text(headline, (340, 184), "Why throw parts of a rocket away?");
text(caption, (cx, 1035), "At liftoff, every engine must accelerate the entire rocket.");
size(kicker, 21); bold(kicker); color(kicker, cyan); hidden(kicker);
size(headline, 38); bold(headline); wrap(headline, 590); hidden(headline);
size(caption, 25); color(caption, dim); wrap(caption, 850); hidden(caption);
sticky(kicker); sticky(headline); sticky(caption);
sticky(mark); sticky(me.footer); sticky(me.endcard);
// ---- one rocket, authored as three persistent SVG stages ----
let rx = 805;
svg(upper, (rx, 420), "asset:svg/physics/rocket-upper-stage.svg", 160);
svg(second, (rx, 570), "asset:svg/physics/rocket-second-stage.svg", 145);
svg(booster, (rx, 730), "asset:svg/physics/rocket-first-stage.svg", 155);
hidden(upper); hidden(second); hidden(booster);
z(upper, 8); z(second, 7); z(booster, 6);
polygon(flame1, (rx-30, 820), (rx, 925), (rx+30, 820));
gradient(flame1, gold, magenta, 90); filled(flame1); glow(flame1, 1.1); hidden(flame1);
polygon(flame2, (rx-23, 660), (rx, 740), (rx+23, 660));
gradient(flame2, cyan, magenta, 90); filled(flame2); glow(flame2, 0.9); hidden(flame2);
polygon(flame3, (rx-19, 520), (rx, 585), (rx+19, 520));
gradient(flame3, gold, cyan, 90); filled(flame3); glow(flame3, 0.9); hidden(flame3);
// ---- a truthful, continuous velocity story ----
let gx = 170;
let gy = 1510;
let sx = 120;
let sy = 72;
line(xaxis, (gx, gy), (930, gy)); color(xaxis, dim); stroke(xaxis, 3);
line(yaxis, (gx, gy), (gx, 1180)); color(yaxis, dim); stroke(yaxis, 3);
text(vlabel, (125, 1175), "velocity"); size(vlabel, 20); color(vlabel, dim);
text(tlabel, (940, 1542), "time"); size(tlabel, 20); color(tlabel, dim);
text(chartTitle, (cx, 1135), "VELOCITY KEEPS BUILDING");
size(chartTitle, 22); bold(chartTitle); color(chartTitle, fg);
plot(v1, (gx, gy), sx, sy, "0.18*x*x", (0,2));
plot(v2, (gx, gy), sx, sy, "0.72 + 0.36*(x-2) + 0.16*(x-2)*(x-2)", (2,4));
plot(v3, (gx, gy), sx, sy, "2.08 + 0.50*(x-4) + 0.20*(x-4)*(x-4)", (4,6));
color(v1, gold); color(v2, magenta); color(v3, cyan);
stroke(v1, 7); stroke(v2, 7); stroke(v3, 7);
untraced(v1); untraced(v2); untraced(v3);
line(sep1, (gx+2*sx, gy), (gx+2*sx, 1215)); dashed(sep1, 10, 9); color(sep1, dim);
line(sep2, (gx+4*sx, gy), (gx+4*sx, 1215)); dashed(sep2, 10, 9); color(sep2, dim);
text(sep1label, (gx+2*sx+55, 1240), "STAGE 1");
text(sep2label, (gx+4*sx+55, 1240), "STAGE 2");
size(sep1label, 18); size(sep2label, 18);
color(sep1label, gold); color(sep2label, magenta);
dot(join1, (gx+2*sx, gy-0.72*sy), 8); color(join1, gold);
dot(join2, (gx+4*sx, gy-2.08*sy), 8); color(join2, magenta);
hidden(xaxis); hidden(yaxis); hidden(vlabel); hidden(tlabel); hidden(chartTitle);
hidden(sep1); hidden(sep2); hidden(sep1label); hidden(sep2label);
hidden(join1); hidden(join2);
// The chart is a screen-space instrument. Above it, the active vehicle keeps
// climbing during every narration and separation beat.
tag(xaxis, flightHud); tag(yaxis, flightHud);
tag(vlabel, flightHud); tag(tlabel, flightHud); tag(chartTitle, flightHud);
tag(v1, flightHud); tag(v2, flightHud); tag(v3, flightHud);
tag(sep1, flightHud); tag(sep2, flightHud);
tag(sep1label, flightHud); tag(sep2label, flightHud);
tag(join1, flightHud); tag(join2, flightHud);
sticky(flightHud);
// ============================== STORY ==============================
step("hook") {
par {
show(kicker, 0.35);
show(headline, 0.50);
show(caption, 0.35);
stagger(0.10) {
show(upper, 0.35);
show(second, 0.35);
show(booster, 0.35);
}
speak("Why does a rocket throw away parts while it is still flying?");
}
}
wait(0.25);
step("first-stage") {
par {
show(xaxis, 0.30); show(yaxis, 0.30);
show(vlabel, 0.25); show(tlabel, 0.25); show(chartTitle, 0.30);
show(flame1, 0.20);
draw(v1, 4.0);
shift(upper, (0,-80), 4.0, smooth);
shift(second, (0,-80), 4.0, smooth);
shift(booster, (0,-80), 4.0, smooth);
shift(flame1, (0,-80), 4.0, smooth);
shift(flame2, (0,-80), 4.0, smooth);
shift(flame3, (0,-80), 4.0, smooth);
say(caption, "Stage 1 burns hard—but it also carries tanks that will soon be empty.", 0.40);
speak("Stage one must push every tank and engine above it.");
}
}
step("drop-empty-mass") {
par {
show(sep1, 0.25); show(sep1label, 0.25); show(join1, 0.20);
fade(flame1, 0.18);
shift(booster, (-120,260), 1.05, in);
rotate(booster, -18, 1.05, smooth);
seq { wait(0.62); fade(booster, 0.35); }
shift(upper, (0,-45), 3.6, smooth);
shift(second, (0,-45), 3.6, smooth);
shift(flame2, (0,-45), 3.6, smooth);
shift(flame3, (0,-45), 3.6, smooth);
say(caption, "Separation does not add speed. It removes dead mass.", 0.35);
speak("Separation adds no speed. It only removes empty mass.");
}
}
cue(whoosh);
step("second-stage") {
par {
show(flame2, 0.18);
draw(v2, 4.0);
shift(upper, (0,-70), 4.0, smooth);
shift(second, (0,-70), 4.0, smooth);
shift(flame2, (0,-70), 4.0, smooth);
shift(flame3, (0,-70), 4.0, smooth);
say(caption, "The lighter vehicle gains more velocity from its next engine.", 0.35);
speak("The next engine pushes less mass, so velocity builds faster.");
}
}
step("third-stage") {
par {
show(sep2, 0.25); show(sep2label, 0.25); show(join2, 0.20);
fade(flame2, 0.18);
shift(second, (125,235), 1.0, in);
rotate(second, 16, 1.0, smooth);
seq { wait(0.58); fade(second, 0.35); }
seq {
wait(0.28);
show(flame3, 0.18);
par {
draw(v3, 3.25);
shift(upper, (0,-50), 3.25, smooth);
shift(flame3, (0,-50), 3.25, smooth);
}
}
say(caption, "Each stage discards structure, then the next stage keeps accelerating.", 0.40);
speak("It repeats: less dead weight, more useful acceleration.");
}
}
cue(chime);
step("answer") {
par {
fade(kicker, 0.30);
pulse(upper, 0.65);
flash(v3, cyan);
say(headline, "Stages turn fuel into velocity—not into dead weight.", 0.45);
say(caption, "The curve never jumps. Its steeper climb is the advantage.", 0.40);
speak("Staging does not teleport a rocket faster. It stops wasting thrust on empty tanks.");
shift(upper, (0,-55), 6.4, smooth);
shift(flame3, (0,-55), 6.4, smooth);
}
}
step("creator-cta") {
par {
fade(kicker, 0.30); fade(headline, 0.30); fade(caption, 0.30);
fade(upper, 0.30); fade(flame3, 0.30);
fade(xaxis, 0.30); fade(yaxis, 0.30);
fade(vlabel, 0.30); fade(tlabel, 0.30); fade(chartTitle, 0.30);
fade(v1, 0.30); fade(v2, 0.30); fade(v3, 0.30);
fade(sep1, 0.30); fade(sep2, 0.30);
fade(sep1label, 0.30); fade(sep2label, 0.30);
fade(join1, 0.30); fade(join2, 0.30);
fade(me.footer, 0.30);
fade(mark, 0.30);
}
show(me.endcard, 0.60);
}
wait(1.6);
creator-how-rocket-launch-works
How a rocket launches, told entirely through a reusable cutaway SVG and Manic motion. The story reveals payload, fuel, oxidizer, combustion chamber, and nozzle; then hot gas streams downward while the vehicle moves upward. It closes the common misconception directly: rockets work in space because they carry their own oxidizer. Cutaway → reaction → thrust → CTA.
// HOW A ROCKET LAUNCH WORKS — a 9:16 cutaway story.
// The reusable SVG supplies the vehicle; Manic supplies the teaching:
// callouts, chamber reaction, directional exhaust, thrust, motion, and CTA.
title("How a Rocket Launch Works");
canvas("9:16");
template("shorts");
voice("gtts");
creator(me, "@anish2good name=Manic_Physics tagline=Physics_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=gold footer=signature cta=Try_it_free safe=reels");
socials(me);
watermark(mark, (w*0.82, h*0.91), "Made With Manic");
endcard(me, "title=Make_Science_Move cta=8gwifi.org/manic");
text(kicker, (cx, 112), "ROCKET PHYSICS · CUTAWAY");
text(headline, (cx, 174), "How does a rocket launch?");
text(caption, (cx, 1440), "A rocket carries two ingredients—not just fuel.");
size(kicker, 21); bold(kicker); color(kicker, cyan); hidden(kicker);
size(headline, 40); bold(headline); wrap(headline, 850); hidden(headline);
size(caption, 27); color(caption, dim); wrap(caption, 880); hidden(caption);
// One importer-safe cutaway asset. Its internal sections remain crisp at any size.
let rx = 540;
let ry = 760;
svg(rocket, (rx, ry), "asset:svg/physics/rocket-cutaway.svg", 300);
hidden(rocket); z(rocket, 5);
// Screen-authored highlights make each internal region independently teachable.
polygon(payloadHi, (480, 430), (540, 395), (600, 430), (600, 550), (480, 550));
rect(fuelHi, (540, 650), 120, 155);
rect(oxidizerHi, (540, 815), 120, 140);
polygon(chamberHi, (500, 900), (580, 900), (570, 955), (540, 980), (510, 955));
polygon(nozzleHi, (515, 975), (565, 975), (590, 1115), (490, 1115));
outlined(payloadHi); outlined(fuelHi); outlined(oxidizerHi); outlined(chamberHi); outlined(nozzleHi);
outline(payloadHi, cyan); outline(fuelHi, gold); outline(oxidizerHi, blue);
outline(chamberHi, magenta); outline(nozzleHi, fg);
stroke(payloadHi, 5); stroke(fuelHi, 5); stroke(oxidizerHi, 5); stroke(chamberHi, 5); stroke(nozzleHi, 5);
opacity(payloadHi, 0.9); opacity(fuelHi, 0.9); opacity(oxidizerHi, 0.9); opacity(chamberHi, 0.9); opacity(nozzleHi, 0.9);
hidden(payloadHi); hidden(fuelHi); hidden(oxidizerHi); hidden(chamberHi); hidden(nozzleHi);
// Clean callouts; the labels stay outside the vehicle silhouette.
line(payloadLead, (605, 470), (770, 420));
line(fuelLead, (600, 630), (790, 590));
line(oxidizerLead, (480, 805), (285, 780));
line(chamberLead, (500, 930), (280, 970));
line(nozzleLead, (575, 1045), (790, 1090));
color(payloadLead, cyan); color(fuelLead, gold); color(oxidizerLead, blue);
color(chamberLead, magenta); color(nozzleLead, fg);
stroke(payloadLead, 3); stroke(fuelLead, 3); stroke(oxidizerLead, 3); stroke(chamberLead, 3); stroke(nozzleLead, 3);
text(payloadLabel, (845, 395), "PAYLOAD");
text(fuelLabel, (855, 565), "FUEL");
text(oxidizerLabel, (195, 755), "OXIDIZER");
text(chamberLabel, (180, 950), "BURN");
text(nozzleLabel, (850, 1070), "NOZZLE");
size(payloadLabel, 22); size(fuelLabel, 22); size(oxidizerLabel, 22); size(chamberLabel, 22); size(nozzleLabel, 22);
bold(payloadLabel); bold(fuelLabel); bold(oxidizerLabel); bold(chamberLabel); bold(nozzleLabel);
color(payloadLabel, cyan); color(fuelLabel, gold); color(oxidizerLabel, blue);
color(chamberLabel, magenta); color(nozzleLabel, fg);
hidden(payloadLead); hidden(fuelLead); hidden(oxidizerLead); hidden(chamberLead); hidden(nozzleLead);
hidden(payloadLabel); hidden(fuelLabel); hidden(oxidizerLabel); hidden(chamberLabel); hidden(nozzleLabel);
// Reaction and force story.
equation(reaction, (cx, 1195), `\text{fuel}+\text{oxidizer}\longrightarrow\text{hot gas}`);
size(reaction, 34); hidden(reaction);
arrow(gasDown, (225, 1070), (225, 1300));
arrow(thrustUp, (850, 1120), (850, 880));
color(gasDown, magenta); color(thrustUp, cyan);
stroke(gasDown, 8); stroke(thrustUp, 8);
hidden(gasDown); hidden(thrustUp);
text(gasLabel, (170, 1320), "EXHAUST ↓");
text(thrustLabel, (930, 900), "THRUST ↑");
size(gasLabel, 22); size(thrustLabel, 22); bold(gasLabel); bold(thrustLabel);
color(gasLabel, magenta); color(thrustLabel, cyan);
hidden(gasLabel); hidden(thrustLabel);
polygon(flame, (500, 1090), (540, 1260), (580, 1090));
gradient(flame, gold, magenta, 90); filled(flame); glow(flame, 1.1); hidden(flame); z(flame, 4);
circle(exhaustSeed, (540, 1180), 12); opacity(exhaustSeed, 0); hidden(exhaustSeed);
particles(exhaust, exhaustSeed, 28, 6, 41, "random");
color(exhaust, gold); glow(exhaust, 0.8); hidden(exhaust);
line(exhaustPath, (540, 1170), (540, 1330)); opacity(exhaustPath, 0);
tag(rocket, vehicle);
tag(payloadHi, vehicle); tag(fuelHi, vehicle); tag(oxidizerHi, vehicle);
tag(chamberHi, vehicle); tag(nozzleHi, vehicle); tag(flame, vehicle);
// Creator chrome remains screen-safe while the vehicle moves.
sticky(kicker); sticky(headline); sticky(caption); sticky(mark);
sticky(me.footer); sticky(me.endcard);
step("hook") {
par {
show(kicker, 0.35);
show(headline, 0.50);
show(caption, 0.35);
show(rocket, 0.70);
speak("A rocket does not push against the ground. So what actually makes it rise?");
}
}
wait(0.35);
step("inside-the-rocket") {
par {
stagger(0.16) {
par { show(payloadHi, 0.25); show(payloadLead, 0.25); show(payloadLabel, 0.25); }
par { show(fuelHi, 0.25); show(fuelLead, 0.25); show(fuelLabel, 0.25); }
par { show(oxidizerHi, 0.25); show(oxidizerLead, 0.25); show(oxidizerLabel, 0.25); }
}
say(caption, "Fuel stores energy. Oxidizer supplies the oxygen needed to release it.", 0.40);
speak("Inside are the payload, fuel, and oxidizer. The oxidizer is why a rocket can burn in space.");
}
}
wait(0.30);
step("combustion") {
par {
show(chamberHi, 0.25); show(chamberLead, 0.25); show(chamberLabel, 0.25);
show(reaction, 0.50);
flash(chamberHi, magenta);
pulse(chamberHi, 0.80);
say(caption, "In the chamber, fuel and oxidizer become extremely hot, high-pressure gas.", 0.40);
speak("They burn together in the combustion chamber, creating hot, high pressure gas.");
}
}
cue(pop);
wait(0.25);
step("shape-the-exhaust") {
par {
fade(reaction, 0.25);
show(nozzleHi, 0.25); show(nozzleLead, 0.25); show(nozzleLabel, 0.25);
show(flame, 0.22);
show(exhaust, 0.15);
stream(exhaust, exhaustPath, 3.2, 34, smooth);
show(gasDown, 0.40); show(gasLabel, 0.30);
say(caption, "The nozzle accelerates that gas downward into a fast exhaust jet.", 0.40);
speak("The nozzle expands and accelerates the gas downward.");
}
}
cue(whoosh);
step("equal-and-opposite") {
par {
fade(payloadHi, 0.25); fade(fuelHi, 0.25); fade(oxidizerHi, 0.25);
fade(chamberHi, 0.25); fade(nozzleHi, 0.25);
fade(payloadLead, 0.25); fade(fuelLead, 0.25); fade(oxidizerLead, 0.25);
fade(chamberLead, 0.25); fade(nozzleLead, 0.25);
fade(payloadLabel, 0.25); fade(fuelLabel, 0.25); fade(oxidizerLabel, 0.25);
fade(chamberLabel, 0.25); fade(nozzleLabel, 0.25);
fade(reaction, 0.25); fade(exhaust, 0.25);
show(thrustUp, 0.40); show(thrustLabel, 0.30);
flash(thrustUp, cyan);
say(headline, "Gas goes down. The rocket goes up.", 0.45);
say(caption, "Newton's third law turns downward exhaust momentum into upward thrust.", 0.40);
speak("Gas goes down. An equal and opposite force pushes the rocket up.");
}
}
wait(0.30);
step("launch") {
par {
fade(kicker, 0.25); fade(headline, 0.25);
shift(vehicle, (0,-250), 4.0, smooth);
shift(thrustUp, (0,-180), 4.0, smooth);
shift(thrustLabel, (0,-180), 4.0, smooth);
stream(exhaust, exhaustPath, 4.0, 38, smooth);
fade(payloadLead, 0.25); fade(fuelLead, 0.25); fade(oxidizerLead, 0.25);
fade(chamberLead, 0.25); fade(nozzleLead, 0.25);
fade(payloadLabel, 0.25); fade(fuelLabel, 0.25); fade(oxidizerLabel, 0.25);
fade(chamberLabel, 0.25); fade(nozzleLabel, 0.25);
fade(reaction, 0.25); fade(gasDown, 0.25); fade(gasLabel, 0.25);
say(caption, "A rocket carries both sides of the reaction, so the process continues above the atmosphere.", 0.45);
speak("Because it carries its own oxidizer, the same thrust cycle keeps working in space.");
}
}
cue(chime);
wait(0.55);
step("creator-cta") {
par {
fade(kicker, 0.30); fade(headline, 0.30); fade(caption, 0.30);
fade(vehicle, 0.30); fade(exhaust, 0.30);
fade(thrustUp, 0.30); fade(thrustLabel, 0.30);
fade(me.footer, 0.30); fade(mark, 0.30);
}
show(me.endcard, 0.60);
}
wait(1.6);
creator-geometry-morph-promo
One persistent twelve-vertex actor becomes a triangle, square, diamond, hexagon, star,
hourglass, arrow, heart, bolt and crown. Equal topology makes the silhouettes interpolate
continuously while become, spin, say and pulse tell a fast geometry ident—then the
restrained Creator endcard closes the promo.
// Creator promo: one persistent twelve-vertex actor becomes ten readable
// silhouettes. Equal point counts make every transition a true geometric
// interpolation rather than a cut between unrelated drawings.
title("Geometry Never Sits Still");
canvas("9:16");
template("black");
creator(me, "@anish2good name=Manic_Geometry tagline=Geometry_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Make_geometry_move safe=clean");
socials(me);
watermark(mark, (w*0.18, h*0.08), "Made With Manic");
endcard(me, "title=Give_Your_Ideas_Motion cta=8gwifi.org/manic");
let u = (w+h-abs(w-h))/1080;
let ox = cx;
let oy = h*0.47;
let r = 205*u;
text(kicker, (cx,h*0.12), "MANIC · GEOMETRY IN MOTION");
text(head, (cx,h*0.21), "One shape. Endless ideas.");
text(shapeName, (cx,h*0.70), "DODECAGON");
text(caption, (cx,h*0.84), "The identity stays. Only the geometry changes.");
size(kicker,20*u); bold(kicker); color(kicker,cyan);
size(head,31*u); bold(head); wrap(head,w*0.78);
size(shapeName,23*u); bold(shapeName); color(shapeName,gold);
size(caption,20*u); bold(caption); color(caption,dim); wrap(caption,w*0.72);
// Twelve points in every blueprint: direct, continuous vertex motion.
polygon(hero,
(ox,oy-r), (ox+r*0.50,oy-r*0.866), (ox+r*0.866,oy-r*0.50),
(ox+r,oy), (ox+r*0.866,oy+r*0.50), (ox+r*0.50,oy+r*0.866),
(ox,oy+r), (ox-r*0.50,oy+r*0.866), (ox-r*0.866,oy+r*0.50),
(ox-r,oy), (ox-r*0.866,oy-r*0.50), (ox-r*0.50,oy-r*0.866));
gradient(hero, cyan, magenta, gold, 35); stroke(hero,4); glow(hero,0.22);
// Four copies of each corner preserve the twelve-point topology.
polygon(triangleForm,
(ox,oy-r), (ox,oy-r), (ox,oy-r), (ox,oy-r),
(ox+r*0.92,oy+r*0.72), (ox+r*0.92,oy+r*0.72), (ox+r*0.92,oy+r*0.72), (ox+r*0.92,oy+r*0.72),
(ox-r*0.92,oy+r*0.72), (ox-r*0.92,oy+r*0.72), (ox-r*0.92,oy+r*0.72), (ox-r*0.92,oy+r*0.72));
color(triangleForm,magenta); stroke(triangleForm,4); glow(triangleForm,0.18); hidden(triangleForm);
polygon(squareForm,
(ox-r*0.82,oy-r*0.82), (ox-r*0.82,oy-r*0.82), (ox-r*0.82,oy-r*0.82),
(ox+r*0.82,oy-r*0.82), (ox+r*0.82,oy-r*0.82), (ox+r*0.82,oy-r*0.82),
(ox+r*0.82,oy+r*0.82), (ox+r*0.82,oy+r*0.82), (ox+r*0.82,oy+r*0.82),
(ox-r*0.82,oy+r*0.82), (ox-r*0.82,oy+r*0.82), (ox-r*0.82,oy+r*0.82));
color(squareForm,blue); stroke(squareForm,4); glow(squareForm,0.18); hidden(squareForm);
polygon(diamondForm,
(ox,oy-r), (ox,oy-r), (ox,oy-r),
(ox+r,oy), (ox+r,oy), (ox+r,oy),
(ox,oy+r), (ox,oy+r), (ox,oy+r),
(ox-r,oy), (ox-r,oy), (ox-r,oy));
color(diamondForm,gold); stroke(diamondForm,4); glow(diamondForm,0.18); hidden(diamondForm);
polygon(hexagonForm,
(ox-r*0.52,oy-r*0.90), (ox-r*0.52,oy-r*0.90),
(ox+r*0.52,oy-r*0.90), (ox+r*0.52,oy-r*0.90),
(ox+r,oy), (ox+r,oy),
(ox+r*0.52,oy+r*0.90), (ox+r*0.52,oy+r*0.90),
(ox-r*0.52,oy+r*0.90), (ox-r*0.52,oy+r*0.90),
(ox-r,oy), (ox-r,oy));
color(hexagonForm,cyan); stroke(hexagonForm,4); glow(hexagonForm,0.18); hidden(hexagonForm);
polygon(starForm,
(ox,oy-r), (ox+r*0.23,oy-r*0.40), (ox+r*0.87,oy-r*0.50),
(ox+r*0.46,oy), (ox+r*0.87,oy+r*0.50), (ox+r*0.23,oy+r*0.40),
(ox,oy+r), (ox-r*0.23,oy+r*0.40), (ox-r*0.87,oy+r*0.50),
(ox-r*0.46,oy), (ox-r*0.87,oy-r*0.50), (ox-r*0.23,oy-r*0.40));
color(starForm,magenta); stroke(starForm,4); glow(starForm,0.22); hidden(starForm);
polygon(hourglassForm,
(ox-r*0.85,oy-r*0.90), (ox,oy-r*0.55), (ox+r*0.85,oy-r*0.90),
(ox+r*0.48,oy-r*0.20), (ox+r*0.16,oy), (ox+r*0.48,oy+r*0.20),
(ox+r*0.85,oy+r*0.90), (ox,oy+r*0.55), (ox-r*0.85,oy+r*0.90),
(ox-r*0.48,oy+r*0.20), (ox-r*0.16,oy), (ox-r*0.48,oy-r*0.20));
color(hourglassForm,violet); stroke(hourglassForm,4); glow(hourglassForm,0.18); hidden(hourglassForm);
polygon(arrowForm,
(ox-r,oy-r*0.28), (ox+r*0.18,oy-r*0.28), (ox+r*0.18,oy-r*0.62),
(ox+r,oy), (ox+r,oy), (ox+r,oy),
(ox+r*0.18,oy+r*0.62), (ox+r*0.18,oy+r*0.28), (ox-r,oy+r*0.28),
(ox-r,oy+r*0.10), (ox-r,oy), (ox-r,oy-r*0.10));
color(arrowForm,lime); stroke(arrowForm,4); glow(arrowForm,0.18); hidden(arrowForm);
polygon(heartForm,
(ox,oy+r), (ox-r*0.82,oy+r*0.15), (ox-r,oy-r*0.38),
(ox-r*0.70,oy-r*0.82), (ox-r*0.24,oy-r*0.78), (ox,oy-r*0.42),
(ox+r*0.24,oy-r*0.78), (ox+r*0.70,oy-r*0.82), (ox+r,oy-r*0.38),
(ox+r*0.82,oy+r*0.15), (ox+r*0.38,oy+r*0.62), (ox,oy+r));
color(heartForm,coral); stroke(heartForm,4); glow(heartForm,0.22); hidden(heartForm);
polygon(boltForm,
(ox+r*0.18,oy-r), (ox-r*0.62,oy+r*0.08), (ox-r*0.14,oy+r*0.02),
(ox-r*0.48,oy+r), (ox+r*0.62,oy-r*0.24), (ox+r*0.16,oy-r*0.18),
(ox+r*0.48,oy-r*0.76), (ox+r*0.34,oy-r*0.54), (ox+r*0.28,oy-r*0.36),
(ox+r*0.24,oy-r*0.20), (ox+r*0.21,oy-r*0.08), (ox+r*0.18,oy-r));
color(boltForm,gold); stroke(boltForm,4); glow(boltForm,0.22); hidden(boltForm);
polygon(crownForm,
(ox-r,oy+r*0.60), (ox-r*0.92,oy-r*0.45), (ox-r*0.46,oy+r*0.05),
(ox-r*0.30,oy-r), (ox,oy-r*0.18), (ox+r*0.30,oy-r),
(ox+r*0.46,oy+r*0.05), (ox+r*0.92,oy-r*0.45), (ox+r,oy+r*0.60),
(ox+r*0.42,oy+r*0.76), (ox-r*0.42,oy+r*0.76), (ox-r,oy+r*0.60));
color(crownForm,cyan); stroke(crownForm,4); glow(crownForm,0.22); hidden(crownForm);
hidden(kicker); hidden(head); hidden(shapeName); hidden(caption); hidden(hero);
step("one persistent shape") {
par {
show(kicker,0.30); show(head,0.45); show(shapeName,0.30);
show(caption,0.35); show(hero,0.55);
}
pulse(hero,0.55);
}
wait(0.30);
step("geometry keeps becoming") {
seq {
par { become(hero,triangleForm,0.62,smooth); say(shapeName,"TRIANGLE",0.22); }
par { become(hero,squareForm,0.62,smooth); say(shapeName,"SQUARE",0.22); spin(hero,45,0.62,smooth); }
par { become(hero,diamondForm,0.58,smooth); say(shapeName,"DIAMOND",0.22); }
par { become(hero,hexagonForm,0.62,smooth); say(shapeName,"HEXAGON",0.22); }
par { become(hero,starForm,0.70,smooth); say(shapeName,"STAR",0.22); spin(hero,30,0.70,smooth); }
par { become(hero,hourglassForm,0.66,smooth); say(shapeName,"HOURGLASS",0.22); }
par { become(hero,arrowForm,0.66,smooth); say(shapeName,"DIRECTION",0.22); }
par { become(hero,heartForm,0.72,smooth); say(shapeName,"HEART",0.22); }
par { become(hero,boltForm,0.66,smooth); say(shapeName,"ENERGY",0.22); }
par { become(hero,crownForm,0.72,smooth); say(shapeName,"STORY",0.22); }
}
}
wait(0.45);
step("the object never resets") {
par {
pulse(hero,0.80);
say(head,"Describe change—not keyframes.",0.40,smooth);
say(caption,"become · spin · recolor · pulse — one actor, one continuous idea.",0.45,smooth);
}
}
wait(1.00);
step("creator call to action") {
par {
fade(kicker,0.30); fade(head,0.30); fade(shapeName,0.30);
fade(caption,0.30); fade(hero,0.40); fade(me.footer,0.30);
show(me.endcard,0.60);
}
}
wait(2.00);
creator-geometry-language-showcase
How much olympiad geometry lives in one triangle? Four coloured midpoint regions prove an exact quarter-area identity; three altitude feet join the three side midpoints on the dashed nine-point circle; centroid, circumcenter, incenter, orthocenter and Euler line complete the story. Moving one free vertex proves the derived intersections and circles remain live.
// Creator showcase: one triangle grows from foundations into two olympiad
// results—four equal midpoint areas and the nine-point circle. Every derived
// construction remains live when vertex C moves.
title("How Much Geometry Lives in One Triangle?");
canvas("9:16");
template("black");
creator(me, "@anish2good name=Manic_Geometry tagline=Constructions_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Animate_the_proof safe=clean");
socials(me);
watermark(mark, (w*0.18, h*0.09), "Made With Manic");
endcard(me, "title=Build_The_Explanation cta=8gwifi.org/manic");
let u = (w+h-abs(w-h))/1080;
let g = (w+h-abs(w-h))*0.21;
let gy = h*0.56;
text(kicker,(cx,h*0.09),"MANIC · LIVE GEOMETRY");
text(head,(cx,h*0.18),"How much lives inside one triangle?");
text(counter,(cx,h*0.28),"01 · THREE FREE POINTS");
text(caption,(cx,h*0.755),"Begin with A, B and C.");
size(kicker,20*u); bold(kicker); color(kicker,cyan);
size(head,29*u); bold(head); wrap(head,w*0.80);
size(counter,20*u); bold(counter); color(counter,gold);
size(caption,21*u); bold(caption); color(caption,dim); wrap(caption,w*0.76);
// Free vertices: every construction below reads these three points.
point(A,(cx-g,gy+g*0.60),"A");
point(B,(cx+g,gy+g*0.60),"B");
point(C,(cx+g*0.12,gy-g),"C");
color(A,cyan); color(B,cyan); color(C,cyan);
hidden(A); hidden(B); hidden(C);
// 1–3: dynamic sides.
segment(ab,A,B); segment(bc,B,C); segment(ca,C,A);
color(ab,fg); color(bc,fg); color(ca,fg);
stroke(ab,3); stroke(bc,3); stroke(ca,3);
untraced(ab); untraced(bc); untraced(ca);
// 4: an angle mark.
anglemark(angleC,A,C,B); color(angleC,gold); stroke(angleC,4); untraced(angleC);
// 5–9: midpoint, median and centroid system.
midpoint(mAB,A,B); midpoint(mBC,B,C); midpoint(mCA,C,A);
color(mAB,lime); color(mBC,lime); color(mCA,lime);
label(mAB,"D",(-22*u,18*u)); label(mBC,"E",(20*u,14*u)); label(mCA,"F",(-22*u,14*u));
hidden(mAB); hidden(mBC); hidden(mCA);
// The midpoint triangle partitions ABC into four congruent, equal-area
// triangles. These fills are exact for the authored starting triangle and are
// intentionally dismissed before the later live vertex drag.
polygon(shadeA,
(cx-g,gy+g*0.60), (cx,gy+g*0.60), (cx-g*0.44,gy-g*0.20));
polygon(shadeB,
(cx+g,gy+g*0.60), (cx+g*0.56,gy-g*0.20), (cx,gy+g*0.60));
polygon(shadeC,
(cx+g*0.12,gy-g), (cx-g*0.44,gy-g*0.20), (cx+g*0.56,gy-g*0.20));
polygon(shadeMid,
(cx,gy+g*0.60), (cx+g*0.56,gy-g*0.20), (cx-g*0.44,gy-g*0.20));
filled(shadeA); color(shadeA,cyan); opacity(shadeA,0.24); hidden(shadeA);
filled(shadeB); color(shadeB,magenta); opacity(shadeB,0.24); hidden(shadeB);
filled(shadeC); color(shadeC,gold); opacity(shadeC,0.24); hidden(shadeC);
filled(shadeMid); color(shadeMid,violet); opacity(shadeMid,0.30); hidden(shadeMid);
segment(de,mAB,mBC); segment(ef,mBC,mCA); segment(fd,mCA,mAB);
color(de,gold); color(ef,gold); color(fd,gold);
stroke(de,3); stroke(ef,3); stroke(fd,3);
untraced(de); untraced(ef); untraced(fd);
equation(areaLaw,(cx,h*0.78),
`K_{ADF}=K_{BDE}=K_{CEF}=K_{DEF}`,
20*u);
equation(areaQuarter,(cx,h*0.86),`K=\frac14K_{ABC}`,22*u);
color(areaLaw,fg); color(areaQuarter,gold);
hidden(areaLaw); hidden(areaQuarter);
segment(medA,A,mBC); segment(medB,B,mCA); segment(medC,C,mAB);
color(medA,lime); color(medB,lime); color(medC,lime);
dashed(medA,10,8); dashed(medB,10,8); dashed(medC,10,8);
untraced(medA); untraced(medB); untraced(medC);
centroid(G,A,B,C); color(G,lime); label(G,"G",(22*u,-15*u)); hidden(G);
// 10–13: circumcenter/circumcircle and incenter/incircle.
circumcenter(O,A,B,C); color(O,magenta); label(O,"O",(-28*u,-15*u)); hidden(O);
circumcircle(omega,A,B,C); outline(omega,magenta); color(omega,panel);
stroke(omega,3); untraced(omega);
incenter(I,A,B,C); color(I,gold); label(I,"I",(22*u,15*u)); hidden(I);
incircle(gamma,A,B,C); outline(gamma,gold); color(gamma,panel);
stroke(gamma,3); untraced(gamma);
// 14–22: the three side intersections of the altitudes, their right-angle
// marks, the orthocenter, nine-point circle and Euler line.
foot(X,A,B,C); foot(Y,B,C,A); foot(Z,C,A,B);
color(X,blue); color(Y,blue); color(Z,blue);
label(X,"X",(18*u,15*u)); label(Y,"Y",(-20*u,15*u)); label(Z,"Z",(18*u,18*u));
hidden(X); hidden(Y); hidden(Z);
segment(altA,A,X); segment(altB,B,Y); segment(altC,C,Z);
color(altA,blue); color(altB,blue); color(altC,blue);
stroke(altA,3); stroke(altB,3); stroke(altC,3);
untraced(altA); untraced(altB); untraced(altC);
rightangle(squareX,A,X,B); rightangle(squareY,B,Y,C); rightangle(squareZ,C,Z,A);
color(squareX,blue); color(squareY,blue); color(squareZ,blue);
stroke(squareX,3); stroke(squareY,3); stroke(squareZ,3);
untraced(squareX); untraced(squareY); untraced(squareZ);
orthocenter(H,A,B,C); color(H,cyan); label(H,"H",(-28*u,15*u)); hidden(H);
circumcircle(nine,mAB,mBC,mCA); outline(nine,violet); color(nine,panel);
stroke(nine,4); dashed(nine,12,8); untraced(nine);
segment(euler,O,H); color(euler,cyan); stroke(euler,4); untraced(euler);
hidden(kicker); hidden(head); hidden(counter); hidden(caption);
step("three points become a question") {
par {
show(kicker,0.30); show(head,0.45); show(counter,0.30); show(caption,0.35);
}
stagger(0.16) { show(A,0.24); show(B,0.24); show(C,0.24); }
par { draw(ab,0.50,smooth); draw(bc,0.50,smooth); draw(ca,0.50,smooth); }
draw(angleC,0.42,smooth);
}
wait(0.35);
step("midpoints create four equal areas") {
seq {
par {
say(counter,"02 · OLYMPIAD AREA PARTITION",0.30);
say(caption,"Join the three side midpoints D, E and F. What happens to the four areas?",0.45,smooth);
}
stagger(0.13) { show(mAB,0.22); show(mBC,0.22); show(mCA,0.22); }
par { draw(de,0.55,smooth); draw(ef,0.55,smooth); draw(fd,0.55,smooth); }
stagger(0.10) {
show(shadeA,0.28); show(shadeB,0.28); show(shadeC,0.28); show(shadeMid,0.28);
}
par {
move(caption,(cx,h*0.70),0.35,smooth);
show(areaLaw,0.40); show(areaQuarter,0.40);
}
par {
pulse(shadeMid,0.60);
say(caption,"Each small triangle has one quarter of the original area.",0.40,smooth);
}
}
}
wait(0.60);
step("medians reveal the balance point") {
seq {
par {
fade(shadeA,0.28); fade(shadeB,0.28); fade(shadeC,0.28); fade(shadeMid,0.28);
fade(areaLaw,0.28); fade(areaQuarter,0.28);
fade(de,0.28); fade(ef,0.28); fade(fd,0.28);
move(caption,(cx,h*0.755),0.30,smooth);
say(counter,"03 · MEDIANS → CENTROID",0.30);
say(caption,"Now connect each vertex to the opposite midpoint. The three medians concur.",0.45,smooth);
}
par { draw(medA,0.62,smooth); draw(medB,0.62,smooth); draw(medC,0.62,smooth); }
show(G,0.28); pulse(G,0.60);
}
}
wait(0.45);
step("two circles find two centers") {
seq {
par {
fade(medA,0.30); fade(medB,0.30); fade(medC,0.30);
fade(mAB,0.30); fade(mBC,0.30); fade(mCA,0.30);
say(counter,"04 · TWO CENTERS, TWO CIRCLES",0.30);
say(caption,"O is equally far from the vertices. I is equally far from the sides.",0.45,smooth);
}
par { show(O,0.25); draw(omega,0.85,smooth); }
par { show(I,0.25); draw(gamma,0.75,smooth); }
par { pulse(O,0.55); pulse(I,0.55); }
}
}
wait(0.55);
step("six intersections reveal the nine point circle") {
seq {
par {
fade(omega,0.28); fade(O,0.28); fade(gamma,0.28); fade(I,0.28);
say(counter,"05 · SIX POINTS, ONE CIRCLE",0.30);
say(caption,"The three midpoints and three altitude feet all lie on one nine-point circle.",0.48,smooth);
}
stagger(0.12) { show(mAB,0.22); show(mBC,0.22); show(mCA,0.22); }
stagger(0.12) { show(X,0.22); show(Y,0.22); show(Z,0.22); }
par {
draw(altA,0.62,smooth); draw(altB,0.62,smooth); draw(altC,0.62,smooth);
draw(squareX,0.42,smooth); draw(squareY,0.42,smooth); draw(squareZ,0.42,smooth);
}
show(H,0.28); pulse(H,0.60);
draw(nine,1.05,smooth);
stagger(0.10) {
pulse(mAB,0.38); pulse(mBC,0.38); pulse(mCA,0.38);
pulse(X,0.38); pulse(Y,0.38); pulse(Z,0.38);
}
}
}
wait(0.70);
step("the Euler line connects the centers") {
seq {
par {
fade(nine,0.28);
fade(mAB,0.28); fade(mBC,0.28); fade(mCA,0.28);
fade(X,0.28); fade(Y,0.28); fade(Z,0.28);
fade(altA,0.28); fade(altB,0.28); fade(altC,0.28);
fade(squareX,0.28); fade(squareY,0.28); fade(squareZ,0.28);
say(counter,"06 · O, G AND H ALIGN",0.30);
say(caption,"Circumcenter, centroid and orthocenter lie on the Euler line.",0.42,smooth);
}
par { show(O,0.20); show(G,0.20); }
draw(euler,0.90,smooth);
stagger(0.16) { pulse(O,0.50); pulse(G,0.50); pulse(H,0.50); }
}
}
wait(0.55);
step("prove every construction is alive") {
seq {
par {
say(counter,"07 · MOVE ONE POINT",0.30);
say(caption,"Move C. Every midpoint, intersection, circle, center and line recomputes.",0.48,smooth);
recolor(C,gold,0.35);
}
par {
show(nine,0.25); show(mAB,0.20); show(mBC,0.20); show(mCA,0.20);
show(X,0.20); show(Y,0.20); show(Z,0.20);
}
move(C,(cx+g*0.60,gy-g*0.82),1.15,smooth);
move(C,(cx-g*0.48,gy-g*0.86),1.15,smooth);
move(C,(cx+g*0.12,gy-g),0.90,smooth);
pulse(C,0.55);
}
}
wait(0.80);
step("geometry becomes a language") {
par {
say(head,"Name the relationships. Manic makes them move.",0.45,smooth);
say(caption,"construct · intersect · shade · prove · deform — one continuous olympiad story",0.45,smooth);
pulse(euler,0.75);
}
}
wait(1.10);
step("creator call to action") {
par {
fade(kicker,0.30); fade(head,0.30); fade(counter,0.30); fade(caption,0.30);
fade(A,0.30); fade(B,0.30); fade(C,0.30);
fade(ab,0.30); fade(bc,0.30); fade(ca,0.30); fade(angleC,0.30);
fade(omega,0.35); fade(nine,0.35);
fade(mAB,0.30); fade(mBC,0.30); fade(mCA,0.30);
fade(X,0.30); fade(Y,0.30); fade(Z,0.30);
fade(O,0.30); fade(G,0.30); fade(H,0.30); fade(euler,0.35);
fade(me.footer,0.30); show(me.endcard,0.60);
}
}
wait(2.00);
creator-hilbert3-spatial-index
A cinematic use for hilbert3, unrelated to recursive cube filling: a scan plane resolves 64
translucent voxels, three red corner-to-corner jumps expose a poor address order, and one
fixed Hilbert route restores locality. Scanner, cache window, counter and orbiting camera
then travel together before H(x,y,z)=d lands the spatial-indexing payoff.
// A different use of hilbert3: spatial indexing, not curve refinement.
// Sixty-four 3D voxels receive one continuous address order. A moving cache
// window shows why axis-adjacent visits preserve useful spatial locality.
title("A 3D Volume, One Sortable Address");
canvas("9:16");
template("black");
creator(me,"@anish2good name=Manic_Data tagline=Spatial_ideas_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=gold footer=compact cta=Animate_the_algorithm safe=clean");
socials(me);
watermark(mark,(w*0.18,h*0.08),"Made With Manic");
endcard(me,"title=Turn_Data_Into_A_Story cta=8gwifi.org/manic");
let u=(w+h-abs(w-h))/1080;
text(kicker,(cx,h*0.14),"MANIC · SPATIAL INDEXING");
text(head,(cx,h*0.24),"How do you sort a 3D world?");
text(caption,(cx,h*0.82),"First, capture a world made from 64 separate voxels.");
counter(address,(cx,h*0.72),0,0,"VOXEL "," / 63");
equation(keyLaw,(cx,h*0.71),`H(x,y,z)=d`,30*u);
text(volumeReadout,(cx,h*0.72),"64 VOXELS · 3 COORDINATES");
text(jumpWarning,(cx,h*0.665),"NON-LOCAL ORDER · DISTANCE SPIKES");
text(localResult,(cx,h*0.72),"63 / 63 STEPS STAY FACE-ADJACENT");
text(probeLabel,(0,0),"ACTIVE CACHE");
size(kicker,20*u); bold(kicker); color(kicker,cyan);
size(head,31*u); bold(head); wrap(head,w*0.80);
size(caption,21*u); bold(caption); color(caption,dim); wrap(caption,w*0.76);
size(address,23*u); bold(address); color(address,gold);
size(volumeReadout,21*u); bold(volumeReadout); color(volumeReadout,dim);
size(jumpWarning,21*u); bold(jumpWarning); color(jumpWarning,red);
size(localResult,21*u); bold(localResult); color(localResult,lime);
size(probeLabel,17*u); bold(probeLabel); color(probeLabel,gold);
camera3((16,-18,14),(0,0,0),42);
// A 4×4×4 voxel volume. The centers match the order-2 Hilbert lattice:
// -2.1, -0.7, 0.7, 2.1 on each axis.
for z in 0..4 {
for y in 0..4 {
for x in 0..4 {
cube3(voxel{x}{y}{z},
((x-1.5)*1.4,(y-1.5)*1.4,(z-1.5)*1.4),
(1.12,1.12,1.12));
opacity(voxel{x}{y}{z},0.11);
hidden(voxel{x}{y}{z});
tag(voxel{x}{y}{z},voxels);
tag(voxel{x}{y}{z},layer{z});
}
}
}
color(layer0,blue);
color(layer1,cyan);
color(layer2,magenta);
color(layer3,gold);
// A cinematic acquisition plane sweeps upward while the volume resolves one
// layer at a time.
cube3(scanPlane,(0,0,-3.0),(5.6,5.6,0.07));
color(scanPlane,cyan); opacity(scanPlane,0.28); hidden(scanPlane);
// The route is used once as an address order—there is no recursive
// refinement and no "line fills a cube" reproduction.
hilbert3(route,(0,0,0),4.2,2,"color=gradient shade=depth");
thick(route,0.040);
hidden(route); untraced(route);
sphere3(scanner,(-2.1,-2.1,-2.1),0.19);
color(scanner,fg); hidden(scanner);
finish3(scanner,"shading=smooth material=metal mesh=0.18 shadow=0.75");
// A translucent neighborhood travels with the current address. It represents
// the small spatial working set a cache or chunked volume reader wants nearby.
cube3(cacheWindow,(-2.1,-2.1,-2.1),(1.34,1.34,1.34));
color(cacheWindow,gold); opacity(cacheWindow,0.16); hidden(cacheWindow);
finish3(cacheWindow,"shading=smooth material=glass mesh=0.10 depth=0.72 shadow=0.18");
// A deliberately poor address order: three long diagonals tear across the
// volume. It is visually dramatic because consecutive addresses are not
// spatial neighbours.
arrow3(jump0,(-2.1,-2.1,-2.1),(2.1,2.1,2.1));
arrow3(jump1,(2.1,2.1,2.1),(-2.1,2.1,-2.1));
arrow3(jump2,(-2.1,2.1,-2.1),(2.1,-2.1,2.1));
color(jump0,red); color(jump1,coral); color(jump2,red);
thick(jump0,0.045); thick(jump1,0.045); thick(jump2,0.045);
hidden(jump0); hidden(jump1); hidden(jump2);
untraced(jump0); untraced(jump1); untraced(jump2);
tag(jump0,badOrder); tag(jump1,badOrder); tag(jump2,badOrder);
pin3(probeLabel,scanner,(18,-18));
hidden(kicker); hidden(head); hidden(caption); hidden(address);
hidden(keyLaw); hidden(volumeReadout); hidden(jumpWarning); hidden(localResult);
hidden(probeLabel);
step("the world becomes data") {
par {
show(kicker,0.30); show(head,0.45); show(caption,0.40);
}
view3(voxels,"isometric",0.90,smooth,1.45);
cue(whoosh);
show(scanPlane,0.25);
show(volumeReadout,0.30);
par {
move3(scanPlane,(0,0,3.0),2.35,smooth);
stagger(0.46) {
show(layer0,0.34); show(layer1,0.34); show(layer2,0.34); show(layer3,0.34);
}
say(caption,"A scan resolves the physical world one layer at a time.",0.45,smooth);
}
fade(scanPlane,0.25);
}
wait(0.50);
step("the wrong order tears space apart") {
par {
fade(volumeReadout,0.25);
show(scanner,0.25); show(address,0.30); show(jumpWarning,0.30);
say(head,"A bad index destroys locality.",0.45,smooth);
say(caption,"Consecutive keys can jump from one corner of the volume to another.",0.45,smooth);
}
cue(whoosh);
par {
draw(jump0,0.80,smooth);
travel3(scanner,jump0,0.80,smooth);
to(address,value,63,0.80,smooth);
}
cue(whoosh);
par {
draw(jump1,0.70,smooth);
travel3(scanner,jump1,0.70,smooth);
to(address,value,3,0.70,smooth);
}
cue(whoosh);
par {
draw(jump2,0.75,smooth);
travel3(scanner,jump2,0.75,smooth);
to(address,value,60,0.75,smooth);
}
shake(jumpWarning,0.45);
}
wait(0.55);
step("hilbert order restores locality") {
par {
fade(badOrder,0.35); fade(jumpWarning,0.25);
move3(scanner,(-2.1,-2.1,-2.1),0.65,smooth);
to(address,value,0,0.65,smooth);
say(head,"One route restores locality.",0.45,smooth);
say(caption,"Hilbert order makes every next address share a face with the last.",0.45,smooth);
}
cue(chime);
par {
show(route,0.20); draw(route,1.80,smooth);
show(cacheWindow,0.30); show(probeLabel,0.30);
}
pulse(scanner,0.55);
}
wait(0.45);
step("one uninterrupted spatial journey") {
attach3(cacheWindow,scanner,(0,0,0));
par {
travel3(scanner,route,7.20,linear);
to(address,value,63,7.20,linear);
orbit3(132,30,29.0,7.20,smooth);
say(caption,"The cache window, address and camera now travel as one synchronized shot.",0.48,smooth);
}
attach3(cacheWindow,none);
}
wait(0.55);
step("the payoff") {
par {
fade(address,0.25); fade(probeLabel,0.25); fade(cacheWindow,0.30);
show(localResult,0.35);
pulse(route,0.80);
say(head,"Locality wins.",0.45,smooth);
say(caption,"All 64 voxels are visited—and all 63 transitions stay face-adjacent.",0.50,smooth);
}
}
wait(1.00);
step("turn location into a key") {
par {
fade(localResult,0.25);
show(keyLaw,0.40);
pulse(route,0.80);
say(head,"Three coordinates become one sortable key.",0.45,smooth);
say(caption,"One idea powers medical volumes, spatial databases, simulations and maps.",0.50,smooth);
}
}
wait(1.35);
step("creator call to action") {
par {
fade(kicker,0.30); fade(head,0.30); fade(caption,0.30); fade(keyLaw,0.30);
fade(scanner,0.30); fade(route,0.40); fade(voxels,0.40); fade(badOrder,0.30);
fade(volumeReadout,0.25); fade(jumpWarning,0.25); fade(localResult,0.25);
fade(me.footer,0.30); show(me.endcard,0.60);
}
}
wait(2.00);
creator-v2-options-socials
The asset-free v2.4 review scene: collision-safe question hierarchy, professional A/B/C/D cards, uniform correct-state spacing, and native YouTube/X/web identity lockups.
// Creator v2.4 review scene: question hierarchy, A/B/C/D answer cards,
// uniform correct-state spacing, and asset-free native social identities.
title("Creator v2.4 — Options + Socials");
canvas("9:16");
template("mono");
watermark(manicMark, (w*0.895-100, h*0.075+24), "Made With Manic");
creator(me, "@anish2good name=Olympiad_Minute yt=zarigatongy x=@anish2good web=8gwifi.org/manic footer=social accent=cyan safe=reels");
quiz(q, `In cyclic quadrilateral $ABCD$, $\angle A=68^\circ$. What is $\angle C$?`,
"studio labels=letters layout=auto density=comfortable motion=calm safe=reels accent=cyan");
option(q, `$68^\circ$`);
option(q, `$102^\circ$`);
option(q, `$112^\circ$`, correct);
option(q, `$122^\circ$`);
timing(q, "balanced ask=1.2 options=1 think=4.8 reveal=0.8 hold=2.2 stagger=0.06");
timerstyle(q, "look=bar position=below number=outside direction=drain color=cyan track=dim label=THINK finish=pulse");
socials(me);
run(q);
creator-v2
The complete v2 core: responsive studio quiz, optics media, width-aware answer cards, a signature creator footer, optional explanation and a branded final end card.
// Creator Kit v2 core — responsive studio quiz + reusable brand system.
// Change the canvas to (1080,1350), (1080,1080), or (1280,720): the same
// question, media, choices, timer and footer reflow into the available space.
title("Creator Kit v2 — Optics Quiz");
canvas("9:16");
template("shorts");
watermark(manicMark, (w*0.91-100, h*0.055+24), "Made With Manic");
creator(me, "@anish2good name=Optics_Lab tagline=Physics_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Follow_for_more safe=shorts");
quiz(q, "Which glass separates blue and red light more?",
"studio layout=media-first reveal=rise timer=bar density=comfortable motion=studio safe=shorts accent=cyan");
option(q, "BK7 crown glass");
option(q, "SF11 flint glass", correct);
option(q, "Both equally");
option(q, "Neither material");
explain(q, "SF11 has stronger wavelength dispersion.", "Sellmeier model");
// Any static kit figure or tagged group can occupy the responsive media region.
prism(p, (540, 670), "sf11");
figure(p);
socials(me);
endcard(me, "cta=Follow_for_more");
par {
run(q, 12);
run(p, 12);
}
par {
fade(q.parts, 0.45);
fade(p, 0.45);
fade(me.footer, 0.45);
}
show(me.endcard, 0.6);
wait(1.4);
creator-v2-timing
Timing v2 in a portrait quiz: exact ask/options/think/reveal/hold phases, LaTeX media, and a segmented timer whose presentation can change without changing the choreography.
// Creator Timing v2 — independent choreography and timer presentation.
// `timing` controls WHEN each beat happens; `timerstyle` controls how the
// countdown looks. With explicit phases, call run(q) without a second duration.
title("Creator Timing v2");
canvas("9:16");
template("shorts");
watermark(manicMark, (w*0.91-100, h*0.055+24), "Made With Manic");
creator(me, "@anish2good name=Number_Lab tagline=Pause_predict_prove yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=magenta secondary=cyan footer=signature cta=Try_the_next_one safe=shorts");
quiz(q, `Which number is equal to $2^5$?`,
"studio layout=media-first reveal=rise density=comfortable motion=calm safe=shorts accent=magenta");
option(q, `$10$`);
option(q, `$16$`);
option(q, `$32$`, correct);
option(q, `$64$`);
explain(q, `$2^5=2\times2\times2\times2\times2=32$.`);
equation(eq, (cx, 650), `2^5 = 2\times2\times2\times2\times2`, 72);
color(eq, magenta); figure(eq); hidden(eq);
// Absolute phases: 1.2s question, 1.1s answers, 6s thinking, 0.8s reveal,
// then a 2.2s answer hold. The total is derived automatically by run(q).
timing(q, "calm ask=1.2 options=1.1 think=6 reveal=0.8 hold=2.2 stagger=0.07");
// A creative segmented timer in the media corner. Change only this line to
// ring/bar/number/ticks/pulse/none without changing the choreography.
timerstyle(q, "look=segments position=below number=outside direction=drain size=large thickness=1.2 color=magenta track=dim label=THINK font=display finish=pulse");
socials(me);
par {
run(q);
show(eq, 0.55);
}
creator-v2-timers
All six native Timing v2 looks—ring, bar, number, segments, ticks and pulse—running side by side. Native shapes keep every look scalable, theme-aware and progress-animatable.
// Timing v2 look gallery — every timer uses the same six-second behaviour.
// All visuals are native manic primitives: scalable, theme-aware and animated.
title("Creator Timing v2 — Native Looks");
canvas("16:9");
template("shorts");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
text(head, (cx, 70), "TIMING V2 — CHOOSE THE LOOK");
size(head, 34); bold(head); color(head, fg);
countdown(ring, (220, 245), 6, "ring size=small color=cyan label=RING finish=hold");
countdown(bar, (640, 245), 6, "bar size=small color=magenta number=outside label=BAR finish=hold");
countdown(number, (1060, 245), 6, "number size=large font=display color=gold label=NUMBER finish=hold");
countdown(segments, (220, 520), 6, "segments size=small color=lime label=SEGMENTS finish=hold");
countdown(ticks, (640, 520), 6, "ticks size=small direction=fill color=cyan label=TICKS finish=hold");
countdown(pulse, (1060, 520), 6, "pulse size=small font=display color=magenta label=PULSE finish=hold");
par {
run(ring, 6);
run(bar, 6);
run(number, 6);
run(segments, 6);
run(ticks, 6);
run(pulse, 6);
}
creator-v2-timing-square
A square feed-card variant with a scaled dramatic preset and a filling tick timer, showing that timing and timer placement reflow independently across formats.
// Creator Timing v2 — square preset scaling and responsive timer placement.
title("Creator Timing v2 — Square");
canvas("1:1");
template("shorts");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
creator(me, "@anish2good name=Proof_Minute tagline=See_the_pattern yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=gold footer=compact cta=Save_this safe=clean");
quiz(q, `If $x+1/x=3$, find $x^2+1/x^2$`,
"glass layout=grid density=comfortable motion=studio pace=dramatic accent=cyan");
option(q, `$5$`);
option(q, `$7$`, correct);
option(q, `$9$`);
option(q, `$11$`);
explain(q, `Square the given relation: $x^2+2+\frac{1}{x^2}=9$.`);
equation(clue, (cx, cy), `\left(x+\frac1x\right)^2`, 72);
color(clue, cyan); figure(clue);
// The timer fills clockwise below the header. The dramatic preset is scaled
// proportionally to a compact eight-second feed clip by run(q, 8).
timerstyle(q, "look=ticks position=below number=inside direction=fill size=small thickness=1.1 color=cyan track=dim label=SOLVE font=mono finish=flash");
socials(me);
run(q, 8);
creator-v2-olympiad-geometry
An olympiad-level geometry Reel built as pause → predict → prove, with a responsive construction, authored explanation and reusable creator identity.
// Creator Kit v2 — olympiad geometry problem.
//
// Problem: From an external point P, tangents PA and PB touch a circle with
// centre O and radius 5. If OP = 13, find the chord length AB.
//
// The tangent points are computed by the geo kit. Nothing is eyeballed:
// tangent-radius perpendicularity, chord AB, and M = AB ∩ OP remain live.
title("Creator v2 — Olympiad Geometry");
canvas("9:16");
template("shorts");
watermark(manicMark, (w*0.91-100, h*0.055+24), "Made With Manic");
creator(me, "@anish2good name=Geometry_Lab tagline=See_the_hidden_lemma yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Pause_then_prove safe=shorts");
quiz(q, `From $P$, $PA$ and $PB$ are tangents to circle $(O)$. If $r=5$ and $OP=13$, find $AB$.`,
"studio layout=media-first reveal=rise timer=bar density=compact motion=calm safe=shorts accent=cyan");
option(q, `$\tfrac{60}{13}$`);
option(q, `$10$`);
option(q, `$\tfrac{120}{13}$`, correct);
option(q, `$12$`);
explain(q, `Tangent-radius gives $OA\perp PA$, hence $PA=12$. With $M=AB\cap OP$, $\triangle OMA\sim\triangle OAP$, so $AM/5=12/13$ and $AB=120/13$.`);
// Source geometry: 40 px represents one unit, so r = 5 and OP = 13.
point(O, (430, 560), "O");
point(R, (430, 360));
point(P, (950, 560), "P");
hidden(O); hidden(R); hidden(P);
circle2(circ, O, R);
color(circ, dim); stroke(circ, 3); untraced(circ);
tangent(t, P, O, R);
label(t0, "A", (22, -18));
label(t1, "B", (22, 18));
color(t0, cyan); color(t1, cyan); hidden(t0); hidden(t1);
segment(pa, P, t0); segment(pb, P, t1);
color(pa, cyan); color(pb, cyan); stroke(pa, 4); stroke(pb, 4);
untraced(pa); untraced(pb);
segment(oa, O, t0); segment(ob, O, t1); segment(op, O, P);
color(oa, dim); color(ob, dim); color(op, fg);
stroke(oa, 3); stroke(ob, 3); stroke(op, 3);
untraced(oa); untraced(ob); untraced(op);
segment(chord, t0, t1);
color(chord, magenta); stroke(chord, 5); untraced(chord);
meet(M, t0, t1, O, P);
label(M, "M", (-28, 24));
color(M, lime); hidden(M);
rightangle(raA, O, t0, P); rightangle(raB, O, t1, P);
color(raA, lime); color(raB, lime); hidden(raA); hidden(raB);
text(r5, (390, 455), "5"); color(r5, fg); size(r5, 30); hidden(r5);
text(op13, (690, 595), "13"); color(op13, fg); size(op13, 30); hidden(op13);
// Every source and dependent entity is included so figure() can safely reflow
// the construction into the responsive media region.
tag(O, fig); tag(R, fig); tag(P, fig); tag(circ, fig);
tag(t0, fig); tag(t1, fig); tag(pa, fig); tag(pb, fig);
tag(oa, fig); tag(ob, fig); tag(op, fig); tag(chord, fig);
tag(M, fig); tag(raA, fig); tag(raB, fig); tag(r5, fig); tag(op13, fig);
figure(fig);
socials(me);
endcard(me, "cta=Pause_then_prove");
par {
run(q, 14);
seq {
wait(1.3);
par { show(O, 0.25); show(P, 0.25); }
draw(circ, 0.7);
draw(op, 0.45);
par { draw(pa, 0.7); draw(pb, 0.7); }
par { show(t0, 0.25); show(t1, 0.25); }
par { draw(oa, 0.45); draw(ob, 0.45); }
par { show(raA, 0.3); show(raB, 0.3); }
draw(chord, 0.5);
par { show(M, 0.25); show(r5, 0.25); show(op13, 0.25); }
}
}
par {
fade(q.parts, 0.45);
fade(fig, 0.45);
fade(me.footer, 0.45);
}
show(me.endcard, 0.6);
wait(1.4);
creator-v2-latex-calculus
Portrait Creator v2 with inline and display LaTeX: a calculus question, fitted formula answers and crisp typesetting throughout the timed reveal.
// Creator Kit v2 + LaTeX — portrait calculus lesson.
// Review focus: mixed inline math, a responsive equation figure, explanation,
// branded footer, progress timer, and the Creator v2 end card.
title("Creator v2 — LaTeX Calculus");
canvas("9:16");
template("shorts");
watermark(manicMark, (w*0.91-100, h*0.055+24), "Made With Manic");
creator(me, "@anish2good name=Calculus_Lab tagline=One_integral_at_a_time yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Save_for_revision safe=shorts");
quiz(q, `Evaluate $\int_0^1 2x\,dx$.`,
"studio layout=media-first reveal=rise timer=bar density=comfortable motion=calm safe=shorts accent=cyan");
option(q, `$0$`);
option(q, `$\tfrac{1}{2}$`);
option(q, `$1$`, correct);
option(q, `$2$`);
explain(q, `The antiderivative is $x^2$, so $1^2-0^2=1$.`, "Fundamental Theorem of Calculus");
equation(eq, (cx, 650), `\int_0^1 2x\,dx = [x^2]_0^1 = 1`, 76);
color(eq, cyan);
figure(eq);
hidden(eq);
socials(me);
endcard(me, "cta=Save_for_revision");
par {
run(q, 12);
show(eq, 0.55);
}
par {
fade(q.parts, 0.45);
fade(eq, 0.45);
fade(me.footer, 0.45);
}
show(me.endcard, 0.6);
wait(1.4);
creator-v2-latex-algebra
Square Creator v2 on a paper surface, checking that algebraic LaTeX and answer cards remain balanced and readable outside the vertical format.
// Creator Kit v2 + LaTeX — square algebra card.
// Review focus: automatic square reflow, paper styling, compact choices,
// inline factor notation, and a clean numeric timer.
title("Creator v2 — LaTeX Algebra");
canvas("square");
template("paper");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
creator(me, "@anish2good name=Algebra_Studio tagline=Patterns_before_procedures yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=magenta secondary=cyan footer=compact cta=Try_it_first safe=clean");
quiz(q, `Which expression equals $x^2-5x+6$?`,
"studio layout=auto reveal=fade timer=number density=compact motion=calm safe=clean accent=magenta");
option(q, `$(x-1)(x-6)$`);
option(q, `$(x-2)(x-3)$`, correct);
option(q, `$(x+2)(x+3)$`);
option(q, `$(x-2)(x+3)$`);
explain(q, `The two numbers multiply to $6$ and add to $-5$.`, "Factor pair: -2 and -3");
equation(eq, (cx, cy), `x^2-5x+6`, 78);
color(eq, magenta);
figure(eq);
hidden(eq);
socials(me);
par {
run(q, 10);
show(eq, 0.55);
}
creator-v2-latex-physics
Landscape Creator v2 with a physics equation, proving the same LaTeX quiz system reflows cleanly for widescreen explainers.
// Creator Kit v2 + LaTeX — landscape physics explainer.
// Review focus: automatic widescreen split layout, formula options, equation
// media, branded footer, and a restrained ring timer.
title("Creator v2 — LaTeX Physics");
canvas("16:9");
template("shorts");
watermark(manicMark, (w*0.955-100, h*0.045+24), "Made With Manic");
creator(me, "@anish2good name=Physics_Notes tagline=Concepts_in_one_frame yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=gold secondary=cyan footer=signature cta=Follow_for_more safe=clean");
quiz(q, `A mass $m$ moves at speed $v$. What is its kinetic energy?`,
"studio layout=auto reveal=fade timer=ring density=compact motion=studio safe=clean accent=gold");
option(q, `$mv$`);
option(q, `$mv^2$`);
option(q, `$\tfrac{1}{2}mv^2$`, correct);
option(q, `$2mv^2$`);
explain(q, `Work changes kinetic energy: $W=\Delta K$.`, "Work–energy theorem");
equation(eq, (cx, cy), `K = \frac{1}{2}mv^2`, 82);
color(eq, gold);
figure(eq);
hidden(eq);
socials(me);
par {
run(q, 10);
show(eq, 0.55);
}
quiz-skins
The quiz Short in a dozen lines: quiz/option/run + a creator/socials footer.
Change the one style word on quiz(...) to switch card SKIN — badge (framed panel +
coloured letter badges), minimal, glass (glowing borders) or plain — and add a
question REVEAL in the same string (e.g. "glass fade"). The correct card lights up with a
green badge + check on reveal; a draining ring counts the timer down.
// A creator quiz Short (9:16). Try the four card SKINS by changing the style
// string on `quiz(...)`: "badge" (default) · "minimal" · "glass" · "plain".
// You can also add a question REVEAL in the same string, order-free, e.g.
// quiz(q, "...", "glass fade") // glass cards + fade-in question
canvas("9:16");
template("shorts");
// a reusable creator profile (set once, drawn by `socials`)
creator(me, "@anish2good yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=magenta");
// the quiz: a question + four answers, one marked `correct`
quiz(q, "What is 7 x 8?", "badge");
option(q, "54");
option(q, "56", correct);
option(q, "48");
option(q, "63");
// play the whole ask -> countdown -> reveal beat over 8 seconds
run(q, 8);
// the footer with the creator's socials
socials(me);
quiz-euler
A quiz Short with an ANIMATED figure: the geo kit constructs the Euler line (the answer),
and figure(...) AUTO-FITS the whole triangle+circumcircle into the zone between the
question header and the answer cards — no coordinate tuning. The question, four cards, the
countdown and the whole ask→countdown→reveal beat are just quiz/option/run.
// ============================================================================
// quiz-euler.manic — the SAME quiz Short, now via the `creator` kit
// ----------------------------------------------------------------------------
// Compare with quiz-geometry.manic (≈60 hand-authored lines): the question,
// four option cards, the countdown, and the whole ask→countdown→reveal beat
// are now `quiz` / `option` / `run`. The creator's footer is `creator`/`socials`.
// Only the figure is hand-drawn (author-supplied — any manic entity/kit).
// ============================================================================
title("Quiz — the Euler Line");
canvas("9:16");
template("paper");
creator(me, "@anish2good yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=magenta");
// --- the quiz: question + four answers (kit handles layout + the beat) ---
quiz(q, "3 triangle centres are always collinear — on WHICH line?");
option(q, "the Euler line", correct);
option(q, "a perpendicular bisector");
option(q, "an angle bisector");
option(q, "a median");
// --- the figure (author-supplied): the geo kit constructs the Euler line ---
point(pA, (335, 760)); point(pB, (745, 745)); point(pC, (540, 470));
hidden(pA); hidden(pB); hidden(pC);
segment(sa, pA, pB); segment(sb, pB, pC); segment(sc, pC, pA);
untraced(sa); untraced(sb); untraced(sc);
circumcircle(cc, pA, pB, pC); color(cc, dim); untraced(cc);
circumcenter(oo, pA, pB, pC); color(oo, magenta); hidden(oo);
centroid(gg, pA, pB, pC); color(gg, lime); hidden(gg);
orthocenter(hh, pA, pB, pC); color(hh, cyan); hidden(hh);
segment(eu, oo, hh); color(eu, fg); stroke(eu, 7); glow(eu, 1.5); untraced(eu);
// tag every figure part, then AUTO-FIT the whole group into the zone between
// the question header and the answer cards — no hand-tuning coordinates.
tag(pA, fig); tag(pB, fig); tag(pC, fig); tag(sa, fig); tag(sb, fig); tag(sc, fig);
tag(cc, fig); tag(oo, fig); tag(gg, fig); tag(hh, fig); tag(eu, fig);
figure(fig, (540, 645), (720, 360));
// ================= THE BEAT =================
socials(me);
par {
run(q, 14); // ask · countdown · reveal — the whole beat
seq { // the figure builds while the question sits
wait(1.6);
par { show(pA, 0.2); show(pB, 0.2); show(pC, 0.2); }
par { draw(sa, 0.5); draw(sb, 0.5); draw(sc, 0.5); }
draw(cc, 0.5);
par { show(oo); show(gg); show(hh); }
draw(eu, 0.9); pulse(eu);
}
}
quiz-geometry
The hand-authored proof behind the kit (≈60 lines from shipped primitives): a question, an
animated geometry figure, four option cards, a countdown and a time-out reveal. Useful to
see what quiz/option/run automate under the hood.
// ============================================================================
// quiz-geometry.manic — a QUIZ SHORT, hand-authored (creator-template proto)
// ----------------------------------------------------------------------------
// The first proof of the "quiz Short" format the `creator` kit will later
// automate (see CAPABILITIES → Creator format templates). Built ENTIRELY from
// shipped primitives on a 9:16 canvas: a typewriter question, an ANIMATED
// geometry figure (the geo kit constructs the Euler line — which IS the answer),
// four option cards, a countdown, a time-out reveal, and a socials footer.
// When the `quiz`/`countdown`/`socials` builtins land, this whole file collapses
// to a few lines — but the beat/layout are proven here first.
// ============================================================================
title("Quiz — the Euler Line");
canvas("9:16"); // portrait 1080×1920 · cx=540 cy=960
template("paper");
// ---- the question (two typewriter lines, top / title-safe) ----
text(q1, (cx, 210), "3 triangle centres are always"); color(q1, fg); size(q1, 44); bold(q1); untraced(q1);
text(q2, (cx, 285), "collinear — on WHICH line?"); color(q2, cyan); size(q2, 44); bold(q2); untraced(q2);
// ---- the animated geometry figure (geo kit) : the Euler line construction ----
// (colours are palette-semantic — fg/cyan/magenta/lime/dim — so they REMAP with
// the template; gold/red/etc. are fixed and would lose contrast on `paper`)
point(A, (335, 760), "A"); point(B, (745, 745), "B"); point(C, (540, 470), "C");
hidden(A); hidden(B); hidden(C);
segment(ab, A, B); segment(bc, B, C); segment(ca, C, A); untraced(ab); untraced(bc); untraced(ca);
circumcircle(cc, A, B, C); color(cc, dim); untraced(cc);
circumcenter(O, A, B, C); color(O, magenta); label(O, "O", (20, -12)); hidden(O);
centroid(G, A, B, C); color(G, lime); label(G, "G", (20, -12)); hidden(G);
orthocenter(H, A, B, C); color(H, cyan); label(H, "H", (-34, -12)); hidden(H);
segment(euler, O, H); color(euler, fg); stroke(euler, 7); glow(euler, 1.5); untraced(euler);
// ---- four option cards, laid out 2×2 (compact — clears the figure) ----
rect(c1, (305, 1010), 428, 120); color(c1, panel); filled(c1); hidden(c1);
rect(c2, (775, 1010), 428, 120); color(c2, panel); filled(c2); hidden(c2);
rect(c3, (305, 1150), 428, 120); color(c3, panel); filled(c3); hidden(c3);
rect(c4, (775, 1150), 428, 120); color(c4, panel); filled(c4); hidden(c4);
text(t1, (305, 1010), "A Euler line"); color(t1, fg); size(t1, 34); hidden(t1);
text(t2, (775, 1010), "B Perp. bisector"); color(t2, fg); size(t2, 34); hidden(t2);
text(t3, (305, 1150), "C Angle bisector"); color(t3, fg); size(t3, 34); hidden(t3);
text(t4, (775, 1150), "D Median"); color(t4, fg); size(t4, 34); hidden(t4);
// ---- countdown ring + digit (palette-semantic → template-safe) ----
circle(ring, (cx, 1400), 62); color(ring, cyan); stroke(ring, 6); outlined(ring); hidden(ring);
text(timer, (cx, 1400), "5"); color(timer, fg); size(timer, 62); bold(timer); hidden(timer);
// ---- socials footer : icons DRAWN from primitives (manic has no raster images),
// so they render on any template; grouped as `foot` to show together ----
line(rule, (150, 1740), (930, 1740)); color(rule, dim); stroke(rule, 2);
// ▶ YouTube chip
rect(yt, (312, 1815), 58, 40); color(yt, magenta); filled(yt);
polygon(ytp, (300, 1802), (300, 1828), (326, 1815), bg);
// ✕ X chip
rect(xb, (404, 1815), 46, 46); color(xb, fg); outlined(xb);
line(x1, (392, 1803), (416, 1827)); color(x1, fg); stroke(x1, 4);
line(x2, (416, 1803), (392, 1827)); color(x2, fg); stroke(x2, 4);
// ◎ Instagram chip
rect(ig, (496, 1815), 46, 46); color(ig, fg); outlined(ig);
circle(igc, (496, 1815), 13); color(igc, fg); outlined(igc);
circle(igd, (509, 1802), 3); color(igd, fg); filled(igd);
// handle + link
text(foot, (740, 1815), "@manic · 8gwifi.org/manic"); color(foot, dim); size(foot, 28);
tag(rule, footer); tag(yt, footer); tag(ytp, footer); tag(xb, footer); tag(x1, footer);
tag(x2, footer); tag(ig, footer); tag(igc, footer); tag(igd, footer); tag(foot, footer);
hidden(footer);
// ================= THE BEAT =================
// 1) ask — typewriter
type(q1, 1.1);
type(q2, 1.0);
wait(0.2);
// 2) the figure constructs itself (animated geometry — the geo kit)
par { show(A, 0.25); show(B, 0.25); show(C, 0.25); }
par { draw(ab, 0.5); draw(bc, 0.5); draw(ca, 0.5); }
draw(cc, 0.6);
par { show(O); show(G); show(H); }
draw(euler, 0.9); pulse(euler); // the three centres line up → the Euler line
wait(0.3);
// 3) options fly in, staggered
seq {
par { show(c1, 0.2); show(t1, 0.2); }
par { show(c2, 0.2); show(t2, 0.2); }
par { show(c3, 0.2); show(t3, 0.2); }
par { show(c4, 0.2); show(t4, 0.2); }
}
show(footer, 0.3);
// 4) countdown 5 → 1
par { show(ring, 0.2); show(timer, 0.2); }
wait(0.8); say(timer, "4", 0.15);
wait(0.8); say(timer, "3", 0.15);
wait(0.8); say(timer, "2", 0.15);
wait(0.8); say(timer, "1", 0.15);
wait(0.8);
// 5) TIME OUT → reveal: card A (the Euler line) glows, the rest dim
say(timer, "0", 0.15); flash(ring, gold);
par {
recolor(c1, lime); flash(c1, gold); pulse(c1);
recolor(t1, void);
fade(c2, 0.5); fade(t2, 0.5);
fade(c3, 0.5); fade(t3, 0.5);
fade(c4, 0.5); fade(t4, 0.5);
flash(euler, gold);
}
wait(1.6);
quiz-geometry-2
A layout stress-test: a different olympiad question with TWO figures side by side (an acute triangle with its circumcentre INSIDE vs an obtuse one with it OUTSIDE), proving the 2×2 options, countdown and footer keep their spacing for richer figure content.
// ============================================================================
// quiz-geometry-2.manic — a second QUIZ SHORT (olympiad geometry)
// ----------------------------------------------------------------------------
// A layout STRESS TEST: same 9:16 quiz scaffold as quiz-geometry.manic, but a
// different olympiad question with TWO figures side-by-side (acute vs obtuse
// triangle) — to prove the spacing + visibility hold for richer figure content.
// Template-agnostic (palette-semantic colours), drawn social icons, 2×2 options.
// ============================================================================
title("Quiz — Circumcentre");
canvas("9:16");
template("terminal");
// ---- the question (two typewriter lines) ----
text(q1, (cx, 210), "A triangle's CIRCUMCENTRE falls"); color(q1, fg); size(q1, 42); bold(q1); untraced(q1);
text(q2, (cx, 283), "outside it — when is it…?"); color(q2, cyan); size(q2, 42); bold(q2); untraced(q2);
// ---- figure ① : an ACUTE triangle (circumcentre INSIDE) ----
point(a1, (190, 800)); point(b1, (455, 800)); point(c1, (320, 560));
hidden(a1); hidden(b1); hidden(c1);
segment(s1a, a1, b1); segment(s1b, b1, c1); segment(s1c, c1, a1);
untraced(s1a); untraced(s1b); untraced(s1c);
circumcenter(o1, a1, b1, c1); color(o1, magenta); hidden(o1);
text(lab1, (322, 865), "acute · inside"); color(lab1, dim); size(lab1, 26);
// ---- figure ② : an OBTUSE triangle (circumcentre OUTSIDE) ----
// c2 sits LEFT of a2, so the angle at a2 is obtuse → circumcentre lands outside
point(a2, (735, 800)); point(b2, (965, 800)); point(c2, (700, 600));
hidden(a2); hidden(b2); hidden(c2);
segment(s2a, a2, b2); segment(s2b, b2, c2); segment(s2c, c2, a2);
untraced(s2a); untraced(s2b); untraced(s2c);
circumcenter(o2, a2, b2, c2); color(o2, magenta); hidden(o2);
text(lab2, (830, 865), "obtuse · outside"); color(lab2, dim); size(lab2, 26);
// ---- four option cards, 2×2 (correct = C, obtuse → c3) ----
rect(c1c, (305, 1010), 428, 120); color(c1c, panel); filled(c1c); hidden(c1c);
rect(c2c, (775, 1010), 428, 120); color(c2c, panel); filled(c2c); hidden(c2c);
rect(c3c, (305, 1150), 428, 120); color(c3c, panel); filled(c3c); hidden(c3c);
rect(c4c, (775, 1150), 428, 120); color(c4c, panel); filled(c4c); hidden(c4c);
text(t1, (305, 1010), "A Acute"); color(t1, fg); size(t1, 34); hidden(t1);
text(t2, (775, 1010), "B Right"); color(t2, fg); size(t2, 34); hidden(t2);
text(t3, (305, 1150), "C Obtuse"); color(t3, fg); size(t3, 34); hidden(t3);
text(t4, (775, 1150), "D Equilateral"); color(t4, fg); size(t4, 34); hidden(t4);
// ---- countdown ring + digit ----
circle(ring, (cx, 1400), 62); color(ring, cyan); stroke(ring, 6); outlined(ring); hidden(ring);
text(timer, (cx, 1400), "5"); color(timer, fg); size(timer, 62); bold(timer); hidden(timer);
// ---- socials footer (drawn vector icons — template-safe) ----
line(rule, (150, 1740), (930, 1740)); color(rule, dim); stroke(rule, 2);
rect(yt, (312, 1815), 58, 40); color(yt, magenta); filled(yt);
polygon(ytp, (300, 1802), (300, 1828), (326, 1815), bg);
rect(xb, (404, 1815), 46, 46); color(xb, fg); outlined(xb);
line(x1, (392, 1803), (416, 1827)); color(x1, fg); stroke(x1, 4);
line(x2, (416, 1803), (392, 1827)); color(x2, fg); stroke(x2, 4);
rect(ig, (496, 1815), 46, 46); color(ig, fg); outlined(ig);
circle(igc, (496, 1815), 13); color(igc, fg); outlined(igc);
circle(igd, (509, 1802), 3); color(igd, fg); filled(igd);
text(foot, (740, 1815), "@manic · 8gwifi.org/manic"); color(foot, dim); size(foot, 28);
tag(rule, footer); tag(yt, footer); tag(ytp, footer); tag(xb, footer); tag(x1, footer);
tag(x2, footer); tag(ig, footer); tag(igc, footer); tag(igd, footer); tag(foot, footer);
hidden(footer);
// ================= THE BEAT =================
// 1) ask
type(q1, 1.1);
type(q2, 1.0);
wait(0.2);
// 2) both figures construct (side by side)
par { show(a1, 0.2); show(b1, 0.2); show(c1, 0.2); show(a2, 0.2); show(b2, 0.2); show(c2, 0.2); }
par {
draw(s1a, 0.5); draw(s1b, 0.5); draw(s1c, 0.5);
draw(s2a, 0.5); draw(s2b, 0.5); draw(s2c, 0.5);
}
par { show(o1); show(o2); pulse(o1); pulse(o2); }
par { show(lab1, 0.3); show(lab2, 0.3); }
wait(0.3);
// 3) options fly in, 2×2 reading order
seq {
par { show(c1c, 0.2); show(t1, 0.2); }
par { show(c2c, 0.2); show(t2, 0.2); }
par { show(c3c, 0.2); show(t3, 0.2); }
par { show(c4c, 0.2); show(t4, 0.2); }
}
show(footer, 0.3);
// 4) countdown 5 → 1
par { show(ring, 0.2); show(timer, 0.2); }
wait(0.8); say(timer, "4", 0.15);
wait(0.8); say(timer, "3", 0.15);
wait(0.8); say(timer, "2", 0.15);
wait(0.8); say(timer, "1", 0.15);
wait(0.8);
// 5) TIME OUT → reveal: card C (Obtuse) glows, the rest fade
say(timer, "0", 0.15); flash(ring, gold);
par {
recolor(c3c, lime); flash(c3c, gold); pulse(c3c);
recolor(t3, void);
fade(c1c, 0.5); fade(t1, 0.5);
fade(c2c, 0.5); fade(t2, 0.5);
fade(c4c, 0.5); fade(t4, 0.5);
flash(o2, gold); // the obtuse triangle's outside centre
}
wait(1.6);
creator-race-quiz
The charts kit composed with everything else: a voiced 9:16 Short that poses a QUIZ (which
economy grew fastest since 1990?), reveals the answer, then plays a bar-chart RACE that
PROVES it — China climbing from the pack to #2. Quiz cards + speak narration over a
par-run race + a Manic CTA card. Problem → guess → proof → CTA.
// creator-race-quiz.manic — a Creator Short that PROVES a claim with a race chart.
// Poses a quiz (which economy grew fastest since 1990?), lets you guess, reveals
// the answer, then plays a bar-chart RACE that settles it — the data as the payoff.
// Shows the charts kit composing with quiz cards, voice, and creator chrome.
//
// ELEVENLABS_API_KEY=... manic examples/creator-race-quiz.manic --record out.mp4
title("The Race-Chart Quiz");
canvas("9:16");
template("shorts");
voice("elevenlabs");
creator(me, "@anish2good name=Manic tagline=Turn_plain_text_into_amazing_data_videos yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Try_it_free safe=reels");
watermark(mark, (w*0.955-100, h*0.045+24), "Made With Manic");
// --- the question ---
text(kick, (cx, h*0.13), "Since 1990, which economy grew the FASTEST?");
size(kick, 46); color(kick, fg); bold(kick); wrap(kick, w*0.86); hidden(kick);
// --- four option cards (India · China · S. Korea · Brazil) ---
let cy0 = h*0.30; let ch = h*0.09; let gap = h*0.115;
rect(card1, (cx, cy0), w*0.74, ch); outlined(card1); outline(card1, dim); stroke(card1, 3); hidden(card1);
rect(card2, (cx, cy0+gap), w*0.74, ch); outlined(card2); outline(card2, dim); stroke(card2, 3); hidden(card2);
rect(card3, (cx, cy0+gap*2), w*0.74, ch); outlined(card3); outline(card3, dim); stroke(card3, 3); hidden(card3);
rect(card4, (cx, cy0+gap*3), w*0.74, ch); outlined(card4); outline(card4, dim); stroke(card4, 3); hidden(card4);
text(lbl1, (cx, cy0), "India"); size(lbl1, 40); color(lbl1, fg); bold(lbl1); hidden(lbl1);
text(lbl2, (cx, cy0+gap), "China"); size(lbl2, 40); color(lbl2, fg); bold(lbl2); hidden(lbl2);
text(lbl3, (cx, cy0+gap*2), "South Korea"); size(lbl3, 40); color(lbl3, fg); bold(lbl3); hidden(lbl3);
text(lbl4, (cx, cy0+gap*3), "Brazil"); size(lbl4, 40); color(lbl4, fg); bold(lbl4); hidden(lbl4);
// --- the proof: a GDP bar-chart race (labels only), hidden until the reveal ---
racechart(gdp, "bar", "1990 1998 2006 2014 2020 2026");
racedata(gdp, "
USA, 5.96, 8.6, 13.8, 17.5, 21.1, 30.3
China, 0.36, 1.1, 2.8, 10.5, 14.7, 20.5
Japan, 3.13, 4.1, 4.6, 4.9, 5.1, 4.4
India, 0.32, 0.4, 0.9, 2.0, 2.7, 4.6
Brazil, 0.46, 0.9, 1.1, 2.5, 1.5, 2.4
South Korea, 0.28, 0.6, 1.0, 1.5, 1.6, 1.9
");
text(cap, (cx, h*0.70), ""); size(cap, 34); color(cap, dim); bold(cap); wrap(cap, w*0.86); hidden(cap);
socials(me);
endcard(me, "title=Race_ANY_Dataset cta=8gwifi.org/manic");
// ---------------- the story ----------------
fade(gdp, 0.01); // keep the race hidden during the quiz
show(kick);
speak("Since nineteen-ninety, one economy grew faster than every other major country. Which one?");
stagger(0.16) { show(card1); show(card2); show(card3); show(card4); }
par { show(lbl1); show(lbl2); show(lbl3); show(lbl4); }
show(cap, 0.3); say(cap, "Lock in your guess…");
wait(1.0);
speak("The answer? China.");
par { recolor(card2, gold); pulse(card2); recolor(lbl2, gold); }
par { fade(card1); fade(card3); fade(card4); fade(lbl1); fade(lbl3); fade(lbl4); }
wait(0.6);
speak("But don't take my word for it — let the data settle it.");
par { fade(card2); fade(lbl2); fade(kick); fade(cap); }
show(gdp);
par {
race(gdp, 8.0);
seq {
speak("Watch China — from the bottom of the pack,");
speak("climbing past Brazil, Korea, India, even Japan,");
speak("all the way to number two.");
}
}
wait(0.6);
speak("The data doesn't argue. And this whole video is one Manic script — drop in any dataset, get a race. Free, at eight g wifi dot org slash manic.");
par { fade(gdp); }
show(me.endcard);
wait(2.6);
Race charts — paste a table, get a race
The charts kit turns a table into an animated race chart — ranked bars, columns or lines that reorder over time as the data changes. Declare a racechart, paste the data with racedata (a value per period per row; icons ride the bars), and play with race. Bars get rescaling gridlines + a live Total; raceline/racepanel add a companion line or a multi-line history panel below the bars. See the charts guide.
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
charts-gdp-race
The flagship bar-chart race: the world’s biggest economies, GDP 1990→2026, with country flags riding the bars. Watch China climb from #7 to #2 as the bars reorder, the axis rescales with gridlines, and the year + Total tick — all from one pasted data block.
// charts-gdp-race.manic — an animated BAR-CHART RACE from a pasted data block.
//
// The `charts` kit turns a table into bars that actually REORDER as the numbers
// change — the viral data-viz format, but true to the data and self-contained in
// one script. Paste your data into `racedata` (one row per entity: label, an icon
// shorthand, then a value per period), then `race` plays the whole thing —
// re-ranking, sliding bars to their new slots, rescaling the axis, and ticking the
// year + values. Bars are rounded, gradient-filled; flags are imported SVG.
//
// Nominal GDP, US$ trillion, 1990 → 2026 (approx.) — watch China climb from the
// bottom to #2, Japan slide, and India rise.
//
// manic examples/charts-gdp-race.manic
canvas("16:9");
template("blank");
racechart(gdp, "bar", "1990 1995 2000 2005 2010 2015 2020 2026",
"The World's Biggest Economies — GDP 1990 to 2026 ($T)");
// paste your table: label, ICON, then one value per period.
// separators are forgiving — commas here, but tabs / spaces / newlines all work.
//
// the ICON column takes ANY svg, not just flags:
// us a flag shorthand → asset:svg/flags/us.svg
// rocket / heart / trophy an emoji/icon alias → asset:svg/emoji|lucide/…
// asset:svg/lucide/lightbulb.svg an explicit set path
// logos/acme.svg your OWN file (any path ending in .svg)
// omit the icon entirely and the row is just label + values.
//
// NOTE: a few flags whose stars are drawn with SVG <use> (e.g. cn — China) render
// imperfectly for now (tracked); most flags and all icons/emoji render cleanly.
racedata(gdp, "
USA, us, 5.96, 7.64, 10.25, 13.04, 15.05, 18.24, 21.06, 30.34
China, cn, 0.36, 0.73, 1.21, 2.29, 6.09, 11.06, 14.69, 20.50
Japan, jp, 3.13, 5.55, 4.97, 4.83, 5.76, 4.44, 5.06, 4.40
Germany, de, 1.60, 2.59, 1.95, 2.85, 3.40, 3.36, 3.89, 4.90
India, in, 0.32, 0.36, 0.47, 0.82, 1.68, 2.10, 2.67, 4.60
UK, gb, 1.09, 1.33, 1.66, 2.54, 2.49, 2.93, 2.70, 3.90
France, fr, 1.27, 1.60, 1.36, 2.20, 2.65, 2.44, 2.63, 3.30
Brazil, br, 0.46, 0.77, 0.65, 0.89, 2.21, 1.80, 1.48, 2.40
");
race(gdp, 14);
charts-column-race
A COLUMN race (vertical bars), built with the per-series raceseries input: men’s tennis
Grand Slam titles 2005→2024, Djokovic climbing to overtake Federer and Nadal, with flags.
// charts-column-race.manic — a COLUMN chart race (vertical bars), built with the
// per-series `raceseries` input (the computed/loop sibling of `racedata`). A sport
// race: the men's tennis Grand Slam count, 2005 → 2024 — watch Djokovic climb from
// nothing to overtake Federer and Nadal. Flags are imported SVG (rs/es/ch/gb).
//
// manic examples/charts-column-race.manic
canvas("16:9");
template("blank");
racechart(gs, "column", "2005 2009 2013 2017 2021 2024",
"Men's Tennis — Grand Slam Titles, 2005 to 2024");
// one entity per player: raceseries(chart, "name", "flag", "v0 v1 …")
raceseries(gs, "Federer", "ch", "5 15 17 19 20 20");
raceseries(gs, "Nadal", "es", "4 6 13 16 20 22");
raceseries(gs, "Djokovic", "rs", "0 1 6 12 20 24");
raceseries(gs, "Murray", "gb", "0 0 2 3 3 3");
race(gs, 10);
charts-line-race
A LINE race: each series draws its line on left-to-right as the years tick, value/label/flag riding the leading tip; the race is which line climbs highest. Fixed axis, decluttered tips.
// charts-line-race.manic — a LINE chart race. Each series draws its line on
// (left → right) as the years tick, with the value, label and flag riding the
// leading tip; the "race" is which line climbs highest. Fixed y-axis (line charts
// don't rescale), L-shaped axes, flags are imported SVG.
//
// manic examples/charts-line-race.manic
canvas("16:9");
template("blank");
racechart(gdp, "line", "1990 2000 2010 2020 2026",
"GDP Over Time — a Line Race ($T)");
racedata(gdp, "
USA, us, 5.96, 10.25, 15.05, 21.06, 30.34
China, cn, 0.36, 1.21, 6.09, 14.69, 20.50
Japan, jp, 3.13, 4.97, 5.76, 5.06, 4.40
Germany, de, 1.60, 1.95, 3.40, 3.89, 4.90
India, in, 0.32, 0.47, 1.68, 2.67, 4.60
");
race(gdp, 10);
charts-bar-line
The bar+line combo: a GDP bar race with a companion raceline across the top showing the
auto-summed world Total, drawn on with a marker and live readout, synced to the race.
// charts-bar-line.manic — a BAR race with a companion LINE (the classic combo).
//
// The bars race as usual (reordering by value), and `raceline` draws a synced line
// across the top strip — here the running TOTAL, auto-summed from the bars (pass
// your own "v0 v1 …" to track a different metric). The line draws on, a marker
// rides its leading point, and a live readout counts up — all locked to the race.
//
// manic examples/charts-bar-line.manic
canvas("16:9");
template("blank");
racechart(gdp, "bar", "1990 2000 2010 2020 2026",
"World GDP — Who's Biggest, and the Total Growing ($T)");
racedata(gdp, "
USA, us, 5.96, 10.25, 15.05, 21.06, 30.34
China, cn, 0.36, 1.21, 6.09, 14.69, 20.50
Japan, jp, 3.13, 4.97, 5.76, 5.06, 4.40
Germany, de, 1.60, 1.95, 3.40, 3.89, 4.90
India, in, 0.32, 0.47, 1.68, 2.67, 4.60
");
// the companion line across the top — a running total of all bars (auto-summed)
raceline(gdp, "World total");
race(gdp, 12);
charts-german-elections
Bar race + a multi-line history racepanel below (the Flourish bar+line layout), from a
REAL CSV: German federal election vote share 1949→2021, 7 parties, missing early years as
0, Total 100. Every party is a bar up top and a color-matched line in the history panel.
// charts-german-elections.manic — a BAR race from real CSV data (German federal
// election vote share, %, 1949-2021). Transcribed from Data.1785183258253.csv:
// the empty separator column is dropped and missing early years (parties that
// didn't exist yet) are 0. Watch CDU/CSU and SPD trade the lead while the Greens,
// Left and AfD climb from nothing.
//
// manic examples/charts-german-elections.manic
canvas("16:9");
template("blank");
racechart(de, "bar", "1949 1953 1957 1961 1965 1969 1972 1976 1980 1983 1987 1990 1994 1998 2002 2005 2009 2013 2017 2021",
"German Federal Elections — Vote Share %, 1949 to 2021");
racedata(de, "
CDU/CSU, 31, 45.2, 50.2, 45.3, 47.6, 46.1, 44.9, 48.6, 44.5, 48.8, 44.3, 43.8, 41.4, 35.1, 38.5, 35.2, 33.8, 41.5, 32.9, 24.1
SPD, 29.2, 28.8, 31.8, 36.2, 39.3, 42.7, 45.8, 42.6, 42.9, 38.2, 37, 33.5, 36.4, 40.9, 38.5, 34.2, 23, 25.7, 20.5, 25.7
FDP, 11.9, 9.5, 7.7, 12.8, 9.5, 5.8, 8.4, 7.9, 10.6, 7, 9.1, 11, 6.9, 6.2, 7.4, 9.8, 14.6, 4.8, 10.7, 11.5
Green, 0, 0, 0, 0, 0, 0, 0, 0, 1.5, 5.6, 8.3, 5.1, 7.3, 6.7, 8.6, 8.1, 10.7, 8.4, 8.9, 14.8
Left, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.4, 4.4, 5.1, 4, 8.7, 11.9, 8.6, 9.2, 4.9
AfD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.7, 12.6, 10.3
Other, 27.6, 16.3, 10.2, 5.6, 3.6, 5.4, 0.9, 0.9, 0.5, 0.4, 1.3, 4.2, 3.6, 6, 3, 4, 6, 6.3, 5.2, 8.6
");
// a synced multi-line history panel below the bars: every party as a line, drawn
// up to a moving time-cursor (the Flourish bar+line combo)
racepanel(de);
race(de, 16);
Boolean shapes
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
boolean
Union / intersection / difference of shapes.
// Boolean Ops — combine two shapes into a new region: union, intersection,
// difference, exclusion (xor). Each cell overlaps a square (cyan outline) and
// a circle (magenta outline); the filled lime shape is the result.
//
// manic examples/boolean.manic
// manic examples/boolean.manic --record out --fps 60
title("Boolean Ops");
canvas(1280, 720);
text(head, (640, 118), "boolean shape ops");
display(head); color(head, cyan); size(head, 36); hidden(head);
// --- union (top-left) ---
rect(aS, (330, 300), 130, 130); outlined(aS); outline(aS, cyan); opacity(aS, 0.4);
circle(aC, (400, 250), 78); outlined(aC); outline(aC, magenta); opacity(aC, 0.4);
union(aR, aS, aC, lime); hidden(aR);
text(aL, (365, 430), "union"); color(aL, dim); size(aL, 22);
// --- intersection (top-right) ---
rect(bS, (880, 300), 130, 130); outlined(bS); outline(bS, cyan); opacity(bS, 0.4);
circle(bC, (950, 250), 78); outlined(bC); outline(bC, magenta); opacity(bC, 0.4);
intersect(bR, bS, bC, lime); hidden(bR);
text(bL, (915, 430), "intersection"); color(bL, dim); size(bL, 20);
// --- difference (bottom-left): square minus circle ---
rect(cS, (330, 545), 130, 130); outlined(cS); outline(cS, cyan); opacity(cS, 0.4);
circle(cC, (400, 495), 78); outlined(cC); outline(cC, magenta); opacity(cC, 0.4);
difference(cR, cS, cC, lime); hidden(cR);
text(cL, (355, 675), "difference (rect - circle)"); color(cL, dim); size(cL, 18);
// --- exclusion / xor (bottom-right) ---
rect(dS, (880, 545), 130, 130); outlined(dS); outline(dS, cyan); opacity(dS, 0.4);
circle(dC, (950, 495), 78); outlined(dC); outline(dC, magenta); opacity(dC, 0.4);
xor(dR, dS, dC, lime); hidden(dR);
text(dL, (915, 675), "exclusion (xor)"); color(dL, dim); size(dL, 18);
// --- script: reveal each result in turn ---
show(head, 0.5);
stagger(0.3) {
show(aR, 0.4);
show(bR, 0.4);
show(cR, 0.4);
show(dR, 0.4);
}
wait(1.5);
3D scenes
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video). See the Going 3D chapter for the words used here. three-d-v2-lab uses the bundled geometry-only asset asset:models/manic-pyramid.obj; production packages install it automatically, or you can replace its model3 call with a built-in solid.
wireframe-spheres
A drifting field of triangulated wireframe spheres, each breathe-ing with a per-index phase so the
field pulses like soap bubbles. finish3(id,"wire=1") renders any solid as bright see-through edges.
// wireframe-spheres — a drifting field of triangulated wireframe spheres, each
// BREATHING (radius oscillating) with a phase tied to its index, so the field
// pulses like a shoal of soap bubbles. The breathing is the 3-D echo of the 2-D
// `wheel-radial` wave; `finish3(id, "wire=1")` renders any solid as its bright
// triangulated wireframe with no filled faces (works on cube/prism/pyramid too).
//
// manic examples/wireframe-spheres.manic
canvas(1080, 1080);
template("black");
camera3((0, -15, 1.5), (0, 0, 0), 56);
let m = 66;
for i in 0..m {
let x = 9.5 * sin(i * 2.399 + 0.5);
let y = 8.5 * sin(i * 1.13 + 1.7);
let z = 6.5 * sin(i * 3.11 + 0.3);
let r = 0.5 + 0.85 * sin(i * 1.7) * sin(i * 1.7);
sphere3(s{i}, (x, y, z), r);
finish3(s{i}, "wire=1");
hue(s{i}, 360 * i / m); // a computed rainbow — hue works on 3D now
}
// every sphere breathes at once, but each with its OWN amplitude, period and
// phase (deterministic pseudo-random from the index — manic has no rand()), so
// the bubbles pulse by different amounts instead of in lockstep.
par {
for i in 0..m {
let ra = 0.5 + 0.5 * sin(i * 12.9 + 0.5); // 0..1 pseudo-random
let rb = 0.5 + 0.5 * sin(i * 7.7 + 2.3);
let rc = 0.5 + 0.5 * sin(i * 5.3 + 4.1);
let amp = 0.28 + 0.55 * ra * ra; // breath size: ~0.28..0.83
let per = 2.0 + 2.2 * rb; // breath rate: 2.0..4.2 s
breathe(s{i}, per, amp, rc, 14);
}
}
three-d-v2
The compact 3D V2 reference: frame a tagged craft, attach its parts, travel one persistent subject along a spatial route, deploy the rig with a rigid turn, and become the final blueprint — five creator words in one continuous scene.
// three-d-v2.manic — the five creator-first spatial motion words
title("3D V2 — Five Words for Spatial Stories");
canvas("16:9");
template("shorts");
watermark(manicMark, (126, 64), "Made With Manic");
creator(me, "@anish2good name=Manic_3D tagline=Spatial_stories yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social cta=Create_yours safe=clean");
socials(me);
camera3((10, -13, 8), (0, 0, 1), 42);
grid3(floor, (0, 0, 0), 6, 1); color(floor, dim); opacity(floor, 0.24);
// A route can be a line3, arrow3, or curve3.
curve3(route, "-4 + 8*t", "-2 + 3*sin(pi*t)", "0.8 + 3.2*sin(pi*t)", (0, 1));
color(route, cyan); thick(route, 0.035); untraced(route);
cube3(ship, (-4, -2, 0.8), (1.4, 1.0, 0.65)); color(ship, gold);
sphere3(sensor, (-4, -2, 1.65), 0.34); color(sensor, cyan);
cube3(leftWing, (-4, -3.15, 0.8), (0.18, 1.8, 0.10)); color(leftWing, magenta);
cube3(rightWing, (-4, -0.85, 0.8), (0.18, 1.8, 0.10)); color(rightWing, magenta);
tag(ship, spacecraft); tag(sensor, spacecraft);
tag(leftWing, spacecraft); tag(rightWing, spacecraft);
tag(leftWing, wings); tag(rightWing, wings);
// A hidden target is a blueprint. become3 keeps `ship` as the persistent id.
sphere3(observatoryBlueprint, (4, -2, 0.95), 0.88);
color(observatoryBlueprint, lime); hidden(observatoryBlueprint);
text(caption, (640, 622), "FRAME · FOLLOW · TRAVEL · TRANSFORM · TURN");
size(caption, 24); bold(caption); color(caption, fg); hidden(caption);
step("frame-the-route") {
par {
view3(route, "fit", 1.0, smooth, 1.30);
show(caption, 0.45);
}
}
step("travel-as-one-system") {
attach3(sensor, ship, (0, 0, 0.85));
attach3(leftWing, ship, (0, -1.15, 0));
attach3(rightWing, ship, (0, 1.15, 0));
par {
draw(route, 2.8, smooth);
travel3(ship, route, 2.8, smooth);
say(caption, "attach3 + travel3", 0.55, smooth);
}
}
step("deploy-and-transform") {
seq {
attach3(sensor, none);
attach3(leftWing, none);
attach3(rightWing, none);
par {
turn3(wings, ship, z, 90, 1.1, smooth);
become3(ship, observatoryBlueprint, 1.1, smooth);
say(caption, "turn3 + become3", 0.55, smooth);
}
par {
view3(spacecraft, "isometric", 1.2, smooth, 1.35);
say(caption, "view3 settles the final composition", 0.55, smooth);
fade(route, 0.65);
}
wait(1.2);
}
}
three-d-v2-story
A vertical creator story about a satellite finding orbit. The same mission survives assembly, launch, transformation, deployment, and screen-aware camera composition without a scene reset.
// three-d-v2-story.manic — a vertical creator story built on 3D V2
title("From Signal to Satellite — A 3D Motion Story");
canvas("9:16");
template("shorts");
watermark(manicMark, (164, 86), "Made With Manic");
creator(me, "@anish2good name=Manic_3D tagline=Make_space_understandable yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social cta=Create_yours safe=reels");
socials(me);
text(kicker, (540, 164), "A 3D MOTION STORY");
size(kicker, 22); bold(kicker); color(kicker, cyan); hidden(kicker);
text(headline, (540, 226), "How a satellite finds its orbit");
size(headline, 39); bold(headline); color(headline, fg); hidden(headline);
text(chapter, (540, 300), "ACT 1 · BUILD ONE SYSTEM");
size(chapter, 23); bold(chapter); color(chapter, gold); hidden(chapter);
text(narration, (540, 1472), "Three parts. One mission.");
size(narration, 30); bold(narration); color(narration, fg); hidden(narration);
text(principle, (540, 1544), "Spatial motion stays readable when relationships survive the move.");
size(principle, 22); color(principle, dim); wrap(principle, 820); hidden(principle);
camera3((10, -14, 8), (0, 0, 1), 42);
grid3(floor, (0, 0, 0), 7, 1); color(floor, dim); opacity(floor, 0.20);
curve3(launchRoute, "-4 + 7*t", "-2 + 2.6*sin(pi*t)", "0.9 + 4.1*sin(pi*t)", (0, 1));
color(launchRoute, cyan); thick(launchRoute, 0.045); untraced(launchRoute);
cube3(core, (-4, -2, 0.9), (1.25, 0.95, 0.72)); color(core, gold); hidden(core);
sphere3(lens, (-4, -2, 1.78), 0.34); color(lens, cyan); hidden(lens);
cube3(panelA, (-4, -3.30, 0.9), (0.18, 2.1, 0.10)); color(panelA, magenta); hidden(panelA);
cube3(panelB, (-4, -0.70, 0.9), (0.18, 2.1, 0.10)); color(panelB, magenta); hidden(panelB);
tag(core, mission); tag(lens, mission); tag(panelA, mission); tag(panelB, mission);
tag(panelA, solarArray); tag(panelB, solarArray);
// The final observatory is only a blueprint. It never appears as a second object.
sphere3(observatory, (3, -2, 1.05), 0.86); color(observatory, lime); hidden(observatory);
text(lensLabel, (0, 0), "signal"); size(lensLabel, 21); color(lensLabel, cyan); hidden(lensLabel);
pin3(lensLabel, lens);
step("assemble") {
seq {
par {
show(kicker, 0.35);
show(headline, 0.50);
show(chapter, 0.35);
show(narration, 0.40);
show(principle, 0.40);
view3(mission, "isometric", 1.0, smooth, 1.55);
}
stagger(0.12) {
show(core, 0.35);
show(lens, 0.35);
show(panelA, 0.35);
show(panelB, 0.35);
}
attach3(lens, core, (0, 0, 0.88));
attach3(panelA, core, (0, -1.30, 0));
attach3(panelB, core, (0, 1.30, 0));
show(lensLabel, 0.30);
wait(0.45);
}
}
step("launch") {
seq {
par {
say(chapter, "ACT 2 · MOVE THE RELATIONSHIP", 0.45, smooth);
say(narration, "The craft travels. Every attached part remembers where it belongs.", 0.55, smooth);
view3(launchRoute, "fit", 1.1, smooth, 1.48);
}
par {
draw(launchRoute, 3.2, smooth);
travel3(core, launchRoute, 3.2, smooth);
say(principle, "attach3 carries context · travel3 carries identity", 0.55, smooth);
}
wait(0.45);
}
}
step("deploy") {
seq {
attach3(lens, none);
attach3(panelA, none);
attach3(panelB, none);
par {
say(chapter, "ACT 3 · DEPLOY THE IDEA", 0.45, smooth);
say(narration, "At orbit, only the changing parts move.", 0.50, smooth);
turn3(solarArray, core, z, 90, 1.25, smooth);
become3(core, observatory, 1.25, smooth);
}
par {
view3(mission, "isometric", 1.4, smooth, 1.58);
say(principle, "turn3 preserves the rig · become3 preserves the subject", 0.55, smooth);
fade(launchRoute, 0.65);
}
par {
view3(mission, "front", 2.2, smooth, 1.24);
pulse(lens, 0.75);
say(narration, "One persistent object. One continuous story.", 0.55, smooth);
}
wait(1.8);
}
}
three-d-v2-lab
A creator-first spatial lab: safe-aware framing, rigid assembly, a live projection and edge, a moving route, surface contour, depth-scaled label, bounded finishes, variable tube, and controlled OBJ geometry in one continuous story.
canvas(1080, 1920);
template("neon");
title("3D relationships, not keyframes");
creator(me, "@anish2good name=Manic yt=zarigatongy x=@anish2good web=8gwifi.org/manic footer=social safe=reels");
socials(me);
watermark(mark, (180, 88), "Made With Manic");
camera3((8, -10, 7), (0, 0, 1), 42);
grid3(floor, (0, 0, 0), 4, 1); color(floor, dim); opacity(floor, 0.28);
text(kicker, (540, 174), "SPATIAL STORY"); size(kicker, 22); color(kicker, cyan);
text(headline, (540, 242), "One point. Three relationships."); size(headline, 46);
text(caption, (540, 1350), "Move the idea — the construction follows."); size(caption, 27); color(caption, dim);
surface3(bowl, "0.16*(x^2+y^2)", (-3,3), (-3,3), 24);
color(bowl, blue); opacity(bowl, 0.42);
finish3(bowl, "shading=smooth mesh=0.18 depth=0.25 shadow=0.15");
contour3(level, bowl, 0.7); color(level, gold); thick(level, 0.025);
model3(beacon, "asset:models/manic-pyramid.obj", (-1.5, -0.4, 0.05), 0.8);
color(beacon, magenta);
finish3(beacon, "material=metal shading=flat mesh=0.20 depth=0.18 shadow=0.30");
point3(probe, (0, 0, 0), 0.15); color(probe, lime);
attach3(probe, beacon, (0, 0, 2.0), rigid);
project3(shadow, probe, "xy"); color(shadow, cyan);
link3(drop, probe, shadow, 0.12); color(drop, cyan); thick(drop, 0.018);
text(probeLabel, (0,0), "live point"); size(probeLabel, 28); color(probeLabel, lime);
label3(probeLabel, probe, 0.34);
curve3(route, "3*cos(pi*t)", "2.4*sin(pi*t)", "0.8+1.4*t", (0,1));
color(route, gold); untraced(route);
curve3(spine, "-2.8+1.2*t", "-2.2", "0.15+1.8*t", (0,1)); hidden(spine);
tube3(signal, spine, "0.025+0.075*t", 10); color(signal, gold); opacity(signal, 0.58);
finish3(signal, "material=metal shading=smooth depth=0.20"); hidden(signal);
tag(bowl, lab); tag(level, lab); tag(beacon, lab); tag(probe, lab);
tag(shadow, lab); tag(drop, lab); tag(route, lab); tag(signal, lab);
tag(spine, lab);
step("frame the safe media area") {
par {
show(kicker, 0.35);
show(headline, 0.55);
show(caption, 0.45);
view3(lab, "isometric", 1.1, smooth, 1.55);
}
wait(0.5);
}
step("rigid attachment") {
par {
turn3(beacon, beacon, z, 110, 1.5, smooth);
orbit3(42, 28, 10.8, 1.5, smooth);
}
wait(0.35);
}
step("live projection") {
par {
shift3(beacon, (2.4, 0.8, 0.3), 1.5, smooth);
look3((0.4, 0.2, 1.1), 1.5, smooth);
}
wait(0.35);
}
step("transformed path travel") {
attach3(probe, none);
par {
draw(route, 2.2, smooth);
travel3(probe, route, 2.2, smooth);
rotate3(route, (0, 0, 24), 2.2, smooth);
show(signal, 0.55);
}
wait(0.7);
}
step("one bounded finish") {
flash(level, gold);
view3(lab, "fit", 1.0, smooth, 1.55);
wait(1.0);
}
creator-coordinate-worlds
Solve the classic 3 × 4 × 12 cuboid diagonal as a complete creator story: isolate the 3 × 4 floor in native 2D, reuse its 5-unit diagonal in textbook-projected 3D, derive the 13-unit answer, then orbit the same retained frame3 in spatial 3D.
// A complete textbook problem told with three truthful presentations:
// 2D isolates the useful right triangle, textbook 3D builds the cuboid cleanly,
// and spatial 3D confirms that the final segment really crosses the volume.
title("The 3–4–12 Box — Find the Space Diagonal");
canvas("9:16");
template("paper");
creator(me,"@anish2good name=Manic_Geometry tagline=Textbooks_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact safe=clean");
socials(me);
watermark(manicMark,(w*0.17,h*0.065),"Made With Manic");
endcard(me,"title=Make_Math_Move cta=8gwifi.org/manic");
let u = (w+h-abs(w-h))/2160;
text(kicker,(cx,h*0.075),"MANIC · PYTHAGORAS IN 3D"); size(kicker,22*u); bold(kicker); color(kicker,cyan); hidden(kicker);
text(headline,(cx,h*0.13),"A box is 3 × 4 × 12. Find its space diagonal."); size(headline,36*u); bold(headline); wrap(headline,w*0.82); hidden(headline);
text(chapter,(cx,h*0.205),"THE PROBLEM"); size(chapter,23*u); bold(chapter); color(chapter,gold); hidden(chapter);
text(caption,(cx,h*0.805),"Connect one corner to the opposite corner—through the box."); size(caption,24*u); bold(caption); wrap(caption,w*0.78); hidden(caption);
text(note,(cx,h*0.855),"Hint: solve one right triangle at a time."); size(note,21*u); color(note,dim); wrap(note,w*0.78); hidden(note);
equation(work,(cx,h*0.735),`D=?`,43*u); hidden(work);
// ---------------------------------------------------------------------------
// 2D: isolate the 3 × 4 floor. This is the truthful view for the first triangle.
// ---------------------------------------------------------------------------
let lx = w*0.24;
let rx = w*0.76;
let ty = h*0.36;
let by = h*0.60;
line(floorBottom,(lx,by),(rx,by)); color(floorBottom,cyan); stroke(floorBottom,4*u); untraced(floorBottom); hidden(floorBottom); tag(floorBottom,floor2d);
line(floorRight,(rx,by),(rx,ty)); color(floorRight,magenta); stroke(floorRight,4*u); untraced(floorRight); hidden(floorRight); tag(floorRight,floor2d);
line(floorTop,(rx,ty),(lx,ty)); color(floorTop,cyan); stroke(floorTop,4*u); untraced(floorTop); hidden(floorTop); tag(floorTop,floor2d);
line(floorLeft,(lx,ty),(lx,by)); color(floorLeft,magenta); stroke(floorLeft,4*u); untraced(floorLeft); hidden(floorLeft); tag(floorLeft,floor2d);
line(floorDiagonal2,(lx,by),(rx,ty)); color(floorDiagonal2,gold); stroke(floorDiagonal2,6*u); untraced(floorDiagonal2); hidden(floorDiagonal2); tag(floorDiagonal2,floor2d);
dot(A2,(lx,by),9*u); filled(A2); color(A2,fg); hidden(A2); tag(A2,floor2d);
dot(C2,(rx,ty),9*u); filled(C2); color(C2,fg); hidden(C2); tag(C2,floor2d);
text(a2,(lx-22*u,by+24*u),"A"); size(a2,22*u); bold(a2); hidden(a2); tag(a2,floor2d);
text(c2,(rx+22*u,ty-20*u),"C"); size(c2,22*u); bold(c2); hidden(c2); tag(c2,floor2d);
text(side4,(cx,by+32*u),"4"); size(side4,24*u); bold(side4); color(side4,cyan); hidden(side4); tag(side4,floor2d);
text(side3,(rx+30*u,(ty+by)/2),"3"); size(side3,24*u); bold(side3); color(side3,magenta); hidden(side3); tag(side3,floor2d);
text(base5,(cx+22*u,(ty+by)/2-18*u),"5"); size(base5,25*u); bold(base5); color(base5,gold); hidden(base5); tag(base5,floor2d);
// ---------------------------------------------------------------------------
// 3D: the scientific frame retains the real 0..4, 0..3, 0..12 coordinates.
// Its display cube is intentionally balanced; the numbered axes keep the data
// honest while the space-diagonal geometry stays readable on a phone.
// ---------------------------------------------------------------------------
camera3((9,-11,8),(0,0,0),17,orthographic);
frame3(boxFrame,(0,0,0),(6,6,6),"x=0..4 y=0..3 z=0..12 xmajor=1 ymajor=1 zmajor=4 planes=xy:min,xz:min,yz:min mode=textbook");
hidden(boxFrame);
// Data-to-display corners of frame3:
// (0,0,0) -> (-3,-3,-3), (4,3,0) -> (3,3,-3), (4,3,12) -> (3,3,3).
point3(A3,(-3,-3,-3),0.15); color(A3,fg); hidden(A3); tag(A3,boxStory);
point3(C3,(3,3,-3),0.15); color(C3,gold); hidden(C3); tag(C3,boxStory);
point3(B3,(3,3,3),0.18); color(B3,lime); hidden(B3); tag(B3,boxStory);
line3(floorDiagonal3,(-3,-3,-3),(3,3,-3)); color(floorDiagonal3,gold); thick(floorDiagonal3,0.075); untraced(floorDiagonal3); hidden(floorDiagonal3); tag(floorDiagonal3,boxStory);
line3(height3,(3,3,-3),(3,3,3)); color(height3,magenta); thick(height3,0.075); untraced(height3); hidden(height3); tag(height3,boxStory);
line3(spaceDiagonal,(-3,-3,-3),(3,3,3)); color(spaceDiagonal,lime); thick(spaceDiagonal,0.105); untraced(spaceDiagonal); hidden(spaceDiagonal); tag(spaceDiagonal,boxStory);
text(a3,(0,0),"A"); size(a3,24*u); bold(a3); hidden(a3); pin3(a3,A3,(-18,16)); tag(a3,boxStory);
text(c3,(0,0),"C"); size(c3,24*u); bold(c3); color(c3,gold); hidden(c3); pin3(c3,C3,(16,14)); tag(c3,boxStory);
text(b3,(0,0),"B"); size(b3,24*u); bold(b3); color(b3,lime); hidden(b3); pin3(b3,B3,(16,-14)); tag(b3,boxStory);
point3(floorMid,(0,0,-3),0.01); hidden(floorMid);
point3(heightMid,(3,3,0),0.01); hidden(heightMid);
point3(spaceMid,(0,0,0),0.01); hidden(spaceMid);
text(floorLabel,(0,0),"AC = 5"); size(floorLabel,21*u); bold(floorLabel); color(floorLabel,gold); hidden(floorLabel); pin3(floorLabel,floorMid,(10,18)); tag(floorLabel,boxStory);
text(heightLabel,(0,0),"CB = 12"); size(heightLabel,21*u); bold(heightLabel); color(heightLabel,magenta); hidden(heightLabel); pin3(heightLabel,heightMid,(18,0)); tag(heightLabel,boxStory);
text(spaceLabel,(0,0),"AB = 13"); size(spaceLabel,22*u); bold(spaceLabel); color(spaceLabel,lime); hidden(spaceLabel); pin3(spaceLabel,spaceMid,(-18,-22)); tag(spaceLabel,boxStory);
// ---------------------------------------------------------------------------
// THE SOLUTION
// ---------------------------------------------------------------------------
step("pose the box problem") {
seq {
par { show(kicker,0.35); show(headline,0.50); show(chapter,0.40); show(caption,0.45); show(note,0.40); show(work,0.40); }
view3(boxFrame,"isometric",0.80,smooth,1.35);
show(boxFrame,0.75);
stagger(0.10) { show(A3,0.25); show(a3,0.25); show(B3,0.25); show(b3,0.25); }
par { show(spaceDiagonal,0.15); draw(spaceDiagonal,1.20,smooth); }
pulse(spaceDiagonal,0.65);
wait(0.60);
}
}
step("find the hidden floor diagonal") {
seq {
par {
fade(boxFrame,0.50);
fade(boxStory,0.50);
say(chapter,"1 · SOLVE THE 2D FLOOR",0.40,smooth);
say(caption,"The floor is a 3 × 4 rectangle. Its diagonal is the first hypotenuse.",0.50,smooth);
say(note,"This part is planar—so 2D is the clearest explanation.",0.45,smooth);
rewrite(work,`AC^2=3^2+4^2`,0.65,smooth);
}
stagger(0.10) {
show(floorBottom,0.10); draw(floorBottom,0.55,smooth);
show(floorRight,0.10); draw(floorRight,0.45,smooth);
show(floorTop,0.10); draw(floorTop,0.55,smooth);
show(floorLeft,0.10); draw(floorLeft,0.45,smooth);
show(A2,0.20); show(a2,0.20); show(C2,0.20); show(c2,0.20);
show(side4,0.25); show(side3,0.25);
}
par {
show(floorDiagonal2,0.10);
draw(floorDiagonal2,1.10,smooth);
rewrite(work,`AC=\sqrt{3^2+4^2}=\sqrt{25}=\textcolor{gold}{5}`,1.15,smooth);
}
show(base5,0.30);
pulse(floorDiagonal2,0.65);
wait(0.65);
}
}
step("use that answer inside the box") {
seq {
par {
fade(floor2d,0.50);
say(chapter,"2 · BUILD THE 3D RIGHT TRIANGLE",0.40,smooth);
say(caption,"Now AC = 5 meets the 12-unit height at a right angle.",0.50,smooth);
say(note,"The same coordinates return as a clean textbook projection.",0.45,smooth);
rewrite(work,`AB^2=AC^2+CB^2`,0.70,smooth);
}
view3(boxFrame,"isometric",0.75,smooth,1.35);
show(boxFrame,0.70);
stagger(0.10) {
show(A3,0.20); show(a3,0.20);
show(C3,0.20); show(c3,0.20);
show(floorDiagonal3,0.10); draw(floorDiagonal3,0.90,smooth); show(floorLabel,0.25);
show(height3,0.10); draw(height3,0.90,smooth); show(heightLabel,0.25);
show(B3,0.20); show(b3,0.20);
}
wait(0.35);
}
}
step("solve the space diagonal") {
seq {
par {
show(spaceDiagonal,0.10);
draw(spaceDiagonal,1.25,smooth);
rewrite(work,`AB=\sqrt{\textcolor{gold}{5^2}+\textcolor{magenta}{12^2}}`,0.95,smooth);
say(caption,"Pythagoras works again—this time across the volume.",0.45,smooth);
}
rewrite(work,`AB=\sqrt{25+144}=\sqrt{169}`,0.85,smooth);
par {
rewrite(work,`\boxed{AB=\textcolor{lime}{13}}`,0.75,smooth);
show(spaceLabel,0.30);
pulse(spaceDiagonal,0.80);
say(note,"Two right triangles · one exact answer",0.40,smooth);
}
wait(0.80);
}
}
step("see why the answer is spatial") {
seq {
par {
present3(boxFrame,spatial,0.70,smooth);
say(chapter,"3 · REVEAL THE SPATIAL TRUTH",0.40,smooth);
say(caption,"Orbit the same construction: AB crosses all three dimensions.",0.50,smooth);
say(note,"2D to calculate · textbook 3D to explain · spatial 3D to understand",0.50,smooth);
}
par { orbit3(48,26,12,1.80,smooth); pulse(spaceDiagonal,0.70); }
par { orbit3(132,34,12,1.80,smooth); pulse(boxFrame.axes,0.70); }
wait(0.85);
}
}
step("creator call to action") {
par {
fade(boxFrame,0.45);
fade(boxStory,0.45);
fade(work,0.35);
fade(chapter,0.35);
fade(caption,0.35);
fade(note,0.35);
show(me.endcard,0.60);
}
wait(1.80);
}
creator-point-to-plane-distance
A pure-3D vector-geometry Short: read the normal of x+y+z=3, project P=(2,2,5) onto the plane, construct the spatial right angle, derive the exact distance 2√3, and orbit the persistent point-plane proof.
// A pure-3D textbook problem. Every geometric object stays spatial:
// a point, a plane, its normal, the perpendicular foot, and the shortest path.
title("Shortest Distance from a Point to a Plane");
canvas("9:16");
template("neon");
creator(me,"@anish2good name=Manic_Geometry tagline=3D_math_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact safe=clean");
socials(me);
watermark(manicMark,(w*0.17,h*0.065),"Made With Manic");
endcard(me,"title=Make_3D_Math_Move cta=8gwifi.org/manic");
let u = (w+h-abs(w-h))/2160;
text(kicker,(cx,h*0.075),"MANIC · VECTOR GEOMETRY"); size(kicker,22*u); bold(kicker); color(kicker,cyan); hidden(kicker);
text(headline,(cx,h*0.125),"How far is a point from a plane?"); size(headline,38*u); bold(headline); wrap(headline,w*0.82); hidden(headline);
text(chapter,(cx,h*0.185),"THE SHORTEST PATH MUST BE PERPENDICULAR"); size(chapter,22*u); bold(chapter); color(chapter,gold); wrap(chapter,w*0.82); hidden(chapter);
equation(problem,(cx,h*0.235),`P=(2,2,5),\qquad \Pi:x+y+z=3`,36*u); hidden(problem);
equation(work,(cx,h*0.745),`d(P,\Pi)=?`,43*u); hidden(work);
text(caption,(cx,h*0.815),"A slanted route is longer. The normal reveals the minimum."); size(caption,24*u); bold(caption); wrap(caption,w*0.80); hidden(caption);
text(note,(cx,h*0.86),"Find the direction · locate the foot · measure the segment"); size(note,21*u); color(note,dim); wrap(note,w*0.80); hidden(note);
// The frame uses the same physical coordinates as the problem:
// x,y in [-3,3] and z in [0,6] map one-to-one into a 6-unit display cube.
// A perspective three-quarter view balances both facts: the plane reads as a
// full surface, while its perpendicular keeps enough screen length to remain
// obvious. This story never switches to an orthographic/textbook projection.
camera3((20,-5,17),(0,0,2.8),42);
frame3(world,(0,0,3),(6,6,6),"x=-3..3 y=-3..3 z=0..6 xmajor=1 ymajor=1 zmajor=2 planes=xy:min mode=spatial");
hidden(world);
// Pi: x+y+z=3, rendered as z=3-x-y over a bounded patch.
surface3(plane,"3-x-y",(-1.5,1.5),(-1.5,1.5),20);
color(plane,cyan);
finish3(plane,"material=glass shading=flat mesh=0.38 depth=0.32 shadow=0.12");
hidden(plane);
// Crisp boundary keeps the translucent plane readable on a phone.
line3(edge0,(-1.5,-1.5,6),(1.5,-1.5,3)); color(edge0,cyan); thick(edge0,0.045); untraced(edge0); hidden(edge0); tag(edge0,planeEdge);
line3(edge1,(1.5,-1.5,3),(1.5,1.5,0)); color(edge1,cyan); thick(edge1,0.045); untraced(edge1); hidden(edge1); tag(edge1,planeEdge);
line3(edge2,(1.5,1.5,0),(-1.5,1.5,3)); color(edge2,cyan); thick(edge2,0.045); untraced(edge2); hidden(edge2); tag(edge2,planeEdge);
line3(edge3,(-1.5,1.5,3),(-1.5,-1.5,6)); color(edge3,cyan); thick(edge3,0.045); untraced(edge3); hidden(edge3); tag(edge3,planeEdge);
// P is the given point. Q is its perpendicular projection onto Pi.
point3(P,(2,2,5),0.20); color(P,magenta); hidden(P); tag(P,geometry);
point3(Q,(0,0,3),0.18); color(Q,gold); hidden(Q); tag(Q,geometry);
text(pLabel,(0,0),"P (2, 2, 5)"); size(pLabel,22*u); bold(pLabel); color(pLabel,magenta); hidden(pLabel); pin3(pLabel,P,(20,-18)); tag(pLabel,geometry);
text(qLabel,(0,0),"Q (0, 0, 3)"); size(qLabel,22*u); bold(qLabel); color(qLabel,gold); hidden(qLabel); pin3(qLabel,Q,(-24,20)); tag(qLabel,geometry);
// The gradient of x+y+z-3 is n=(1,1,1).
arrow3(normal,(0,0,3),(1.15,1.15,4.15)); color(normal,gold); thick(normal,0.075); untraced(normal); hidden(normal);
point3(nTip,(1.15,1.15,4.15),0.01); hidden(nTip);
text(nLabel,(0,0),"n = (1, 1, 1)"); size(nLabel,21*u); bold(nLabel); color(nLabel,gold); hidden(nLabel); pin3(nLabel,nTip,(18,-14));
// The minimum-distance segment is parallel to n and ends at Q.
line3(distanceLine,(0,0,3),(2,2,5)); color(distanceLine,lime); thick(distanceLine,0.105); untraced(distanceLine); hidden(distanceLine); tag(distanceLine,geometry);
point3(distanceMid,(1,1,4),0.01); hidden(distanceMid);
text(distanceLabel,(0,0),"d = 2√3"); size(distanceLabel,23*u); bold(distanceLabel); color(distanceLabel,lime); hidden(distanceLabel); pin3(distanceLabel,distanceMid,(-22,-20)); tag(distanceLabel,geometry);
// A small spatial right-angle mark: one side lies in Pi, one follows n.
line3(right0,(0,0,3),(0.45,-0.45,3)); color(right0,fg); thick(right0,0.045); untraced(right0); hidden(right0); tag(right0,rightMark);
line3(right1,(0.45,-0.45,3),(0.70,-0.20,3.25)); color(right1,fg); thick(right1,0.045); untraced(right1); hidden(right1); tag(right1,rightMark);
line3(right2,(0.70,-0.20,3.25),(0.25,0.25,3.25)); color(right2,fg); thick(right2,0.045); untraced(right2); hidden(right2); tag(right2,rightMark);
step("pose the spatial problem") {
seq {
par {
show(kicker,0.35); show(headline,0.50); show(chapter,0.45);
show(problem,0.55); show(work,0.40); show(caption,0.45); show(note,0.40);
}
show(world,0.70);
par {
show(plane,0.75);
show(edge0,0.10); draw(edge0,0.70,smooth);
show(edge1,0.10); draw(edge1,0.70,smooth);
show(edge2,0.10); draw(edge2,0.70,smooth);
show(edge3,0.10); draw(edge3,0.70,smooth);
}
stagger(0.12) { show(P,0.25); show(pLabel,0.30); pulse(P,0.60); }
wait(0.65);
}
}
step("read the plane normal") {
seq {
par {
say(chapter,"1 · READ THE NORMAL FROM THE PLANE",0.40,smooth);
say(caption,"The coefficients of x, y, and z point perpendicular to Π.",0.50,smooth);
say(note,"For ax + by + cz + d = 0, the normal is (a,b,c).",0.45,smooth);
rewrite(work,`\Pi:x+y+z-3=0`,0.65,smooth);
fade(pLabel,0.30);
}
par {
show(Q,0.25);
show(normal,0.10); draw(normal,1.10,smooth);
show(nLabel,0.35);
rewrite(work,`\mathbf n=(1,1,1)`,0.80,smooth);
}
pulse(normal,0.65);
wait(0.60);
}
}
step("locate the perpendicular foot") {
seq {
par {
fade(normal,0.35); fade(nLabel,0.35);
say(chapter,"2 · DROP THE PERPENDICULAR",0.40,smooth);
say(caption,"Move from P opposite the normal until the plane equation becomes true.",0.50,smooth);
say(note,"Q = P − t n must lie on Π.",0.40,smooth);
rewrite(work,`Q=(2,2,5)-t(1,1,1)`,0.80,smooth);
}
rewrite(work,`(2-t)+(2-t)+(5-t)=3\Rightarrow t=2`,1.00,smooth);
par {
show(distanceLine,0.10); draw(distanceLine,1.20,smooth);
show(right0,0.10); draw(right0,0.55,smooth);
show(right1,0.10); draw(right1,0.45,smooth);
show(right2,0.10); draw(right2,0.55,smooth);
rewrite(work,`Q=(0,0,3)`,0.75,smooth);
}
pulse(Q,0.65);
wait(0.65);
}
}
step("measure the shortest segment") {
seq {
par {
say(chapter,"3 · MEASURE THE NORMAL SEGMENT",0.40,smooth);
say(caption,"The point-to-plane formula measures exactly the green segment PQ.",0.50,smooth);
say(note,"Absolute value gives distance; the denominator normalizes n.",0.45,smooth);
rewrite(work,`d=\frac{|2+2+5-3|}{\sqrt{1^2+1^2+1^2}}`,1.05,smooth);
}
rewrite(work,`d=\frac{6}{\sqrt3}=\textcolor{lime}{2\sqrt3}`,0.90,smooth);
par {
rewrite(work,`\boxed{d(P,\Pi)=\textcolor{lime}{2\sqrt3}}`,0.75,smooth);
show(distanceLabel,0.30);
pulse(distanceLine,0.80);
}
wait(0.75);
}
}
step("orbit the proof") {
seq {
par {
say(chapter,"THE ANSWER IS A SPATIAL RELATIONSHIP",0.45,smooth);
say(caption,"Orbit the proof: PQ stays normal to Π from every viewpoint.",0.50,smooth);
say(note,"One plane · one projection · one shortest path",0.45,smooth);
fade(pLabel,0.35); fade(qLabel,0.35);
}
par { orbit3(38,28,24,1.80,smooth); pulse(distanceLine,0.70); }
par { orbit3(68,30,24,1.80,smooth); pulse(plane,0.70); }
wait(0.80);
}
}
step("creator call to action") {
par {
fade(world,0.45); fade(plane,0.45); fade(planeEdge,0.45);
fade(geometry,0.45); fade(rightMark,0.45);
fade(problem,0.35); fade(work,0.35); fade(chapter,0.35);
fade(caption,0.35); fade(note,0.35);
show(me.endcard,0.60);
}
wait(1.80);
}
frame3-grid-policies
The six Asymptote-inspired scientific grid policies in one review: lower walls, a logarithmic axis, top and explicit planes, coloured parallel sections, major/minor grids, and independent per-axis intervals.
// Review file for the six Asymptote grid3 policies that motivated frame3.
// Each chapter is a normal frame3 declaration, not a renderer special case.
title("Scientific Grid Planes — Asymptote Parity");
canvas("16:9");
template("mono");
watermark(manicMark,(170,70),"Made With Manic");
text(titleText,(640,68),"One frame vocabulary · six grid policies"); size(titleText,30); bold(titleText);
text(caption,(640,670),"Three lower walls"); size(caption,24); bold(caption); color(caption,gold);
camera3((10,-12,8),(0,0,0),15,orthographic);
frame3(walls,(0,0,0),(8,8,6),"x=-2..2 y=-2..2 z=-1..3 planes=xy:min,xz:min,yz:min major=1 mode=textbook");
frame3(logWalls,(0,0,0),(8,8,6),"x=0..4 y=0..4 z=0.1..100 planes=xy:min,xz:min zscale=log major=1 mode=textbook");
frame3(positioned,(0,0,0),(8,8,6),"x=-2..2 y=-2..2 z=-2..2 planes=xy:max,xz:0 major=1 mode=textbook");
frame3(parallel,(0,0,0),(8,8,6),"x=-2..2 y=-2..2 z=-2..2 planes=xy:-0.5@cyan,xy:1.5@magenta major=1 mode=textbook");
frame3(majorMinor,(0,0,0),(8,8,6),"x=-2..2 y=-2..2 z=-1..2 planes=xy:min major=1 minor=0.5 mode=textbook");
frame3(axisPolicy,(0,0,0),(8,8,6),"x=-2..2 y=-2..2 z=-2..2 planes=xy:min,yz:min xmajor=0.5 ymajor=1 zmajor=2 mode=textbook");
hidden(logWalls); hidden(positioned); hidden(parallel); hidden(majorMinor); hidden(axisPolicy);
step("three lower walls") {
show(walls,0.65);
wait(0.70);
}
step("linear floor with logarithmic height") {
par { fade(walls,0.45); show(logWalls,0.60); say(caption,"Linear floor · logarithmic z",0.40,smooth); }
wait(0.70);
}
step("top and explicit-position planes") {
par { fade(logWalls,0.45); show(positioned,0.60); say(caption,"XY at max · XZ at y=0",0.40,smooth); }
wait(0.70);
}
step("parallel colored sections") {
par { fade(positioned,0.45); show(parallel,0.60); say(caption,"Two parallel XY sections",0.40,smooth); }
wait(0.70);
}
step("major and minor lines") {
par { fade(parallel,0.45); show(majorMinor,0.60); say(caption,"Major ticks derive the major grid",0.40,smooth); }
wait(0.70);
}
step("independent axis policy") {
par { fade(majorMinor,0.45); show(axisPolicy,0.60); say(caption,"Each axis chooses its own interval",0.40,smooth); }
present3(axisPolicy,spatial,0.70,smooth);
orbit3(118,30,12,2.10,smooth);
wait(1.40);
}
asymptote-randomwalk3-reference
The recurring Asymptote 3D random-walk families reduced to two readable models: world-axis choices and a local turtle frame with 90°/60° turns.
// The seven Asymptote randomwalk examples reduce to two reusable models:
// six world-axis choices, or a local 3D turtle that turns by 90°/60°.
title("Deterministic 3D Random Walks");
canvas("16:9");
template("mono");
watermark(mark, (w*0.11,h*0.075), "Made With Manic");
text(kicker, (cx,h*0.07), "GENERATIVE 3D · RANDOMWALK3");
text(headline, (cx,h*0.13), "The seed fixes the journey. The model changes its shape.");
text(caption, (cx,h*0.91), "Axis walk · uniform choices · direction colour · camera-aware depth");
size(kicker,19); bold(kicker); color(kicker,dim);
size(headline,32); bold(headline);
size(caption,20); bold(caption); color(caption,gold);
camera3((12,-15,10),(0,0,0),38,orthographic);
randomwalk3(axisWalk, (0,0,0), 12000, 21,
"mode=axis distribution=uniform color=direction shade=depth scale=0.12");
untraced(axisWalk);
randomwalk3(turtle90, (0,0,0), 9000, 21,
"mode=turtle angle=90 distribution=gaussian color=turn shade=depth scale=0.12");
untraced(turtle90); hidden(turtle90);
randomwalk3(turtle60, (0,0,0), 9000, 21,
"mode=turtle angle=60 distribution=uniform color=turn shade=depth scale=0.12");
untraced(turtle60); hidden(turtle60);
step("six world directions") {
par { draw(axisWalk,2.20,smooth); view3(axisWalk,"fit",0.60,smooth,1.20); }
}
wait(0.65);
step("the frame can turn with the walker") {
par {
fade(axisWalk,0.40);
show(turtle90,0.05);
say(caption,"Turtle frame · Gaussian choices · 90° turns",0.40);
}
par { draw(turtle90,2.20,smooth); view3(turtle90,"fit",0.60,smooth,1.20); }
}
wait(0.65);
step("change one angle") {
par {
fade(turtle90,0.40);
show(turtle60,0.05);
say(caption,"Turtle frame · uniform choices · 60° turns",0.40);
}
par { draw(turtle60,2.20,smooth); view3(turtle60,"fit",0.60,smooth,1.20); }
}
wait(0.70);
step("inspect the structure") {
orbit3(115,28,13,2.10,smooth);
}
wait(1.20);
creator-randomwalk3-diffusion
A creator Short grows the same seeded 3D walk from 100 to 10,000 decisions and reveals the large-scale shape hiding inside random local choices.
// Creator short: many random decisions do not create visual noise only—the
// choice model leaves a characteristic 3D signature.
title("Can Randomness Have a Shape?");
canvas("9:16");
template("paper");
creator(me, "@anish2good name=Manic_Generative tagline=Randomness_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=compact cta=Animate_the_invisible safe=clean");
socials(me);
watermark(mark, (w*0.16,h*0.055), "Made With Manic");
endcard(me, "title=Make_Invisible_Systems_Visible cta=8gwifi.org/manic");
let u = (w+h-abs(w-h))/1080;
text(kicker, (cx,h*0.11), "MANIC · RANDOM SYSTEMS");
text(headline, (cx,h*0.18), "Can randomness have a shape?");
text(caption, (cx,h*0.77), "Start at one point. Choose one of six directions.");
text(count, (cx,h*0.70), "100 STEPS");
size(kicker,20*u); bold(kicker); color(kicker,cyan);
size(headline,31*u); bold(headline); wrap(headline,w*0.78);
size(caption,23*u); bold(caption); wrap(caption,w*0.75);
size(count,20*u); bold(count); color(count,gold);
camera3((9,-12,8),(0,0,0),38,orthographic);
randomwalk3(w100,(0,0,0),100,42,"mode=axis color=direction scale=0.25");
randomwalk3(w1000,(0,0,0),1000,42,"mode=axis color=direction shade=depth scale=0.16");
randomwalk3(w10000,(0,0,0),10000,42,"mode=axis color=direction shade=depth scale=0.09");
untraced(w100); untraced(w1000); untraced(w10000);
hidden(w1000); hidden(w10000);
hidden(kicker); hidden(headline); hidden(caption); hidden(count);
step("one random journey") {
par {
show(kicker,0.35); show(headline,0.45); show(caption,0.40); show(count,0.35);
draw(w100,1.25,smooth); view3(w100,"fit",0.55,smooth,1.35);
}
}
wait(0.55);
step("the cloud begins to emerge") {
par {
fade(w100,0.35); show(w1000,0.05);
say(count,"1,000 STEPS",0.30);
say(caption,"More choices reveal a spreading 3D cloud.",0.40);
}
par { draw(w1000,1.75,smooth); view3(w1000,"fit",0.55,smooth,1.35); }
}
wait(0.60);
step("scale exposes the law") {
par {
fade(w1000,0.35); show(w10000,0.05);
say(count,"10,000 STEPS · SAME SEED",0.30);
say(caption,"The individual turns are random. The large-scale diffusion is not.",0.45);
}
par { draw(w10000,2.30,smooth); view3(w10000,"fit",0.60,smooth,1.35); }
orbit3(112,30,11,1.70,smooth);
}
wait(0.85);
step("creator takeaway") {
say(caption,"Manic turns a model and a seed into a camera-ready explanation.",0.45);
recolor(headline,gold,0.45);
}
wait(1.00);
step("call to action") {
par {
fade(kicker,0.30); fade(headline,0.30); fade(caption,0.30); fade(count,0.30);
fade(w10000,0.40); fade(me.footer,0.30); show(me.endcard,0.60);
}
}
wait(1.80);
parameterized-generated-families3
The compact generated-family reference: one ordinary parameter continuously changes a helix, height field, and torus while every object keeps its id, sample topology, material, and timeline identity.
// PARAMETERIZED GENERATED FAMILIES — one ordinary parameter, three stable 3-D
// objects. `p` changes sampled points; ids, materials, transforms, and sample
// topology stay intact. No per-vertex scripting or snapshot replacement.
title("Parameterized Generated Families");
canvas("16:9");
template("mono");
watermark(mark, (170, 55), "Made With Manic");
text(kicker, (640, 48), "MANIC · GENERATED 3-D FAMILIES");
text(headline, (640, 92), "One parameter · three continuous shapes");
text(caption, (640, 664), "curve3(t,p) · surface3(x,y,p) · param3(u,v,p)");
size(kicker, 18); color(kicker, dim); bold(kicker);
size(headline, 34); bold(headline);
size(caption, 19); color(caption, dim);
parameter(shape, (640, 612), 0, 0, 1, "shape", 2);
if h > 1.45*w {
camera3((0, -36, 8), (0, 0, 0.4), 40);
}
else {
camera3((10, -17, 9), (0, 0, 0.4), 40);
}
grid3(floor, (0, 0, -2.2), 8, 1);
color(floor, dim); opacity(floor, 0.14);
// A widening helix at the left.
curve3(helix,
"-4 + cos(t)",
"sin(t)",
"-1.7 + 0.55*t",
(0, 6.2832));
bind(shape, helix, formula,
"-4 + (1+0.45*p)*cos(t)",
"(1+0.45*p)*sin(t)",
"-1.7 + 0.55*t");
color(helix, cyan); thick(helix, 0.065); untraced(helix);
tag(helix, family);
// A bowl that becomes a rippling field in the middle.
surface3(land, "0.15*(x*x+y*y)", (-1.7,1.7), (-1.7,1.7), 28);
bind(shape, land, formula,
"(1-p)*0.15*(x*x+y*y) + p*0.72*sin(1.7*x)*cos(1.7*y)");
color(land, gold);
finish3(land, "material=glass shading=smooth mesh=0.30 depth=0.28 shadow=0.12");
tag(land, family);
// A torus whose tube inflates at the right.
param3(ring,
"4+(1.15+0.20*cos(v))*cos(u)",
"(1.15+0.20*cos(v))*sin(u)",
"0.20*sin(v)",
(0,6.2832), (0,6.2832), 28);
bind(shape, ring, formula,
"4+(1.15+(0.20+0.52*p)*cos(v))*cos(u)",
"(1.15+(0.20+0.52*p)*cos(v))*sin(u)",
"(0.20+0.52*p)*sin(v)");
color(ring, magenta);
finish3(ring, "material=metal shading=smooth mesh=0.22 depth=0.30 shadow=0.16");
tag(ring, family);
hidden(helix); hidden(land); hidden(ring);
step("reveal one stable family") {
par {
view3(family, "fit", 0.9, smooth, 1.55);
stagger(0.16) {
show(helix, 0.45);
show(land, 0.45);
show(ring, 0.45);
}
}
}
wait(0.45);
step("change the shared parameter") {
par {
to(shape, value, 1, 3.4, smooth);
say(caption, "The same three objects are resampled smoothly from p = 0 to p = 1.", 0.45);
}
}
wait(1.2);
creator-bowl-to-saddle
A vertical calculus Short asks when a bowl loses one direction of curvature. One persistent surface passes through the exact p=1/2 threshold and becomes a saddle, then closes with a creator CTA.
// CREATOR SHORT — a real calculus problem told through one continuously
// deforming surface. The shape is not replaced between the bowl, threshold,
// and saddle states.
title("When Does a Bowl Become a Saddle?");
canvas("9:16");
template("paper");
creator(me, "@anish2good name=Manic_Math tagline=Equations_that_move yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=signature cta=Create_your_own safe=reels");
socials(me);
watermark(mark, (w*0.955-100, h*0.045+24), "Made With Manic");
text(kicker, (540, 164), "A 20-SECOND CALCULUS STORY");
text(headline, (540, 226), "When does a bowl stop being a bowl?");
text(problem, (540, 292), "Find the exact value of p where one curvature disappears.");
size(kicker, 21); bold(kicker); color(kicker, cyan); hidden(kicker);
size(headline, 38); bold(headline); wrap(headline, 880); hidden(headline);
size(problem, 22); color(problem, dim); wrap(problem, 840); hidden(problem);
equation(rule, (540, 378), `z=0.22x^2+0.22(1-2p)y^2`, 39);
hidden(rule);
parameter(mix, (540, 1270), 0, 0, 1, "p", 2);
camera3((8.5, -11.5, 7.5), (0, 0, 0), 41);
surface3(world, "0.22*(x*x+y*y)", (-3,3), (-3,3), 34);
bind(mix, world, formula, "0.22*x*x + 0.22*(1-2*p)*y*y");
color(world, cyan);
finish3(world, "material=glass shading=smooth mesh=0.34 depth=0.34 shadow=0.16");
hidden(world);
text(chapter, (540, 1352), "p = 0 · positive curvature in both directions");
text(insight, (540, 1422), "Watch the coefficient of y².");
text(cta, (540, 1398), "Build the transformation, not the keyframes.");
text(link, (540, 1452), "TRY MANIC → 8gwifi.org/manic");
size(chapter, 23); bold(chapter); color(chapter, fg); wrap(chapter, 850); hidden(chapter);
size(insight, 21); color(insight, dim); hidden(insight);
size(cta, 23); bold(cta); hidden(cta);
size(link, 20); bold(link); color(link, cyan); hidden(link);
step("pose the problem") {
par {
show(kicker, 0.35);
show(headline, 0.50);
show(problem, 0.45);
show(rule, 0.55);
show(world, 0.65);
show(chapter, 0.40);
show(insight, 0.40);
view3(world, "isometric", 0.9, smooth, 1.62);
}
}
wait(0.65);
step("reach the turning point") {
par {
to(mix, value, 0.5, 2.8, smooth);
say(chapter, "p = ½ · the y² coefficient becomes zero", 0.45);
say(insight, "At this instant the bowl becomes a one-directional trough.", 0.45);
rewrite(rule, `p=\frac12\quad\Longrightarrow\quad z=0.22x^2`, 1.10, smooth);
orbit3(-45, 28, 16.2, 2.8, smooth);
}
}
wait(0.75);
step("cross into a saddle") {
par {
to(mix, value, 1, 2.8, smooth);
say(chapter, "p > ½ · one curvature turns negative", 0.45);
say(insight, "The same persistent surface is now a saddle.", 0.45);
rewrite(rule, `p=1\quad\Longrightarrow\quad z=0.22(x^2-y^2)`, 1.10, smooth);
orbit3(-62, 25, 16.2, 2.8, smooth);
}
}
wait(0.65);
step("answer and invite") {
par {
say(problem, "Answer: p = ½ is the exact transition.", 0.50);
fade(chapter, 0.35);
fade(insight, 0.35);
show(cta, 0.45);
show(link, 0.45);
}
}
wait(1.8);
story-living-dependency-cloud
A living dependency cloud grows, links itself by nearest neighbours, and drifts as one batched 3D cast. The relationships stay attached without hand-authoring dozens of nodes or links.
title("The Dependency Cloud That Refuses to Freeze");
canvas("9:16");
template("paper");
watermark(mark, (w*0.16, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.085), "ENGINE FOUNDATION · BATCHED RELATIONSHIPS");
text(headline, (cx, h*0.13), "Can 180 dependencies move as one idea?");
text(caption, (cx, h*0.82), "One collection. Stable children. Every relationship stays truthful.");
size(kicker, 20); color(kicker, dim); bold(kicker); hidden(kicker);
size(headline, 34); bold(headline); wrap(headline, w*0.82); hidden(headline);
size(caption, 22); color(caption, dim); wrap(caption, w*0.78); hidden(caption);
camera3((14,-20,13),(0,0,0),46);
collection3(services,(0,0,0),180,(4.2,2.6,2.2),41,0.055);
links3(dependencies,services,nearest,2);
color(services,cyan);
color(dependencies,dim);
hidden(services);
hidden(dependencies);
step("the-problem") {
show(kicker,0.35);
show(headline,0.45);
show(caption,0.40);
}
wait(0.45);
step("the-system-awakens") {
par {
show(services,0.65);
show(dependencies,0.85);
say(caption,"The engine batches the repeated geometry but preserves every indexed identity.",0.45);
}
}
wait(0.35);
step("relationships-remain-live") {
par {
drift3(services,6.0,0.42);
orbit3(35,24,28.0,6.0,smooth);
say(caption,"As the nodes drift, the nearest-neighbour graph is derived again from the same state.",0.45);
}
}
wait(0.45);
step("takeaway") {
pulse(services,0.7);
say(caption,"Large dependency scenes become one creator intention—not hundreds of scripts.",0.45);
}
wait(1.2);
story-dependent-chain-history
An articulated signal arm is generated as one dependent chain: every endpoint begins at the previous endpoint, while the tip leaves a truthful history of its real route.
title("How Rotations Become a Drawing");
canvas("9:16");
template("blank");
watermark(mark, (w*0.16, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.085), "ENGINE FOUNDATION · DEPENDENT CHAINS");
text(headline, (cx, h*0.13), "A drawing can hide inside rotating links");
text(caption, (cx, h*0.83), "Each endpoint begins where the previous endpoint finishes.");
size(kicker,20); color(kicker,dim); bold(kicker); hidden(kicker);
size(headline,34); bold(headline); wrap(headline,w*0.84); hidden(headline);
size(caption,22); color(caption,dim); wrap(caption,w*0.80); hidden(caption);
camera3((0,-18,13),(0,0,0),46);
collection3(harmonics,(0,0,0),7,(0,0,0),7,0.10);
links3(arms,harmonics,chain);
trail3(memory,harmonics,6,0.035);
child3(tip,harmonics,6,0.14);
color(harmonics,cyan);
color(arms,magenta);
color(memory,gold);
color(tip,gold);
hidden(harmonics); hidden(arms); hidden(memory); hidden(tip);
step("question") {
show(kicker,0.35);
show(headline,0.45);
show(caption,0.40);
}
wait(0.35);
step("build-the-chain") {
par {
show(harmonics,0.55);
show(arms,0.65);
show(memory,0.45);
show(tip,0.45);
cue(tick);
say(caption,"Seven stable links share one generated dependency chain.",0.40);
}
}
wait(0.30);
step("remember-the-tip") {
par {
chain3(harmonics,"2.2 1.1 0.72 0.52 0.40 0.32 0.26","1 -3 5 -7 9 -11 13",8.0);
say(caption,"Only the moving tip writes history; the rest remain a readable mechanism.",0.45);
}
}
wait(0.45);
step("takeaway") {
cue(chime);
pulse(tip,0.7);
say(caption,"The same foundation serves epicycles, linkages, robot arms, and live derived plots.",0.45);
}
wait(1.2);
fourier-series-live-wave
Twelve visible orbit rings form one epicycle chain while historyplot3 turns every
endpoint’s exact y-history into a coloured partial sum across two complete periods—one
continuous motion, one source of truth.
// A focused Fourier story: every orbit and every partial sum is generated from
// the same live collection. The circles move; their histories become the wave.
title("A Wave Drawn by Rotating Circles");
canvas("9:16");
template("blank");
watermark(mark,(w*0.15,h*0.045),"Made With Manic");
text(kicker,(cx,h*0.075),"FOURIER SERIES · LIVE DERIVED HISTORY");
text(headline,(cx,h*0.115),"Can rotating circles draw a wave?");
text(caption,(cx,h*0.18),"Every coloured trace is the real history of one moving endpoint.");
text(cta,(cx,h*0.925),"BUILD YOUR OWN VISUAL STORY → 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(caption,20); color(caption,dim); wrap(caption,w*0.84); hidden(caption);
size(cta,21); color(cta,cyan); bold(cta); hidden(cta);
camera3((-29,-25,43),(1,-2,0),42,orthographic);
collection3(harmonics,(-3,5,0),12,(0,0,0),11,0.065);
line3(sweep,(-3,5,0),(10,5,0)); hidden(sweep);
links3(arms,harmonics,chain);
child3(tip,harmonics,11,0.13);
arrow3(chainX,(-11,5,0),(7,5,0));
arrow3(chainY,(-3,-2,0),(-3,10.5,0));
arrow3(plotX,(-7,-10.5,0),(12,-10.5,0));
arrow3(plotY,(-7,-10.5,0),(-7,-4.2,0));
point3(shotA,(-7.5,-11,0),0.01);
point3(shotB,(8.5,10.5,0),0.01);
tag(shotA,shot); tag(shotB,shot);
hidden(shotA); hidden(shotB);
tag(harmonics,construction); tag(arms,construction); tag(tip,construction);
tag(chainX,construction); tag(chainY,construction);
tag(plotX,construction); tag(plotY,construction);
for i in 0..12 {
ring3(orbit{i},harmonics,i,72);
historyplot3(partial{i},harmonics,i,y,(-7,-10.5,0),(19,6));
thick(orbit{i},0.030);
thick(partial{i},0.020);
tag(orbit{i},construction);
tag(partial{i},construction);
}
equation(formula,(0,0),`f(t)=\sum_{n=1}^{12}\frac{2(-1)^{n+1}}{\pi n}\sin(nt)`,25);
label3(formula,(-4,-2.7,0),0.82);
tag(formula,construction);
color(harmonics,fg); color(arms,fg); color(tip,fg);
color(chainX,fg); color(chainY,fg); color(plotX,fg); color(plotY,fg);
color(formula,fg);
thick(chainX,0.016); thick(chainY,0.016);
thick(plotX,0.016); thick(plotY,0.016);
color(orbit0,lime); color(partial0,lime);
color(orbit1,gold); color(partial1,gold);
color(orbit2,orange); color(partial2,orange);
color(orbit3,coral); color(partial3,coral);
color(orbit4,red); color(partial4,red);
color(orbit5,magenta); color(partial5,magenta);
color(orbit6,violet); color(partial6,violet);
color(orbit7,indigo); color(partial7,indigo);
color(orbit8,blue); color(partial8,blue);
color(orbit9,cyan); color(partial9,cyan);
color(orbit10,mint); color(partial10,mint);
color(orbit11,fg); color(partial11,fg);
hidden(construction);
step("ask the visual question") {
show(kicker,0.30);
show(headline,0.42);
show(caption,0.38);
}
wait(0.35);
step("reveal one connected machine") {
par {
show(construction,0.85);
view3(shot,"top",1.35,smooth,1.08);
say(caption,"Each circle starts where the previous circle ends.",0.38);
}
}
wait(0.30);
step("let motion become data") {
par {
// Doubling every angular rate makes the base harmonic complete exactly
// two revolutions while preserving all relative frequencies.
chain3(harmonics,"2.0372 -1.0186 0.6791 -0.5093 0.4074 -0.3395 0.2910 -0.2546 0.2264 -0.2037 0.1852 -0.1698","2 4 6 8 10 12 14 16 18 20 22 24",16.0);
travel3(harmonics,sweep,16.0,linear);
seq {
say(caption,"Cycle one: the broad motion appears, then smaller circles restore its detail.",0.40);
wait(6.10);
say(caption,"Cycle two: the same relationships repeat continuously—without a reset or jump.",0.40);
wait(5.20);
say(caption,"The endpoint histories now contain two complete periods of the signal.",0.40);
}
}
}
wait(0.35);
step("create with Manic") {
par {
pulse(tip,0.75);
pulse(partial11,0.75);
show(cta,0.45);
say(caption,"Describe the relationship. Manic keeps the circles, histories, motion, and camera together.",0.42);
}
}
wait(1.45);
definedmotion-fourier-gallery
Five depth-stacked Fourier families rotate at once while twenty coloured ring3
orbits drive twenty world-space partial histories per family. Focus and overview cameras
observe the same compiled motion before a creator CTA closes the Short.
// Visual acceptance story for DefinedMotion resources/animation2.gif.
// Twenty visible orbit rings create each relation. Every coloured partial-sum
// history is derived from the exact endpoint that draws it.
title("Fourier Series — Rotations Become Waves");
canvas("9:16");
template("mono");
watermark(mark,(w*0.15,h*0.045),"Made With Manic");
text(chapter,(cx,h*0.075),"FOURIER SERIES · FIVE RELATIONS");
text(caption,(cx,h*0.85),"Twenty rotating circles. One continuously derived signal.");
text(cta,(cx,h*0.925),"BUILD VISUAL EXPLANATIONS → 8gwifi.org/manic");
size(chapter,18); color(chapter,dim); bold(chapter); hidden(chapter);
size(caption,20); color(caption,dim); hidden(caption);
size(cta,21); color(cta,cyan); bold(cta); hidden(cta);
camera3((-31,-25,44),(1,-2,0),42,orthographic);
def relation(k,zv) {
// The chain, its two local axes, and the waveform axes all share one layer.
collection3(series{k},(-3,5,zv),20,(0,0,0),11,0.055);
line3(sweep{k},(-3,5,zv),(10,5,zv));
hidden(sweep{k});
links3(arms{k},series{k},chain);
child3(tip{k},series{k},19,0.12);
arrow3(chainX{k},(-11,5,zv),(7,5,zv));
arrow3(chainY{k},(-3,-3,zv),(-3,13,zv));
arrow3(plotX{k},(-7,-12,zv),(12,-12,zv));
arrow3(plotY{k},(-7,-12,zv),(-7,-3,zv));
// A cinematic crop: the axes may continue beyond the frame, as in the
// reference, while the active rings and the growing history stay dominant.
point3(shot{k}A,(-7.5,-10,zv),0.01);
point3(shot{k}B,(8.5,10,zv),0.01);
tag(shot{k}A,shot{k}); tag(shot{k}B,shot{k});
hidden(shot{k}A); hidden(shot{k}B);
tag(series{k},layer{k}); tag(arms{k},layer{k}); tag(tip{k},layer{k});
tag(chainX{k},layer{k}); tag(chainY{k},layer{k});
tag(plotX{k},layer{k}); tag(plotY{k},layer{k});
tag(layer{k},stack);
for i in 0..20 {
ring3(orbit{k}_{i},series{k},i,72);
historyplot3(wave{k}_{i},series{k},i,y,(-7,-12,zv),(19,8.5));
thick(orbit{k}_{i},0.022);
thick(wave{k}_{i},0.014);
tag(orbit{k}_{i},layer{k}); tag(wave{k}_{i},layer{k});
}
color(series{k},fg); color(arms{k},fg); color(tip{k},fg);
color(chainX{k},fg); color(chainY{k},fg);
color(plotX{k},fg); color(plotY{k},fg);
thick(chainX{k},0.016); thick(chainY{k},0.016);
thick(plotX{k},0.016); thick(plotY{k},0.016);
color(orbit{k}_0,lime); color(wave{k}_0,lime);
color(orbit{k}_1,gold); color(wave{k}_1,gold);
color(orbit{k}_2,gold); color(wave{k}_2,gold);
color(orbit{k}_3,magenta); color(wave{k}_3,magenta);
color(orbit{k}_4,coral); color(wave{k}_4,coral);
color(orbit{k}_5,red); color(wave{k}_5,red);
color(orbit{k}_6,orange); color(wave{k}_6,orange);
color(orbit{k}_7,magenta); color(wave{k}_7,magenta);
color(orbit{k}_8,violet); color(wave{k}_8,violet);
color(orbit{k}_9,indigo); color(wave{k}_9,indigo);
color(orbit{k}_10,cyan); color(wave{k}_10,cyan);
color(orbit{k}_11,blue); color(wave{k}_11,blue);
color(orbit{k}_12,mint); color(wave{k}_12,mint);
color(orbit{k}_13,lime); color(wave{k}_13,lime);
color(orbit{k}_14,gold); color(wave{k}_14,gold);
color(orbit{k}_15,cyan); color(wave{k}_15,cyan);
color(orbit{k}_16,lime); color(wave{k}_16,lime);
color(orbit{k}_17,magenta); color(wave{k}_17,magenta);
color(orbit{k}_18,coral); color(wave{k}_18,coral);
color(orbit{k}_19,cyan); color(wave{k}_19,cyan);
}
relation(0,-20);
relation(1,-10);
relation(2,0);
relation(3,10);
relation(4,20);
point3(overviewA,(-11,-12,-20),0.01);
point3(overviewB,(12,13,20),0.01);
tag(overviewA,overviewShot); tag(overviewB,overviewShot);
hidden(overviewA); hidden(overviewB);
equation(formula0,(0,0),`\sum_{n=1}^{N}\frac{4}{\pi(2n-1)}\sin((2n-1)t)`,26);
equation(formula1,(0,0),`\sum_{n=1}^{N}\frac{2(-1)^{n+1}}{\pi n}\sin(nt)`,26);
equation(formula2,(0,0),`\sum_{n=1}^{N}\frac{2w_n}{\pi n}\sin(nt)`,26);
equation(formula3,(0,0),`\sum_{n=1}^{N}\frac{2}{\pi n}\sin(n^2t)`,26);
equation(formula4,(0,0),`\sum_{n=1}^{N}\frac{2r_n}{\pi n}\sin(nt)`,26);
label3(formula0,(-8,-2,-20),0.82);
label3(formula1,(-8,-2,-10),0.82);
label3(formula2,(-8,-2,0),0.82);
label3(formula3,(-8,-2,10),0.82);
label3(formula4,(-8,-2,20),0.82);
for i in 0..5 {
tag(formula{i},layer{i}); tag(formula{i},stack); color(formula{i},fg);
}
hidden(stack);
step("enter the Fourier world") {
show(chapter,0.30);
show(caption,0.35);
show(stack,0.85);
}
wait(0.35);
step("twenty circles write five signals") {
par {
chain3(series0,"4.0744 1.3581 0.8149 0.5821 0.4527 0.3704 0.3134 0.2716 0.2397 0.2144 0.1940 0.1771 0.1630 0.1509 0.1405 0.1314 0.1235 0.1164 0.1101 0.1045","1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39",17.0);
chain3(series1,"2.0372 -1.0186 0.6791 -0.5093 0.4074 -0.3395 0.2910 -0.2546 0.2264 -0.2037 0.1852 -0.1698 0.1567 -0.1455 0.1358 -0.1273 0.1198 -0.1132 0.1072 -0.1019","1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20",17.0);
chain3(series2,"0 0.0276 0.0716 0.1154 0.1537 0.1838 0.2040 0.2136 0.2127 0.2023 0.1839 0.1595 0.1314 0.1020 0.0735 0.0480 0.0271 0.0119 0.0029 0","1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20",17.0);
chain3(series3,"2.0372 1.0186 0.6791 0.5093 0.4074 0.3395 0.2910 0.2546 0.2264 0.2037 0.1852 0.1698 0.1567 0.1455 0.1358 0.1273 0.1198 0.1132 0.1072 0.1019","1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400",17.0);
chain3(series4,"1.3334 0.0973 0.0648 0.3333 0.4074 0.2222 0.0278 0.0243 0.1482 0.2037 0.1212 0.0162 0.0150 0.0952 0.1358 0.0833 0.0114 0.0108 0.0702 0.1019","1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20",17.0);
travel3(series0,sweep0,17.0,linear);
travel3(series1,sweep1,17.0,linear);
travel3(series2,sweep2,17.0,linear);
travel3(series3,sweep3,17.0,linear);
travel3(series4,sweep4,17.0,linear);
seq {
wait(0.45);
par {
fade(layer1,0.80); fade(layer2,0.80); fade(layer3,0.80); fade(layer4,0.80);
view3(shot0,"isometric",1.45,smooth,1.08);
say(caption,"Square wave — odd harmonics sharpen every corner.",0.35);
}
wait(1.65);
par {
fade(layer0,0.65); show(layer1,0.65);
view3(shot1,"isometric",1.35,smooth,1.08);
say(caption,"Sawtooth — every harmonic joins with alternating direction.",0.35);
}
wait(1.65);
par {
fade(layer1,0.65); show(layer2,0.65);
view3(shot2,"isometric",1.35,smooth,1.08);
say(caption,"Hann window — the outer harmonics taper away.",0.35);
}
wait(1.65);
par {
fade(layer2,0.65); show(layer3,0.65);
view3(shot3,"isometric",1.35,smooth,1.08);
say(caption,"Quadratic chirp — frequency accelerates as n squared.",0.35);
}
wait(1.65);
par {
fade(layer3,0.65); show(layer4,0.65);
view3(shot4,"isometric",1.35,smooth,1.08);
say(caption,"Envelope ripple — selected harmonics breathe in and out.",0.35);
}
wait(1.55);
par {
show(layer0,0.55); show(layer1,0.55); show(layer2,0.55); show(layer3,0.55);
view3(overviewShot,"isometric",1.5,smooth,1.30);
say(caption,"One relationship engine. Five very different signals.",0.35);
}
}
}
}
wait(0.35);
step("create with Manic") {
show(cta,0.45);
pulse(stack,0.75);
say(caption,"Describe the relationship; Manic keeps every ring, endpoint, history, and camera together.",0.40);
}
wait(1.4);
fourier-square-wave-live
Thirteen orbit rings add odd harmonics 1 through 25. Their live partial histories show the plateaus flatten and the corners sharpen across two uninterrupted square-wave cycles before a creator CTA closes the Short.
// Odd harmonics build a square wave. Every partial sum is a live history of
// the corresponding endpoint, so the corners emerge from the geometry itself.
title("How Odd Harmonics Build a Square Wave");
canvas("9:16");
template("paper");
watermark(mark,(w*0.15,h*0.045),"Made With Manic");
text(kicker,(cx,h*0.075),"FOURIER · ODD HARMONICS");
text(headline,(cx,h*0.115),"Watch a square wave find its corners");
text(caption,(cx,h*0.18),"Start with n = 1. Then add 3, 5, 7 … all the way to 25.");
text(cta,(cx,h*0.925),"TURN IDEAS INTO ANIMATION → 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(caption,20); color(caption,dim); wrap(caption,w*0.84); hidden(caption);
size(cta,21); color(cta,cyan); bold(cta); hidden(cta);
camera3((-29,-25,43),(1,-2,0),42,orthographic);
collection3(series,(-3,5,0),13,(0,0,0),11,0.065);
line3(sweep,(-3,5,0),(10,5,0)); hidden(sweep);
links3(arms,series,chain);
child3(tip,series,12,0.13);
arrow3(chainX,(-11,5,0),(7,5,0));
arrow3(chainY,(-3,-2,0),(-3,10.5,0));
arrow3(plotX,(-7,-10.5,0),(12,-10.5,0));
arrow3(plotY,(-7,-10.5,0),(-7,-4.2,0));
point3(shotA,(-7.5,-11,0),0.01);
point3(shotB,(8.5,10.5,0),0.01);
tag(shotA,shot); tag(shotB,shot);
hidden(shotA); hidden(shotB);
tag(series,construction); tag(arms,construction); tag(tip,construction);
tag(chainX,construction); tag(chainY,construction);
tag(plotX,construction); tag(plotY,construction);
for i in 0..13 {
ring3(orbit{i},series,i,72);
historyplot3(partial{i},series,i,y,(-7,-10.5,0),(19,6));
thick(orbit{i},0.030);
thick(partial{i},0.020);
tag(orbit{i},construction);
tag(partial{i},construction);
}
equation(formula,(0,0),`S_{13}(t)=\sum_{k=0}^{12}\frac{4}{\pi(2k+1)}\sin((2k+1)t)`,24);
label3(formula,(-4,-2.7,0),0.80);
tag(formula,construction);
color(series,fg); color(arms,fg); color(tip,fg);
color(chainX,fg); color(chainY,fg); color(plotX,fg); color(plotY,fg);
color(formula,fg);
thick(chainX,0.016); thick(chainY,0.016);
thick(plotX,0.016); thick(plotY,0.016);
color(orbit0,lime); color(partial0,lime);
color(orbit1,gold); color(partial1,gold);
color(orbit2,orange); color(partial2,orange);
color(orbit3,coral); color(partial3,coral);
color(orbit4,red); color(partial4,red);
color(orbit5,magenta); color(partial5,magenta);
color(orbit6,violet); color(partial6,violet);
color(orbit7,indigo); color(partial7,indigo);
color(orbit8,blue); color(partial8,blue);
color(orbit9,cyan); color(partial9,cyan);
color(orbit10,mint); color(partial10,mint);
color(orbit11,lime); color(partial11,lime);
color(orbit12,fg); color(partial12,fg);
hidden(construction);
step("begin with one sine wave") {
show(kicker,0.30);
show(headline,0.42);
show(caption,0.38);
}
wait(0.35);
step("reveal the odd-harmonic chain") {
par {
show(construction,0.85);
view3(shot,"top",1.35,smooth,1.08);
say(caption,"Every smaller orbit contributes the next odd frequency.",0.38);
}
}
wait(0.30);
step("build the corners live") {
par {
// Two complete base cycles: all odd harmonic rates are doubled together.
chain3(series,"4.0744 1.3581 0.8149 0.5821 0.4527 0.3704 0.3134 0.2716 0.2397 0.2144 0.1940 0.1771 0.1630","2 6 10 14 18 22 26 30 34 38 42 46 50",16.0);
travel3(series,sweep,16.0,linear);
seq {
say(caption,"Cycle one: odd harmonics flatten the plateaus and steepen both sides.",0.40);
wait(6.10);
say(caption,"Cycle two: every orbit closes and the square-wave pattern returns continuously.",0.40);
wait(5.20);
say(caption,"Two complete periods make the repeating structure impossible to miss.",0.40);
}
}
}
wait(0.35);
step("create with Manic") {
par {
pulse(tip,0.75);
pulse(partial12,0.75);
show(cta,0.45);
say(caption,"One declarative chain becomes circles, partial sums, a live waveform, and a complete visual story.",0.42);
}
}
wait(1.45);
growing-fractal-tree3
One seeded split-turn-shrink rule grows 1,023 branches. tree3 keeps each generation
renderer-batched while progressive draw and a bounds-aware camera reveal the canopy.
title("One Rule Grows a Forest");
canvas("9:16");
template("blank");
watermark(mark,(w*0.16,h*0.055),"Made With Manic");
text(kicker,(cx,h*0.085),"FRACTAL TREE · BATCHED GENERATIONS");
text(headline,(cx,h*0.13),"How can one branch become 1,023?");
text(caption,(cx,h*0.84),"Repeat one rule: split, turn, shrink—and keep every generation readable.");
text(cta,(cx,h*0.91),"CREATE YOUR OWN → 8gwifi.org/manic");
size(kicker,20); color(kicker,dim); bold(kicker); hidden(kicker);
size(headline,34); bold(headline); wrap(headline,w*0.84); hidden(headline);
size(caption,22); color(caption,dim); wrap(caption,w*0.82); hidden(caption);
size(cta,22); color(cta,cyan); bold(cta); hidden(cta);
camera3((9,-13,8),(0,0,3.2),43);
tree3(tree,(0,0,-3.2),2.1,27,0.72,10,42);
for i in 0..10 { untraced(tree.d{i}); hidden(tree.d{i}); }
hidden(tree.leaves);
step("the seed") {
show(kicker,0.3); show(headline,0.45); show(caption,0.4);
view3(tree,"isometric",0.7,smooth,1.42);
}
wait(0.35);
step("grow by generation") {
par {
stagger(0.16) {
for i in 0..10 {
par { show(tree.d{i},0.22); draw(tree.d{i},0.58,smooth); }
}
}
say(caption,"Each depth is one renderer batch, even when it contains hundreds of branches.",0.45);
view3(tree,"isometric",1.1,smooth,1.48);
}
}
wait(0.4);
step("find the canopy") {
par {
show(tree.leaves,0.65);
seq {
orbit3(42,24,21.0,2.1,smooth);
view3(tree,"isometric",0.9,smooth,1.42);
}
say(caption,"A semantic camera journey reveals the rule first, then the whole canopy.",0.45);
}
}
wait(0.5);
step("takeaway") {
par {
pulse(tree.leaves,0.7);
say(caption,"Deep procedural geometry stays simple to author, deterministic, and smooth to seek.",0.45);
show(cta,0.45);
}
}
wait(1.3);
hilbert-curve3-space-filling
One continuous 3D Hilbert path refines from 7 to 32,767 segments while keeping its identity, arc-length colour, cube bounds, and uninterrupted orbit camera. The generated curve visits every lattice cell exactly once at each order.
// Engine Test 7 — a 3D Hilbert curve refines from 7 to 32,767 segments.
// One generated path keeps its identity while become3, draw, and orbit3 tell
// the story. The curve is exact: each order visits every lattice cell once.
title("Hilbert Curve in 3D");
canvas("16:9");
template("black");
creator(me,"@anish2good name=Manic_Math tagline=Space_filling_curves_made_visible yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=magenta secondary=cyan footer=compact cta=Animate_the_infinite safe=clean");
watermark(mark,(105,52),"Made With Manic");
endcard(me,"title=Make_Fractals_Move cta=Create_with_Manic");
text(kicker,(cx,46),"HILBERT CURVE IN 3D");
text(head,(cx,86),"How can one line fill a cube?");
text(caption,(cx,626),"Begin with one continuous route through all eight octants.");
text(orderLabel,(cx,668),"ORDER 1 · 7 SEGMENTS");
size(kicker,20); bold(kicker); color(kicker,magenta);
size(head,34); bold(head);
size(caption,21); color(caption,dim);
size(orderLabel,19); bold(orderLabel);
camera3((13,-15,11),(0,0,0),42);
// A quiet wireframe establishes the volume before the curve enters it.
line3(e0,(-3,-3,-3),(3,-3,-3)); line3(e1,(3,-3,-3),(3,3,-3));
line3(e2,(3,3,-3),(-3,3,-3)); line3(e3,(-3,3,-3),(-3,-3,-3));
line3(e4,(-3,-3,3),(3,-3,3)); line3(e5,(3,-3,3),(3,3,3));
line3(e6,(3,3,3),(-3,3,3)); line3(e7,(-3,3,3),(-3,-3,3));
line3(e8,(-3,-3,-3),(-3,-3,3)); line3(e9,(3,-3,-3),(3,-3,3));
line3(e10,(3,3,-3),(3,3,3)); line3(e11,(-3,3,-3),(-3,3,3));
tag(e0,bounds); tag(e1,bounds); tag(e2,bounds); tag(e3,bounds);
tag(e4,bounds); tag(e5,bounds); tag(e6,bounds); tag(e7,bounds);
tag(e8,bounds); tag(e9,bounds); tag(e10,bounds); tag(e11,bounds);
color(bounds,dim); opacity(bounds,0.34); thick(bounds,0.012);
hidden(bounds); untraced(bounds);
// The five exact refinements. Hidden curves are blueprints for become3.
hilbert3(curve,(0,0,0),5.65,1);
hilbert3(order2,(0,0,0),5.65,2);
hilbert3(order3,(0,0,0),5.65,3);
hilbert3(order4,(0,0,0),5.65,4);
hilbert3(order5,(0,0,0),5.65,5);
hidden(curve); untraced(curve);
hidden(order2); hidden(order3); hidden(order4); hidden(order5);
hidden(kicker); hidden(head); hidden(caption); hidden(orderLabel);
step("pose the question") {
par {
show(kicker,0.30);
show(head,0.45);
show(caption,0.40);
show(orderLabel,0.35);
show(bounds,0.35);
draw(bounds,1.25,smooth);
}
}
wait(0.45);
step("visit the eight octants") {
par {
show(curve,0.30);
draw(curve,1.65,smooth);
orbit3(54,25,16.2,1.65,smooth);
}
}
wait(0.45);
step("refine the route") {
par {
seq {
par {
become3(curve,order2,1.30,smooth);
say(orderLabel,"ORDER 2 · 63 SEGMENTS",0.35,smooth);
say(caption,"Replace every visit with a smaller, rotated copy—without breaking the line.",0.45,smooth);
}
wait(0.35);
par {
become3(curve,order3,1.55,smooth);
say(orderLabel,"ORDER 3 · 511 SEGMENTS",0.35,smooth);
say(caption,"The gaps shrink while continuity survives every refinement.",0.45,smooth);
}
wait(0.35);
par {
become3(curve,order4,1.90,smooth);
say(orderLabel,"ORDER 4 · 4,095 SEGMENTS",0.35,smooth);
say(caption,"One path now threads through thousands of cells in the cube.",0.45,smooth);
}
wait(0.35);
par {
become3(curve,order5,2.40,smooth);
say(orderLabel,"ORDER 5 · 32,767 SEGMENTS",0.35,smooth);
say(caption,"In the limit, the continuous curve reaches every point in the volume.",0.50,smooth);
}
}
orbit3(186,31,17.8,8.55,linear);
}
}
wait(0.80);
step("takeaway") {
par {
pulse(curve,0.80);
say(head,"A one-dimensional path can fill 3D space.",0.50,smooth);
say(caption,"Refinement adds detail—not disconnected pieces.",0.45,smooth);
}
}
wait(1.35);
step("cta") {
par {
fade(kicker,0.30); fade(head,0.30); fade(caption,0.30);
fade(orderLabel,0.30); fade(bounds,0.40); fade(curve,0.45);
show(me.endcard,0.60);
}
}
wait(2.00);
latex-disintegration
A quadratic factors into two solutions, then those symbols become persistent points at
2 and 3. Semantic rewrite preserves the algebra; disintegrate removes the notation
while the mathematical meaning remains visibly in place.
// A semantic LaTeX story: notation changes, meaning becomes position, and the
// final symbols may disappear without taking the mathematical result with them.
title("The Equation Disappears. The Roots Remain.");
canvas("9:16");
template("blank");
watermark(mark,(w*0.16,h*0.050),"Made With Manic");
text(kicker,(cx,h*0.085),"ALGEBRA · SYMBOLS → STRUCTURE");
text(headline,(cx,h*0.145),"What survives when an equation disappears?");
text(caption,(cx,h*0.77),"Two solutions are hiding inside one quadratic.");
text(cta,(cx,h*0.90),"MAKE MEANING MOVE → 8gwifi.org/manic");
size(kicker,19); color(kicker,dim); bold(kicker); hidden(kicker);
size(headline,33); bold(headline); wrap(headline,w*0.84); hidden(headline);
size(caption,22); color(caption,dim); wrap(caption,w*0.82); hidden(caption);
size(cta,22); color(cta,cyan); bold(cta); hidden(cta);
text(symbolLabel,(w*0.18,h*0.29),"THE SYMBOLS");
text(meaningLabel,(w*0.18,h*0.54),"THE MEANING");
size(symbolLabel,18); color(symbolLabel,dim); bold(symbolLabel); hidden(symbolLabel);
size(meaningLabel,18); color(meaningLabel,dim); bold(meaningLabel); hidden(meaningLabel);
equation(work,(cx,h*0.39),`x^2-5x+6=0`,66);
hidden(work);
let axisY = h*0.63;
let axisLeft = w*0.16;
let axisRight = w*0.84;
let x0 = w*0.20;
let dx = w*0.15;
arrow(numberAxis,(axisLeft,axisY),(axisRight,axisY));
color(numberAxis,dim); stroke(numberAxis,3);
tag(numberAxis,numberLine);
for i in 0..5 {
line(tick{i},(x0+i*dx,axisY-13),(x0+i*dx,axisY+13));
color(tick{i},dim); stroke(tick{i},3);
tag(tick{i},numberLine);
}
text(n0,(x0,axisY+42),"0");
text(n1,(x0+dx,axisY+42),"1");
text(n2,(x0+2*dx,axisY+42),"2");
text(n3,(x0+3*dx,axisY+42),"3");
text(n4,(x0+4*dx,axisY+42),"4");
size(n0,18); size(n1,18); size(n2,18); size(n3,18); size(n4,18);
color(n0,dim); color(n1,dim); color(n2,dim); color(n3,dim); color(n4,dim);
tag(n0,numberLine); tag(n1,numberLine); tag(n2,numberLine);
tag(n3,numberLine); tag(n4,numberLine);
circle(root2,(x0+2*dx,axisY),15);
circle(root3,(x0+3*dx,axisY),15);
filled(root2); filled(root3);
color(root2,cyan); color(root3,orange);
glow(root2,0.72); glow(root3,0.72);
tag(root2,solutions); tag(root3,solutions);
equation(root2Label,(x0+2*dx,axisY-62),`x=2`,30);
equation(root3Label,(x0+3*dx,axisY-62),`x=3`,30);
color(root2Label,cyan); color(root3Label,orange);
tag(root2Label,solutions); tag(root3Label,solutions);
hidden(numberLine);
hidden(solutions);
step("find what is hiding") {
par {
show(kicker,0.30);
show(headline,0.45);
show(symbolLabel,0.35);
show(caption,0.40);
show(work,0.55);
}
}
wait(0.55);
step("expose the two conditions") {
par {
rewrite(work,`\textcolor{cyan}{(x-2)}\textcolor{orange}{(x-3)}=0`,1.15,smooth);
say(caption,"Factoring reveals two independent ways for the product to become zero.",0.45);
}
}
wait(0.55);
step("turn answers into positions") {
par {
rewrite(work,`\textcolor{cyan}{x=2}\qquad\text{or}\qquad\textcolor{orange}{x=3}`,1.05,smooth);
seq {
show(meaningLabel,0.30);
show(numberLine,0.55);
show(root2,0.35);
show(root2Label,0.30);
wait(0.18);
show(root3,0.35);
show(root3Label,0.30);
}
say(caption,"The answers are no longer only symbols—they now occupy exact locations.",0.45);
}
}
wait(0.70);
step("remove the notation, keep the result") {
par {
disintegrate(work,1.60);
seq {
wait(0.35);
pulse(root2,0.60);
pulse(root3,0.60);
}
say(caption,"The notation can turn to dust. The two solutions remain exactly where they belong.",0.45);
}
}
wait(0.75);
step("create explanations that preserve meaning") {
par {
pulse(solutions,0.75);
show(cta,0.45);
say(caption,"Great visual explanations do more than move symbols—they reveal what those symbols mean.",0.45);
}
}
wait(1.45);
story-time-varying-field3
A seed cloud enters a 3D vector field whose formula changes with normalized time. Deterministic advection, live arrows, one addressable particle, its trail, and a follow camera all sample the same seekable timeline.
title("The Current Changes While We Travel");
canvas("9:16");
template("blank");
watermark(mark, (w*0.16, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.085), "ENGINE FOUNDATION · LIVE 3D FIELDS");
text(headline, (cx, h*0.13), "What if the current changes mid-flight?");
text(caption, (cx, h*0.83), "The arrows and particles sample the same field at the same story time.");
size(kicker,20); color(kicker,dim); bold(kicker); hidden(kicker);
size(headline,34); bold(headline); wrap(headline,w*0.84); hidden(headline);
size(caption,22); color(caption,dim); wrap(caption,w*0.80); hidden(caption);
camera3((16,-20,14),(0,0,0),46);
vectorfield3(current,(0,0,0),(4.2,3.2,2.6),"-y + 0.7*sin(p*tau)","x + 0.35*cos(p*tau)","0.45*sin(z+p*tau)",6);
collection3(tracers,(0,0,0),90,(2.5,1.8,1.5),73,0.065);
child3(hero,tracers,0,0.14);
trail3(route,tracers,0,0.032);
color(current,dim);
color(tracers,cyan);
color(hero,gold);
color(route,gold);
hidden(current); hidden(tracers); hidden(hero); hidden(route);
step("question") {
show(kicker,0.35);
show(headline,0.45);
show(caption,0.40);
}
wait(0.35);
step("reveal-the-current") {
par {
show(current,0.70);
show(tracers,0.55);
show(hero,0.45);
show(route,0.45);
say(caption,"A bounded formula declares x, y, z and normalized time p.",0.40);
}
}
wait(0.30);
step("flow-and-follow") {
followshot3(hero,(0,0,0.15));
par {
advect3(tracers,current,7.0,1.15);
orbit3(55,26,29.0,7.0,smooth);
say(caption,"Deterministic RK4 moves every tracer while the camera follows one stable child.",0.45);
}
followshot3(none);
}
wait(0.45);
step("takeaway") {
cue(chime);
say(caption,"Field, motion, trail, and shot now tell one synchronized truth.",0.45);
}
wait(1.2);
story-addressable-asset-cues
A grouped console OBJ becomes an addressable technical assembly. Its base, screen, and key reveal as named parts with projected notation, a local procedural cue, and particle punctuation—no remote asset or callback.
title("An Asset Becomes a Story");
canvas("9:16");
template("mono");
watermark(mark, (w*0.16, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.085), "ENGINE FOUNDATION · ASSETS, LABELS & CUES");
text(headline, (cx, h*0.15), "An imported object should still be explainable");
text(caption, (cx, h*0.82), "Groups become addressable parts—then motion, notation, sound, and effects compose normally.");
size(kicker,18); color(kicker,dim); bold(kicker); hidden(kicker);
size(headline,34); bold(headline); wrap(headline,w*0.84); hidden(headline);
size(caption,21); color(caption,dim); wrap(caption,w*0.82); hidden(caption);
camera3((16,-22,15),(0,0,0),46);
assembly3(console,"asset:models/manic-console.obj",(0,0,0),1.35);
color(console.base,dim);
color(console.screen,cyan);
color(console.key,magenta);
text(screenNote,(0,0),"world-sized display");
text(keyNote,(0,0),"addressable input");
size(screenNote,22); color(screenNote,cyan); hidden(screenNote);
size(keyNote,22); color(keyNote,magenta); hidden(keyNote);
label3(screenNote,console.screen,0.82);
label3(keyNote,console.key,0.82);
hidden(console);
circle(effectBox,(cx,h*0.70),72);
particles(sparks,effectBox,34,4,19);
color(sparks,gold);
hidden(effectBox); hidden(sparks);
step("import") {
show(kicker,0.35);
show(headline,0.45);
show(caption,0.40);
show(console,0.75);
cue(whoosh);
}
wait(0.35);
step("address-the-parts") {
seq {
show(screenNote,0.35);
pulse(console.screen,0.65);
fade(screenNote,0.25);
show(keyNote,0.35);
pulse(console.key,0.65);
}
say(caption,"OBJ groups arrive as console.base, console.screen, and console.key.",0.42);
cue(tick);
}
wait(0.35);
step("compose-the-explanation") {
par {
turn3(console,(0,0,0),z,22,1.4,smooth);
orbit3(30,28,30,1.4,smooth);
say(caption,"Depth-scaled labels remain crisp while the grouped asset moves.",0.40);
}
}
wait(0.30);
step("finish-with-a-beat") {
show(sparks,0.15);
par {
burst(sparks,0.9);
pulse(console.key,0.65);
say(caption,"A reusable cue and the existing generic burst add the final editorial beat.",0.40);
}
cue(pop);
}
wait(1.2);
trapped-light-dimensions
A photon escapes one dimension at a time: first a 5-unit line, then the 5–12–13 diagonal of a plane, and finally the 13–84–85 diagonal through a volume. One persistent light beam makes the generalized Pythagorean idea visible.
// ============================================================================
// trapped-light-dimensions.manic — a continuous 1D → 2D → 3D journey
// ----------------------------------------------------------------------------
// One photon and one equation persist through the whole story. A fiber sweeps
// sideways into a glass floor; that same floor rises into a transparent room.
// The visual construction makes the distance formula grow one perpendicular
// square at a time instead of presenting three unrelated formulas.
// ============================================================================
title("The Trapped Light Beam — From 1D to 3D");
canvas("9:16");
template("neon");
watermark(manicMark, (238, 108), "Made With Manic");
creator(me, "@anish2good name=Manic_3D tagline=Build_dimensions_visually yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social safe=reels");
socials(me);
text(kicker, (540, 164), "A JOURNEY THROUGH DIMENSIONS");
size(kicker, 21); bold(kicker); color(kicker, cyan); hidden(kicker);
text(headline, (540, 228), "The trapped light beam");
size(headline, 43); bold(headline); color(headline, fg); hidden(headline);
text(dimension, (540, 300), "1D · A THREAD IN THE DARK");
size(dimension, 23); bold(dimension); color(dimension, gold); hidden(dimension);
text(narration, (540, 1328), "No up. No down. Only backwards and forwards.");
size(narration, 27); bold(narration); color(narration, fg); wrap(narration, 860); hidden(narration);
text(insight, (540, 1386), "One permitted direction");
size(insight, 20); color(insight, dim); hidden(insight);
equation(distance, (540, 1482), `L=\left|x_2-x_1\right|`, 39);
hidden(distance);
// ---------------------------------------------------------------------------
// THE SPATIAL CAST
// ---------------------------------------------------------------------------
camera3((9, -16, 8), (2.5, 0, 0), 38);
// 1D: a five-unit fiber and one persistent photon.
line3(fiber, (0,0,0), (5,0,0)); color(fiber, cyan); thick(fiber, 0.065); untraced(fiber);
point3(A, (0,0,0), 0.16); color(A, fg); hidden(A);
point3(B, (5,0,0), 0.16); color(B, fg); hidden(B);
point3(photon, (0,0,0), 0.22); color(photon, gold); hidden(photon);
finish3(photon, "material=glass shading=smooth depth=0.35 shadow=0.20");
text(aLabel, (0,0), "A · x = 0"); size(aLabel, 21); color(aLabel, fg); hidden(aLabel); pin3(aLabel, A);
text(bLabel, (0,0), "B · x = 5"); size(bLabel, 21); color(bLabel, fg); hidden(bLabel); pin3(bLabel, B);
point3(xMid, (2.5,0,0), 0.01); hidden(xMid);
text(xLabel, (0,0), "a = 5"); size(xLabel, 22); bold(xLabel); color(xLabel, cyan); hidden(xLabel); pin3(xLabel, xMid);
// 2D: pull a copy of the fiber along y. The shallow cuboid is the glass floor.
line3(sweepLine, (0,0,0), (5,0,0)); color(sweepLine, magenta); thick(sweepLine, 0.045); hidden(sweepLine);
line3(yEdge, (0,0,0), (0,12,0)); color(yEdge, magenta); thick(yEdge, 0.045); untraced(yEdge); hidden(yEdge);
point3(yMid, (0,6,0), 0.01); hidden(yMid);
text(yLabel, (0,0), "b = 12"); size(yLabel, 22); bold(yLabel); color(yLabel, magenta); hidden(yLabel); pin3(yLabel, yMid);
cube3(glassFloor, (2.5,6,0), (5,12,0.12)); color(glassFloor, blue); hidden(glassFloor);
finish3(glassFloor, "material=glass shading=flat mesh=0.42 depth=0.30 shadow=0.12");
line3(floorDiagonal, (0,0,0.08), (5,12,0.08)); color(floorDiagonal, gold); thick(floorDiagonal, 0.075); untraced(floorDiagonal); hidden(floorDiagonal);
point3(floorTarget, (5,12,0.08), 0.19); color(floorTarget, fg); hidden(floorTarget);
text(floorTargetLabel, (0,0), "(5, 12)"); size(floorTargetLabel, 20); color(floorTargetLabel, fg); hidden(floorTargetLabel); pin3(floorTargetLabel, floorTarget);
point3(floorMid, (2.5,6,0.08), 0.01); hidden(floorMid);
text(floorPathLabel, (0,0), "base = 13"); size(floorPathLabel, 21); bold(floorPathLabel); color(floorPathLabel, gold); hidden(floorPathLabel); pin3(floorPathLabel, floorMid);
// 3D: the flat floor becomes a transparent room. The 84-unit height is
// compressed to 18 visual units so the reused 13-unit floor diagonal remains
// readable; labels and mathematics retain the real coordinates. Four persistent
// roof edges rise as one tagged rig while the transparent volume resolves.
cube3(room, (2.5,6,9), (5,12,18)); color(room, blue); hidden(room);
finish3(room, "material=glass shading=flat mesh=0.50 depth=0.38 shadow=0.16");
line3(roofFront, (0,0,0), (5,0,0)); color(roofFront, cyan); thick(roofFront, 0.055); hidden(roofFront); tag(roofFront, roof);
line3(roofBack, (0,12,0), (5,12,0)); color(roofBack, cyan); thick(roofBack, 0.055); hidden(roofBack); tag(roofBack, roof);
line3(roofLeft, (0,0,0), (0,12,0)); color(roofLeft, cyan); thick(roofLeft, 0.055); hidden(roofLeft); tag(roofLeft, roof);
line3(roofRight, (5,0,0), (5,12,0)); color(roofRight, cyan); thick(roofRight, 0.055); hidden(roofRight); tag(roofRight, roof);
line3(zEdge, (5,12,0), (5,12,18)); color(zEdge, lime); thick(zEdge, 0.08); untraced(zEdge); hidden(zEdge);
point3(zMid, (5,12,9), 0.01); hidden(zMid);
text(zLabel, (0,0), "c = 84"); size(zLabel, 22); bold(zLabel); color(zLabel, lime); hidden(zLabel); pin3(zLabel, zMid);
line3(spaceDiagonal, (0,0,0), (5,12,18)); color(spaceDiagonal, gold); thick(spaceDiagonal, 0.10); untraced(spaceDiagonal); hidden(spaceDiagonal);
point3(spaceTarget, (5,12,18), 0.24); color(spaceTarget, fg); hidden(spaceTarget);
text(spaceTargetLabel, (0,0), "(5, 12, 84)"); size(spaceTargetLabel, 20); color(spaceTargetLabel, fg); hidden(spaceTargetLabel); pin3(spaceTargetLabel, spaceTarget);
// ---------------------------------------------------------------------------
// THE JOURNEY
// ---------------------------------------------------------------------------
step("one dimension") {
seq {
par {
show(kicker, 0.35);
show(headline, 0.55);
show(dimension, 0.40);
show(narration, 0.45);
show(insight, 0.45);
show(distance, 0.55);
view3(fiber, "front", 1.0, smooth, 1.65);
}
stagger(0.12) {
show(A, 0.30);
show(aLabel, 0.30);
draw(fiber, 1.10, smooth);
show(B, 0.30);
show(bLabel, 0.30);
show(xLabel, 0.30);
show(photon, 0.35);
}
wait(0.45);
}
}
step("the bound beam") {
seq {
say(narration, "Fire one photon from A to B. There is nowhere else to go.", 0.50, smooth);
par {
travel3(photon, fiber, 2.25, smooth);
rewrite(distance, `L=\left|5-0\right|=\textcolor{gold}{5}`, 1.10, smooth);
say(insight, "Distance is the length of the only available track", 0.45, smooth);
}
pulse(photon, 0.70);
wait(0.55);
}
}
step("unlock the second direction") {
seq {
par {
move3(photon, (0,0,0), 0.80, smooth);
fade(aLabel, 0.35);
fade(bLabel, 0.35);
say(dimension, "2D · UNFOLDING THE FLOOR", 0.45, smooth);
say(narration, "Pull the fiber sideways. One line sweeps out a reflective surface.", 0.55, smooth);
say(insight, "A new perpendicular direction appears: y", 0.45, smooth);
}
par {
show(yEdge, 0.15);
draw(yEdge, 1.10, smooth);
show(yLabel, 0.35);
view3(glassFloor, "isometric", 1.35, smooth, 1.55);
}
par {
show(sweepLine, 0.25);
move3(sweepLine, (0,12,0), 1.90, smooth);
show(glassFloor, 1.90, smooth);
}
to(glassFloor, opacity, 0.22, 0.40, smooth);
wait(0.45);
}
}
step("cut across the floor") {
seq {
par {
show(floorTarget, 0.30);
show(floorTargetLabel, 0.35);
show(floorPathLabel, 0.35);
say(narration, "The photon can turn now — but the straight diagonal is shortest.", 0.55, smooth);
say(insight, "The old distance and the new sweep meet at 90 degrees", 0.45, smooth);
}
par {
show(floorDiagonal, 0.15);
draw(floorDiagonal, 2.35, smooth);
travel3(photon, floorDiagonal, 2.35, smooth);
rewrite(distance, `L=\sqrt{\textcolor{cyan}{5^2}+\textcolor{magenta}{12^2}}=\sqrt{169}=\textcolor{gold}{13}`, 1.35, smooth);
}
pulse(photon, 0.70);
wait(0.65);
}
}
step("raise the roof") {
seq {
par {
move3(photon, (0,0,0), 0.90, smooth);
fade(xLabel, 0.35);
fade(yLabel, 0.35);
fade(floorTargetLabel, 0.35);
say(dimension, "3D · FILLING THE VOID", 0.45, smooth);
say(narration, "Lift the entire floor upward. Surface becomes volume.", 0.55, smooth);
say(insight, "Height compressed visually · coordinates and geometry stay exact", 0.45, smooth);
}
par {
to(room, opacity, 0.22, 2.45, smooth);
show(roof, 0.20);
shift3(roof, (0,0,18), 2.45, smooth);
view3(room, "isometric", 2.45, smooth, 1.62);
show(zEdge, 0.15);
draw(zEdge, 2.30, smooth);
show(zLabel, 0.45);
}
par { show(spaceTarget, 0.40); show(spaceTargetLabel, 0.40); }
wait(0.55);
}
}
step("cut through space") {
seq {
say(narration, "The photon cuts through open space: floor diagonal against height.", 0.55, smooth);
par {
show(spaceDiagonal, 0.15);
draw(spaceDiagonal, 2.80, smooth);
travel3(photon, spaceDiagonal, 2.80, smooth);
rewrite(distance, `L=\sqrt{\textcolor{gold}{13^2}+\textcolor{lime}{84^2}}=\sqrt{7225}=\textcolor{gold}{85}`, 1.50, smooth);
say(insight, "13² + 84² = 85²", 0.45, smooth);
}
pulse(photon, 0.80);
wait(0.70);
}
}
step("one recursive idea") {
seq {
par {
say(dimension, "THE PATTERN · ONE RIGHT ANGLE AT A TIME", 0.50, smooth);
say(narration, "Do not memorize three formulas. Build one distance recursively.", 0.55, smooth);
say(insight, "Each previous diagonal becomes the next dimension's base", 0.50, smooth);
rewrite(distance, `L=\sqrt{\textcolor{cyan}{a^2}+\textcolor{magenta}{b^2}+\textcolor{lime}{c^2}}`, 1.65, smooth);
view3(room, "fit", 2.20, smooth, 1.70);
}
par {
pulse(photon, 0.80);
flash(spaceDiagonal, gold);
roll3(3, 1.60, smooth);
}
wait(2.20);
}
}
dimensions-unfold
A point stretches into a line, the line sweeps sideways into a plane, and the plane lifts into a room. The geometry grows continuously instead of resetting between 1D, 2D, and 3D.
// ============================================================================
// dimensions-unfold.manic — how a point becomes a line, a plane, then a room
// ----------------------------------------------------------------------------
// The geometry never cuts to a replacement scene. One point grows two halves
// of a wire; that same wire sweeps across y while a grid resolves behind it;
// the completed sheet then lifts as one tagged roof while a transparent room
// appears below. The animation is the explanation.
// ============================================================================
title("How Space Learned to Grow — 1D to 3D");
canvas("9:16");
template("neon");
watermark(manicMark, (238, 108), "Made With Manic");
creator(me, "@anish2good name=Manic_3D tagline=Dimensions_in_motion yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social safe=reels");
socials(me);
text(kicker, (540, 164), "FROM A POINT TO A WORLD");
size(kicker, 21); bold(kicker); color(kicker, cyan); hidden(kicker);
text(headline, (540, 228), "How space learned to grow");
size(headline, 42); bold(headline); color(headline, fg); hidden(headline);
text(phase, (540, 300), "PROLOGUE · THE FIRST POINT");
size(phase, 23); bold(phase); color(phase, gold); hidden(phase);
text(narration, (540, 1330), "In the beginning, there was only one location.");
size(narration, 27); bold(narration); color(narration, fg); wrap(narration, 860); hidden(narration);
text(principle, (540, 1390), "No direction. No distance. Just here.");
size(principle, 20); color(principle, dim); hidden(principle);
text(progress, (540, 1492), "POINT");
size(progress, 27); bold(progress); color(progress, cyan); hidden(progress);
text(takeaway, (540, 1554), "DIMENSIONS ARE MOTIONS THAT LEAVE SPACE BEHIND");
size(takeaway, 17); bold(takeaway); color(takeaway, gold); hidden(takeaway);
// ---------------------------------------------------------------------------
// 0D → 1D: one point extends left and right.
// ---------------------------------------------------------------------------
camera3((0, -11, 4.5), (0, -4, 0), 39);
point3(seed, (0,-4,0), 0.12); color(seed, gold); hidden(seed);
finish3(seed, "material=glass shading=smooth depth=0.40 shadow=0.25");
line3(leftThread, (0,-4,0), (-0.02,-4,0)); color(leftThread, cyan); thick(leftThread, 0.065); hidden(leftThread); tag(leftThread, wire); tag(leftThread, sheetSkin);
line3(rightThread, (0,-4,0), (0.02,-4,0)); color(rightThread, cyan); thick(rightThread, 0.065); hidden(rightThread); tag(rightThread, wire); tag(rightThread, sheetSkin);
point3(leftEnd, (-4,-4,0), 0.14); color(leftEnd, fg); hidden(leftEnd);
point3(rightEnd, (4,-4,0), 0.14); color(rightEnd, fg); hidden(rightEnd);
point3(leftLabelAnchor, (-3.25,-4,0.58), 0.01); hidden(leftLabelAnchor);
point3(rightLabelAnchor, (3.25,-4,0.58), 0.01); hidden(rightLabelAnchor);
text(leftLabel, (0,0), "BACKWARD"); size(leftLabel, 19); bold(leftLabel); color(leftLabel, dim); hidden(leftLabel); pin3(leftLabel, leftLabelAnchor);
text(rightLabel, (0,0), "FORWARD"); size(rightLabel, 19); bold(rightLabel); color(rightLabel, dim); hidden(rightLabel); pin3(rightLabel, rightLabelAnchor);
arrow3(leftChoice, (0,-4,0.18), (-3.5,-4,0.18)); color(leftChoice, magenta); thick(leftChoice, 0.035); untraced(leftChoice); hidden(leftChoice);
arrow3(rightChoice, (0,-4,0.18), (3.5,-4,0.18)); color(rightChoice, magenta); thick(rightChoice, 0.035); untraced(rightChoice); hidden(rightChoice);
// ---------------------------------------------------------------------------
// 2D: the wire sweeps from y=-4 to y=4 and leaves a sheet/grid behind.
// Every visible grid member joins sheetSkin so the whole plane can later rise.
// ---------------------------------------------------------------------------
line3(baseEdge, (-4,-4,0), (4,-4,0)); color(baseEdge, cyan); thick(baseEdge, 0.050); hidden(baseEdge); tag(baseEdge, sheetSkin);
cube3(sheet, (0,0,0), (8,8,0.08)); color(sheet, blue); hidden(sheet); tag(sheet, sheetSkin);
finish3(sheet, "material=glass shading=flat mesh=0.18 depth=0.24 shadow=0.10");
for i in 1..8 {
let p = -4+i;
line3(row{i}, (-4,p,0.05), (4,p,0.05));
color(row{i}, magenta); thick(row{i}, 0.022); untraced(row{i}); hidden(row{i}); tag(row{i}, sheetGrid); tag(row{i}, sheetSkin);
line3(col{i}, (p,-4,0.05), (p,4,0.05));
color(col{i}, cyan); thick(col{i}, 0.022); untraced(col{i}); hidden(col{i}); tag(col{i}, sheetGrid); tag(col{i}, sheetSkin);
}
// ---------------------------------------------------------------------------
// 3D: the complete sheet becomes the roof. Four pillars and a restrained glass
// volume make the hollow space below legible without a replacement cut.
// ---------------------------------------------------------------------------
cube3(room, (0,0,4), (8,8,8)); color(room, blue); hidden(room);
finish3(room, "material=glass shading=flat mesh=0.42 depth=0.34 shadow=0.16");
line3(pillar0, (-4,-4,0), (-4,-4,8)); color(pillar0, lime); thick(pillar0, 0.060); untraced(pillar0); hidden(pillar0); tag(pillar0, pillars);
line3(pillar1, (4,-4,0), (4,-4,8)); color(pillar1, lime); thick(pillar1, 0.060); untraced(pillar1); hidden(pillar1); tag(pillar1, pillars);
line3(pillar2, (-4,4,0), (-4,4,8)); color(pillar2, lime); thick(pillar2, 0.060); untraced(pillar2); hidden(pillar2); tag(pillar2, pillars);
line3(pillar3, (4,4,0), (4,4,8)); color(pillar3, lime); thick(pillar3, 0.060); untraced(pillar3); hidden(pillar3); tag(pillar3, pillars);
point3(nearCorner, (-4,-4,0), 0.18); color(nearCorner, gold); hidden(nearCorner);
point3(farCorner, (4,4,8), 0.18); color(farCorner, gold); hidden(farCorner);
text(nearLabel, (0,0), "NEAR · BOTTOM · LEFT"); size(nearLabel, 18); color(nearLabel, fg); hidden(nearLabel); pin3(nearLabel, nearCorner);
text(farLabel, (0,0), "FAR · TOP · RIGHT"); size(farLabel, 18); color(farLabel, fg); hidden(farLabel); pin3(farLabel, farCorner);
// ---------------------------------------------------------------------------
// ONE CONTINUOUS TIMELINE
// ---------------------------------------------------------------------------
step("the first point") {
seq {
par {
show(kicker, 0.35);
show(headline, 0.55);
show(phase, 0.40);
show(narration, 0.45);
show(principle, 0.45);
show(progress, 0.40);
show(seed, 0.45);
}
pulse(seed, 0.80);
wait(0.65);
}
}
step("the point extends") {
seq {
par {
say(phase, "1D · THE FIRST THREAD", 0.45, smooth);
say(narration, "The point stretches itself out and creates the first path.", 0.55, smooth);
say(principle, "Length appears — but the world is still trapped on one track.", 0.50, smooth);
say(progress, "POINT → LENGTH", 0.45, smooth);
show(leftThread, 0.15);
show(rightThread, 0.15);
grow3(leftThread, (-4,-4,0), 2.25, smooth);
grow3(rightThread, (4,-4,0), 2.25, smooth);
view3(wire, "front", 1.45, smooth, 1.70);
}
par {
fade(seed, 0.35);
show(leftEnd, 0.30); show(rightEnd, 0.30);
show(leftLabel, 0.35); show(rightLabel, 0.35);
show(leftChoice, 0.15); show(rightChoice, 0.15);
draw(leftChoice, 0.90, smooth); draw(rightChoice, 0.90, smooth);
}
wait(0.60);
}
}
step("left or right") {
seq {
say(narration, "Every journey has only two choices: backward or forward.", 0.50, smooth);
par { pulse(leftThread, 0.65); pulse(rightThread, 0.65); }
wait(0.80);
}
}
step("the sideways sweep") {
seq {
par {
fade(leftChoice, 0.35); fade(rightChoice, 0.35);
fade(leftLabel, 0.35); fade(rightLabel, 0.35);
say(phase, "2D · THE UNFOLDING SHEET", 0.45, smooth);
say(narration, "The wire shimmers, grabs its whole length, and moves sideways.", 0.55, smooth);
say(principle, "Its motion leaves a flat floor behind: width is born.", 0.50, smooth);
say(progress, "POINT → LENGTH → WIDTH", 0.45, smooth);
}
par {
show(baseEdge, 0.18);
shift3(wire, (0,8,0), 2.80, smooth);
to(sheet, opacity, 0.14, 2.80, smooth);
view3(sheet, "isometric", 2.10, smooth, 1.52);
stagger(0.075) { for i in 1..8 { show(row{i}, 0.12); show(col{i}, 0.12); } }
stagger(0.075) { for i in 1..8 { draw(row{i}, 0.70, smooth); draw(col{i}, 0.70, smooth); } }
}
wait(0.65);
}
}
step("the flat world") {
seq {
say(narration, "The old wire is now one edge of an entire plane.", 0.50, smooth);
say(principle, "Forward, backward, left, and right — still no above.", 0.45, smooth);
pulse(sheetGrid, 0.75);
wait(0.80);
}
}
step("the rising room") {
seq {
par {
say(phase, "3D · THE RISING ROOM", 0.45, smooth);
say(narration, "The complete sheet catches a breath and lifts straight upward.", 0.55, smooth);
say(principle, "The moving surface fills the hollow space below it.", 0.50, smooth);
say(progress, "POINT → LENGTH → WIDTH → HEIGHT", 0.50, smooth);
}
par {
shift3(sheetSkin, (0,0,8), 2.85, smooth);
to(room, opacity, 0.16, 2.85, smooth);
show(pillars, 0.15);
draw(pillars, 2.70, smooth);
view3(room, "isometric", 2.85, smooth, 1.52);
}
wait(0.65);
}
}
step("the space within") {
seq {
par {
show(nearCorner, 0.30); show(farCorner, 0.30);
show(nearLabel, 0.35); show(farLabel, 0.35);
show(takeaway, 0.45);
say(phase, "THE SPACE WITHIN", 0.45, smooth);
say(narration, "Depth rushes in. Near and far finally have meaning.", 0.55, smooth);
say(principle, "A dimension is an old world moving in a new perpendicular direction.", 0.55, smooth);
view3(room, "fit", 1.80, smooth, 1.62);
}
par {
pulse(sheetSkin, 0.85);
pulse(pillars, 0.85);
roll3(3, 1.80, smooth);
}
wait(2.20);
}
}
textbook-length-area-volume
Why units become cm, cm², and cm³: one measured segment sweeps out a rectangle, then the rectangle rises into a cuboid. A textbook measurement story built from extrusion rather than three disconnected formulas.
// ============================================================================
// textbook-length-area-volume.manic — centimetres, square centimetres, cubes
// ============================================================================
title("Length, Area, Volume — Why the Units Change");
canvas("9:16");
template("blueprint");
watermark(manicMark, (220, 175), "Made With Manic");
creator(me, "@anish2good name=Manic_Math tagline=Textbooks_in_motion yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social safe=reels");
socials(me);
text(kicker, (540,164), "THE MEASUREMENT LADDER"); size(kicker,21); bold(kicker); color(kicker,cyan); hidden(kicker);
text(headline, (540,228), "Why units become squared, then cubed"); size(headline,36); bold(headline); hidden(headline);
text(phase, (540,300), "1D · MEASURE LENGTH"); size(phase,23); bold(phase); color(phase,gold); hidden(phase);
text(narration, (540,1328), "A ruler counts how many unit segments fit along one direction."); size(narration,26); bold(narration); wrap(narration,850); hidden(narration);
text(principle, (540,1388), "One direction · centimetres"); size(principle,20); color(principle,dim); hidden(principle);
equation(measure, (540,1480), `L=\textcolor{cyan}{6}\text{ cm}`, 40); hidden(measure);
text(takeaway, (540,1560), "THE UNIT CHANGES BECAUSE THE NUMBER OF DIRECTIONS CHANGES"); size(takeaway,18); bold(takeaway); color(takeaway,gold); hidden(takeaway);
// This is the settled `view3(length,"front",...,1.65)` composition: the
// opening line is already inside the Creator media region, so frame one moves
// the mathematics rather than jolting the camera into place.
camera3((0.26,-33.6,-4.14),(0.26,-2,-4.14),40);
line3(length, (-3,-2,0), (3,-2,0)); color(length,cyan); thick(length,0.07); untraced(length); tag(length,areaSkin);
point3(start, (-3,-2,0),0.15); color(start,fg); hidden(start);
point3(finish, (3,-2,0),0.15); color(finish,fg); hidden(finish);
point3(lengthMid, (0,-2,0),0.01); hidden(lengthMid);
text(lengthLabel,(0,0),"6 cm"); size(lengthLabel,22); bold(lengthLabel); color(lengthLabel,cyan); hidden(lengthLabel); pin3(lengthLabel,lengthMid);
// Six visible unit segments sit directly on the measured line.
for i in 0..6 {
line3(unit{i}, (-3+i,-2,0.10), (-2+i,-2,0.10));
color(unit{i},gold); thick(unit{i},0.035); untraced(unit{i}); hidden(unit{i}); tag(unit{i},units1d); tag(unit{i},areaSkin);
}
// The measured line sweeps four centimetres sideways to create 24 unit squares.
cube3(areaSheet, (0,0,0), (6,4,0.08)); color(areaSheet,blue); hidden(areaSheet); tag(areaSheet,areaSkin);
finish3(areaSheet,"material=glass shading=flat mesh=0.20 depth=0.25 shadow=0.10");
line3(areaBase,(-3,-2,0),(3,-2,0)); color(areaBase,cyan); thick(areaBase,0.05); hidden(areaBase); tag(areaBase,areaSkin);
for i in 1..4 {
let yy=-2+i;
line3(areaRow{i},(-3,yy,0.05),(3,yy,0.05)); color(areaRow{i},magenta); thick(areaRow{i},0.025); untraced(areaRow{i}); hidden(areaRow{i}); tag(areaRow{i},areaGrid); tag(areaRow{i},areaSkin);
}
for i in 0..7 {
let xx=-3+i;
line3(areaCol{i},(xx,-2,0.05),(xx,2,0.05)); color(areaCol{i},cyan); thick(areaCol{i},0.025); untraced(areaCol{i}); hidden(areaCol{i}); tag(areaCol{i},areaGrid); tag(areaCol{i},areaSkin);
}
point3(widthMid,(-3,0,0),0.01); hidden(widthMid);
text(widthLabel,(0,0),"4 cm"); size(widthLabel,22); bold(widthLabel); color(widthLabel,magenta); hidden(widthLabel); pin3(widthLabel,widthMid);
// The complete square-unit sheet rises three centimetres and leaves 72 cubes.
cube3(volumeBox,(0,0,1.5),(6,4,3)); color(volumeBox,blue); hidden(volumeBox);
finish3(volumeBox,"material=glass shading=flat mesh=0.48 depth=0.34 shadow=0.16");
line3(height0,(-3,-2,0),(-3,-2,3)); color(height0,lime); thick(height0,0.06); untraced(height0); hidden(height0); tag(height0,heightEdges);
line3(height1,(3,-2,0),(3,-2,3)); color(height1,lime); thick(height1,0.06); untraced(height1); hidden(height1); tag(height1,heightEdges);
line3(height2,(-3,2,0),(-3,2,3)); color(height2,lime); thick(height2,0.06); untraced(height2); hidden(height2); tag(height2,heightEdges);
line3(height3,(3,2,0),(3,2,3)); color(height3,lime); thick(height3,0.06); untraced(height3); hidden(height3); tag(height3,heightEdges);
point3(heightMid,(3,2,1.5),0.01); hidden(heightMid);
text(heightLabel,(0,0),"3 cm"); size(heightLabel,22); bold(heightLabel); color(heightLabel,lime); hidden(heightLabel); pin3(heightLabel,heightMid);
step("count one direction") {
seq {
par { show(kicker,0.35); show(headline,0.55); show(phase,0.40); show(narration,0.45); show(principle,0.40); show(measure,0.50); }
par { show(start,0.25); show(finish,0.25); draw(length,1.25,smooth); show(lengthLabel,0.35); }
par {
stagger(0.10) { for i in 0..6 { show(unit{i},0.10); } }
stagger(0.10) { for i in 0..6 { draw(unit{i},0.35,smooth); } }
}
wait(0.60);
}
}
step("sweep out area") {
seq {
par {
say(phase,"2D · COUNT UNIT SQUARES",0.45,smooth);
say(narration,"Move the six-centimetre length through four new centimetres.",0.55,smooth);
say(principle,"Two perpendicular directions · square centimetres",0.45,smooth);
rewrite(measure,`A=\textcolor{cyan}{6}\times\textcolor{magenta}{4}=\textcolor{gold}{24}\text{ cm}^2`,1.10,smooth);
fade(start,0.30); fade(finish,0.30);
view3(areaSheet,"isometric",1.25,smooth,1.48);
}
par {
shift3(units1d,(0,4,0),2.60,smooth);
shift3(length,(0,4,0),2.60,smooth);
show(areaBase,0.15);
to(areaSheet,opacity,0.15,2.60,smooth);
show(widthLabel,0.35);
stagger(0.07) { for i in 1..4 { show(areaRow{i},0.10); } for i in 0..7 { show(areaCol{i},0.10); } }
stagger(0.07) { for i in 1..4 { draw(areaRow{i},0.55,smooth); } for i in 0..7 { draw(areaCol{i},0.55,smooth); } }
}
wait(0.70);
}
}
step("see twenty four squares") {
seq {
say(narration,"Six columns by four rows: the floor contains 24 unit squares.",0.50,smooth);
par { pulse(areaGrid,0.75); pulse(areaSheet,0.75); }
wait(0.75);
}
}
step("stack into volume") {
seq {
par {
say(phase,"3D · STACK UNIT CUBES",0.45,smooth);
say(narration,"Lift the complete 24-square layer through three centimetres.",0.55,smooth);
say(principle,"Three perpendicular directions · cubic centimetres",0.45,smooth);
rewrite(measure,`V=\textcolor{gold}{24}\times\textcolor{lime}{3}=\textcolor{gold}{72}\text{ cm}^3`,1.10,smooth);
fade(lengthLabel,0.30); fade(widthLabel,0.30);
}
par {
shift3(areaSkin,(0,0,3),2.70,smooth);
to(volumeBox,opacity,0.17,2.70,smooth);
show(heightEdges,0.15); draw(heightEdges,2.55,smooth);
show(heightLabel,0.35);
view3(volumeBox,"isometric",2.70,smooth,1.50);
}
wait(0.70);
}
}
step("the measurement ladder") {
seq {
par {
show(takeaway,0.45);
say(phase,"LENGTH → AREA → VOLUME",0.50,smooth);
say(narration,"The number is not merely larger. The kind of unit has changed.",0.55,smooth);
say(principle,"cm measures a path · cm² tiles a surface · cm³ fills space",0.55,smooth);
view3(volumeBox,"fit",1.80,smooth,1.58);
}
par { pulse(volumeBox,0.85); pulse(heightEdges,0.85); roll3(3,1.60,smooth); }
wait(2.10);
}
}
textbook-coordinate-worlds
A point earns a longer address as dimensions unlock: x on a line, (x,y) on a plane, then (x,y,z) in space. Coordinates remain attached to the same idea while the world expands around it.
// ============================================================================
// textbook-coordinate-worlds.manic — number line → coordinate plane → space
// ============================================================================
title("A Point Gets an Address — From 1D to 3D");
canvas("9:16");
template("blueprint");
watermark(manicMark, (220, 175), "Made With Manic");
creator(me, "@anish2good name=Manic_Math tagline=Textbooks_in_motion yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social safe=reels");
socials(me);
text(kicker,(540,164),"COORDINATE GEOMETRY"); size(kicker,21); bold(kicker); color(kicker,cyan); hidden(kicker);
text(headline,(540,228),"A point gains one coordinate at a time"); size(headline,32); bold(headline); hidden(headline);
text(phase,(540,300),"1D · THE NUMBER LINE"); size(phase,23); bold(phase); color(phase,gold); hidden(phase);
text(narration,(540,1328),"One direction needs one number: move three units along x."); size(narration,26); bold(narration); wrap(narration,850); hidden(narration);
text(principle,(540,1392),"Every new perpendicular direction adds one coordinate."); size(principle,20); color(principle,dim); hidden(principle);
equation(address,(540,1490),`P=\textcolor{cyan}{(3)}`,44); hidden(address);
text(takeaway,(540,1572),"THE POINT STAYS THE SAME · ITS ADDRESS BECOMES MORE PRECISE"); size(takeaway,18); bold(takeaway); color(takeaway,gold); hidden(takeaway);
camera3((0,0,14),(0,0,0),38);
// One persistent point begins at the origin, then gains y and z coordinates.
point3(P,(0,0,0),0.18); color(P,gold); hidden(P);
text(pLabel,(0,0),"P"); size(pLabel,34); bold(pLabel); color(pLabel,gold); hidden(pLabel); label3(pLabel,P,0.34);
point3(origin,(0,0,0),0.10); color(origin,fg); hidden(origin);
text(originLabel,(0,0),"O"); size(originLabel,34); color(originLabel,dim); hidden(originLabel); label3(originLabel,origin,0.36);
line3(xAxis,(-4,0,0),(4,0,0)); color(xAxis,cyan); thick(xAxis,0.045); untraced(xAxis); hidden(xAxis); tag(xAxis,axes);
line3(yAxis,(0,-3,0),(0,3,0)); color(yAxis,magenta); thick(yAxis,0.045); untraced(yAxis); hidden(yAxis); tag(yAxis,axes);
line3(zAxis,(0,0,0),(0,0,4)); color(zAxis,lime); thick(zAxis,0.045); untraced(zAxis); hidden(zAxis); tag(zAxis,axes);
point3(xEnd,(4,0,0),0.01); hidden(xEnd);
point3(yEnd,(0,3,0),0.01); hidden(yEnd);
point3(zEnd,(0,0,4),0.01); hidden(zEnd);
text(xLabel,(0,0),"x"); size(xLabel,34); bold(xLabel); color(xLabel,cyan); hidden(xLabel); label3(xLabel,xEnd,0.38);
text(yLabel,(0,0),"y"); size(yLabel,34); bold(yLabel); color(yLabel,magenta); hidden(yLabel); label3(yLabel,yEnd,0.38);
text(zLabel,(0,0),"z"); size(zLabel,34); bold(zLabel); color(zLabel,lime); hidden(zLabel); label3(zLabel,zEnd,0.38);
// Tick marks remain quiet so the address, rather than decoration, leads.
for i in -4..5 {
line3(xTick{i},(i,-0.10,0),(i,0.10,0)); color(xTick{i},cyan); thick(xTick{i},0.025); untraced(xTick{i}); hidden(xTick{i}); tag(xTick{i},xTicks);
}
for i in -3..4 {
line3(yTick{i},(-0.10,i,0),(0.10,i,0)); color(yTick{i},magenta); thick(yTick{i},0.025); untraced(yTick{i}); hidden(yTick{i}); tag(yTick{i},yTicks);
}
for i in 1..5 {
line3(zTick{i},(-0.10,0,i),(0.10,0,i)); color(zTick{i},lime); thick(zTick{i},0.025); untraced(zTick{i}); hidden(zTick{i}); tag(zTick{i},zTicks);
}
grid3(plane,(0,0,0),4,1); color(plane,blue); opacity(plane,0.22); hidden(plane); tag(plane,world2d);
cube3(space,(0,0,2),(8,6,4)); color(space,blue); hidden(space); tag(space,world3d);
finish3(space,"material=glass shading=flat mesh=0.18 depth=0.22 shadow=0.10");
// Guides expose how each coordinate is read from the axes.
line3(xGuide,(0,0,0),(3,0,0)); color(xGuide,cyan); thick(xGuide,0.065); untraced(xGuide); hidden(xGuide); tag(xGuide,guides);
line3(yGuide,(3,0,0),(3,2,0)); color(yGuide,magenta); thick(yGuide,0.065); untraced(yGuide); hidden(yGuide); tag(yGuide,guides);
line3(zGuide,(3,2,0),(3,2,3)); color(zGuide,lime); thick(zGuide,0.065); untraced(zGuide); hidden(zGuide); tag(zGuide,guides);
point3(xFoot,(3,0,0),0.11); color(xFoot,cyan); hidden(xFoot);
point3(xyFoot,(3,2,0),0.11); color(xyFoot,magenta); hidden(xyFoot);
project3(shadow,P,"xy"); color(shadow,magenta); hidden(shadow);
link3(drop,P,shadow,0.12); color(drop,lime); thick(drop,0.025); hidden(drop);
step("one number locates the point") {
seq {
par { show(kicker,0.35); show(headline,0.55); show(phase,0.40); show(narration,0.45); show(principle,0.40); show(address,0.50); view3(xAxis,"top",0.9,smooth,1.65); }
par { show(origin,0.20); show(originLabel,0.25); show(P,0.25); show(pLabel,0.25); draw(xAxis,1.10,smooth); show(xLabel,0.25); show(xTicks,0.35); }
par { shift3(P,(3,0,0),1.65,smooth); draw(xGuide,1.65,smooth); }
par { show(xFoot,0.25); pulse(P,0.65); pulse(address,0.65); }
wait(0.60);
}
}
step("the plane adds a second direction") {
seq {
par {
say(phase,"2D · THE COORDINATE PLANE",0.45,smooth);
say(narration,"Open a perpendicular y-direction, then move two units sideways.",0.55,smooth);
rewrite(address,`P=\textcolor{cyan}{(3},\textcolor{magenta}{2)}`,0.95,smooth);
show(plane,0.80); draw(yAxis,1.35,smooth); show(yLabel,0.30); show(yTicks,0.40);
view3(plane,"top",1.55,smooth,1.55);
}
par { shift3(P,(0,2,0),1.75,smooth); draw(yGuide,1.75,smooth); }
par { show(xyFoot,0.25); pulse(guides,0.80); pulse(address,0.80); }
wait(0.70);
}
}
step("read the ordered pair") {
seq {
say(narration,"First read x, then y. Order matters: (3,2) is not (2,3).",0.50,smooth);
par { pulse(xGuide,0.60); pulse(yGuide,0.60); }
wait(0.85);
}
}
step("space adds height") {
seq {
par {
say(phase,"3D · COORDINATE SPACE",0.45,smooth);
say(narration,"Raise a z-axis out of the plane and lift the point three units.",0.55,smooth);
rewrite(address,`P=\textcolor{cyan}{(3},\textcolor{magenta}{2},\textcolor{lime}{3)}`,1.05,smooth);
to(space,opacity,0.14,2.40,smooth);
draw(zAxis,1.55,smooth); show(zLabel,0.30); show(zTicks,0.40);
view3(space,"isometric",2.35,smooth,1.48);
}
par {
shift3(P,(0,0,3),2.15,smooth);
draw(zGuide,2.15,smooth);
show(shadow,0.30); show(drop,0.30);
}
par { pulse(P,0.70); pulse(address,0.70); }
wait(0.70);
}
}
step("one point three coordinates") {
seq {
par {
show(takeaway,0.45);
say(phase,"ONE ADDRESS · THREE DIRECTIONS",0.50,smooth);
say(narration,"x says across, y says sideways, and z says how high.",0.55,smooth);
say(principle,"1D: (x) · 2D: (x,y) · 3D: (x,y,z)",0.55,smooth);
view3(space,"fit",1.70,smooth,1.55);
}
par { pulse(axes,0.90); pulse(P,0.90); roll3(3,1.55,smooth); }
wait(1.80);
}
}
textbook-function-to-solid
A diameter becomes a semicircle and the semicircle revolves into a sphere. The story links a 1D domain, a 2D graph, and a 3D solid through one continuous generating motion.
// ============================================================================
// textbook-function-to-solid.manic — interval → graph → solid of revolution
// ============================================================================
title("The Revolving Semicircle — From 1D to 3D");
canvas("9:16");
template("neon");
watermark(manicMark,(220,175),"Made With Manic");
creator(me,"@anish2good name=Manic_Math tagline=Textbooks_in_motion yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social safe=reels");
socials(me);
text(kicker,(540,164),"SOLIDS OF REVOLUTION"); size(kicker,21); bold(kicker); color(kicker,cyan); hidden(kicker);
text(headline,(540,228),"A 1D domain grows into a 3D sphere"); size(headline,41); bold(headline); hidden(headline);
text(phase,(540,300),"1D · CHOOSE THE DOMAIN"); size(phase,23); bold(phase); color(phase,gold); hidden(phase);
text(narration,(540,1328),"Begin with every x-value from −2 to 2 on a number line."); size(narration,26); bold(narration); wrap(narration,850); hidden(narration);
text(principle,(540,1392),"The interval controls where the graph exists."); size(principle,20); color(principle,dim); hidden(principle);
equation(formula,(540,1488),`x\in\textcolor{cyan}{[-2,2]}`,44); hidden(formula);
text(takeaway,(540,1572),"DOMAIN → PROFILE → ROTATION → VOLUME"); size(takeaway,18); bold(takeaway); color(takeaway,gold); hidden(takeaway);
camera3((0,-14,1.8),(0,0,0.7),39);
// The x-axis and interval are the 1D seed for the whole construction.
line3(xAxis,(-2.6,0,0),(2.6,0,0)); color(xAxis,dim); thick(xAxis,0.035); untraced(xAxis); hidden(xAxis);
line3(domain,(-2,0,0),(2,0,0)); color(domain,cyan); thick(domain,0.09); untraced(domain); hidden(domain); tag(domain,profile);
point3(leftEnd,(-2,0,0),0.13); color(leftEnd,cyan); hidden(leftEnd);
point3(rightEnd,(2,0,0),0.13); color(rightEnd,cyan); hidden(rightEnd);
point3(origin,(0,0,0),0.09); color(origin,fg); hidden(origin);
point3(xEnd,(2.6,0,0),0.01); hidden(xEnd);
text(xLabel,(0,0),"x"); size(xLabel,23); bold(xLabel); color(xLabel,fg); hidden(xLabel); label3(xLabel,xEnd,0.30);
// The upper semicircle is a textbook profile: y = sqrt(4 - x^2).
curve3(semicircle,"-2+4*t","0","sqrt(4-(-2+4*t)^2)",(0,1)); color(semicircle,magenta); thick(semicircle,0.065); untraced(semicircle); hidden(semicircle); tag(semicircle,profile);
param3(region,
"-2+4*u",
"0",
"v*sqrt(4-(-2+4*u)^2)",
(0,1),(0,1),32);
color(region,magenta); hidden(region); tag(region,profile);
finish3(region,"material=glass shading=flat mesh=0.18 depth=0.18 shadow=0.08");
// Three sample heights explain how the graph rises from the domain.
line3(heightA,(-1,0,0),(-1,0,1.732)); color(heightA,gold); thick(heightA,0.035); untraced(heightA); hidden(heightA); tag(heightA,samples); tag(heightA,profile);
line3(heightB,(0,0,0),(0,0,2)); color(heightB,gold); thick(heightB,0.035); untraced(heightB); hidden(heightB); tag(heightB,samples); tag(heightB,profile);
line3(heightC,(1,0,0),(1,0,1.732)); color(heightC,gold); thick(heightC,0.035); untraced(heightC); hidden(heightC); tag(heightC,samples); tag(heightC,profile);
point3(apex,(0,0,2),0.10); color(apex,gold); hidden(apex); tag(apex,profile);
// Rotating the filled profile around x generates a radius-2 sphere.
param3(sphere,
"2*cos(u)",
"2*sin(u)*cos(v)",
"2*sin(u)*sin(v)",
(0,3.1416),(0,6.2832),38);
color(sphere,blue); hidden(sphere); tag(sphere,solid);
finish3(sphere,"material=glass shading=smooth mesh=0.24 depth=0.34 shadow=0.16");
curve3(equator,"0","2*cos(t)","2*sin(t)",(0,6.2832)); color(equator,cyan); thick(equator,0.035); untraced(equator); hidden(equator); tag(equator,solid);
curve3(meridian,"2*cos(t)","0","2*sin(t)",(0,6.2832)); color(meridian,magenta); thick(meridian,0.035); untraced(meridian); hidden(meridian); tag(meridian,solid);
step("mark the one dimensional domain") {
seq {
par { show(kicker,0.35); show(headline,0.55); show(phase,0.40); show(narration,0.45); show(principle,0.40); show(formula,0.50); view3(domain,"front",0.9,smooth,1.72); }
par { draw(xAxis,0.90,smooth); draw(domain,1.35,smooth); show(leftEnd,0.25); show(rightEnd,0.25); show(origin,0.25); show(xLabel,0.25); }
par { pulse(domain,0.75); pulse(formula,0.75); }
wait(0.65);
}
}
step("the function supplies height") {
seq {
par {
say(phase,"2D · DRAW THE PROFILE",0.45,smooth);
say(narration,"At each x, the function supplies a height above the axis.",0.55,smooth);
say(principle,"The interval becomes the base of an upper semicircle.",0.45,smooth);
rewrite(formula,`y=\textcolor{magenta}{\sqrt{4-x^2}},\quad -2\le x\le2`,1.05,smooth);
draw(semicircle,2.10,smooth);
stagger(0.22) { show(heightA,0.20); show(heightB,0.20); show(heightC,0.20); }
stagger(0.22) { draw(heightA,0.65,smooth); draw(heightB,0.65,smooth); draw(heightC,0.65,smooth); }
}
par { show(apex,0.25); pulse(samples,0.75); }
wait(0.70);
}
}
step("fill the generating region") {
seq {
par {
say(narration,"The curve and the x-axis enclose the region that will rotate.",0.50,smooth);
to(region,opacity,0.26,1.65,smooth);
view3(region,"front",1.40,smooth,1.62);
}
par { pulse(region,0.75); pulse(semicircle,0.75); }
wait(0.75);
}
}
step("rotate the profile into space") {
seq {
par {
say(phase,"3D · ROTATE ABOUT THE x-AXIS",0.45,smooth);
say(narration,"Sweep the entire semicircle through 360° around its diameter.",0.55,smooth);
say(principle,"Every point traces a circle; together those circles form a sphere.",0.50,smooth);
rewrite(formula,`x^2+y^2+z^2=\textcolor{gold}{4}`,1.05,smooth);
turn3(profile,(0,0,0),x,360,3.10,smooth);
to(sphere,opacity,0.34,3.10,smooth);
show(equator,0.45); draw(equator,2.15,smooth);
show(meridian,0.45); draw(meridian,2.15,smooth);
view3(solid,"isometric",2.60,smooth,1.55);
}
par { pulse(solid,0.85); roll3(4,1.50,smooth); }
wait(0.75);
}
}
step("read the solid of revolution") {
seq {
par {
show(takeaway,0.45);
say(phase,"THE SAME RADIUS · A NEW DIMENSION",0.50,smooth);
say(narration,"The 2D radius is still 2, but rotation has filled 3D volume.",0.55,smooth);
say(principle,"A semicircle rotated about its diameter generates a sphere.",0.50,smooth);
rewrite(formula,`V=\frac{4}{3}\pi(2)^3=\textcolor{gold}{\frac{32\pi}{3}}`,1.15,smooth);
view3(solid,"fit",1.65,smooth,1.58);
}
par { pulse(sphere,0.90); pulse(formula,0.90); roll3(3,1.50,smooth); }
wait(1.90);
}
}
textbook-statistical-dimensions
Data grows from a one-variable number line to a two-variable scatter plot and a three-variable point cloud. The axes and observations evolve together so statistical dimension reads as information, not decoration.
// ============================================================================
// textbook-statistical-dimensions.manic
// One variable → two variables → three variables, using the same 12 students.
// Stats kit: summary + correlation. 3D primitives reveal the third variable.
// ============================================================================
title("Statistical Dimensions — From a List to a Data Cloud");
canvas("9:16");
template("blueprint");
watermark(manicMark,(220,175),"Made With Manic");
creator(me,"@anish2good name=Manic_Stats tagline=Textbooks_in_motion yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social safe=reels");
socials(me);
text(kicker,(540,164),"MULTIVARIATE STATISTICS"); size(kicker,21); bold(kicker); color(kicker,cyan); hidden(kicker);
text(headline,(540,228),"The same students, in more dimensions"); size(headline,33); bold(headline); hidden(headline);
text(phase,(540,300),"1D · ONE VARIABLE"); size(phase,23); bold(phase); color(phase,gold); hidden(phase);
text(narration,(540,1328),"Record only height. Each student needs one number."); size(narration,26); bold(narration); wrap(narration,850); hidden(narration);
text(principle,(540,1392),"Centre and spread describe one-dimensional data."); size(principle,20); color(principle,dim); hidden(principle);
equation(address,(540,1488),`\text{student}=\textcolor{cyan}{(\text{height})}`,39); hidden(address);
text(takeaway,(540,1572),"MORE VARIABLES DO NOT ADD PEOPLE · THEY ADD STRUCTURE"); size(takeaway,18); bold(takeaway); color(takeaway,gold); hidden(takeaway);
let vx=518;
let vy=610;
// Twelve students, measured in centimetres.
summary(one,(vx,vy),
"150 154 158 160 164 167 170 173 176 180 184 188",760,cyan);
untraced(one.line); hidden(one.dots); hidden(one.band);
hidden(one.meanmark); hidden(one.meanlbl);
hidden(one.medianmark); hidden(one.medianlbl);
hidden(one.readout); hidden(one.min); hidden(one.max);
// The same students: height and arm span. Target points remain hidden because
// the 1D dots will `become` these blueprints, preserving one identity per row.
correlation(two,(vx,vy),10,
"150 151 154 153 158 160 160 159 164 165 167 168 170 169 173 175 176 174 180 181 184 185 188 187",cyan);
hidden(two.points); untraced(two.line); hidden(two.r);
// Front view: x = height, screen-up z = arm span. Depth y stores shoe size.
// The camera begins in the exact 2D projection; its later isometric move is
// what makes the third variable perceptible.
camera3((0.53,-64.4,-5.34),(0.53,0,-5.34),40);
cube3(dataRoom,(0,0,3.1),(7.2,5.6,6.2)); color(dataRoom,blue); hidden(dataRoom); tag(dataRoom,multivariate);
finish3(dataRoom,"material=glass shading=flat mesh=0.17 depth=0.22 shadow=0.10");
line3(heightAxis,(-3.6,-2.8,0),(3.6,-2.8,0)); color(heightAxis,cyan); thick(heightAxis,0.045); untraced(heightAxis); hidden(heightAxis); tag(heightAxis,multivariate); tag(heightAxis,axes3d);
line3(shoeAxis,(-3.6,-2.8,0),(-3.6,2.8,0)); color(shoeAxis,magenta); thick(shoeAxis,0.045); untraced(shoeAxis); hidden(shoeAxis); tag(shoeAxis,multivariate); tag(shoeAxis,axes3d);
line3(armAxis,(-3.6,-2.8,0),(-3.6,-2.8,6.2)); color(armAxis,lime); thick(armAxis,0.045); untraced(armAxis); hidden(armAxis); tag(armAxis,multivariate); tag(armAxis,axes3d);
point3(heightEnd,(3.6,-2.8,0),0.01); hidden(heightEnd);
point3(shoeEnd,(-3.6,2.8,0),0.01); hidden(shoeEnd);
point3(armEnd,(-3.6,-2.8,6.2),0.01); hidden(armEnd);
text(heightLabel,(0,0),"height"); size(heightLabel,25); bold(heightLabel); color(heightLabel,cyan); hidden(heightLabel); label3(heightLabel,heightEnd,0.40);
text(shoeLabel,(0,0),"shoe size"); size(shoeLabel,25); bold(shoeLabel); color(shoeLabel,magenta); hidden(shoeLabel); label3(shoeLabel,shoeEnd,0.40);
text(armLabel,(0,0),"arm span"); size(armLabel,25); bold(armLabel); color(armLabel,lime); hidden(armLabel); label3(armLabel,armEnd,0.40);
// Normalised triples: (height, shoe-size depth, arm-span height).
point3(p0,(-3.167,-2.4,0.000),0.13); color(p0,cyan); hidden(p0); tag(p0,cloud); tag(p0,multivariate);
point3(p1,(-2.500,-1.6,0.333),0.13); color(p1,cyan); hidden(p1); tag(p1,cloud); tag(p1,multivariate);
point3(p2,(-1.833,-1.6,1.500),0.13); color(p2,cyan); hidden(p2); tag(p2,cloud); tag(p2,multivariate);
point3(p3,(-1.500,-0.8,1.333),0.13); color(p3,cyan); hidden(p3); tag(p3,cloud); tag(p3,multivariate);
point3(p4,(-0.833,-0.8,2.333),0.13); color(p4,cyan); hidden(p4); tag(p4,cloud); tag(p4,multivariate);
point3(p5,(-0.333, 0.0,2.833),0.13); color(p5,cyan); hidden(p5); tag(p5,cloud); tag(p5,multivariate);
point3(p6,( 0.167, 0.0,3.000),0.13); color(p6,cyan); hidden(p6); tag(p6,cloud); tag(p6,multivariate);
point3(p7,( 0.667, 0.8,4.000),0.16); color(p7,gold); hidden(p7); tag(p7,cloud); tag(p7,multivariate);
point3(p8,( 1.167, 0.8,3.833),0.13); color(p8,cyan); hidden(p8); tag(p8,cloud); tag(p8,multivariate);
point3(p9,( 1.833, 1.6,5.000),0.13); color(p9,cyan); hidden(p9); tag(p9,cloud); tag(p9,multivariate);
point3(p10,(2.500, 1.6,5.667),0.13); color(p10,cyan); hidden(p10); tag(p10,cloud); tag(p10,multivariate);
point3(p11,(3.167, 2.4,6.000),0.13); color(p11,cyan); hidden(p11); tag(p11,cloud); tag(p11,multivariate);
project3(frontShadow,p7,"xz"); color(frontShadow,gold); hidden(frontShadow); tag(frontShadow,multivariate);
link3(depthLink,p7,frontShadow,0.14); color(depthLink,magenta); thick(depthLink,0.025); hidden(depthLink); tag(depthLink,multivariate);
text(depthNote,(0,0),"shoe size adds depth"); size(depthNote,24); bold(depthNote); color(depthNote,gold); hidden(depthNote); label3(depthNote,p7,0.42);
step("one variable centre and spread") {
seq {
par { show(kicker,0.35); show(headline,0.55); show(phase,0.40); show(narration,0.45); show(principle,0.40); show(address,0.55); }
draw(one.line,0.75,smooth);
par { show(one.min,0.25); show(one.max,0.25); stagger(0.09) { for i in 0..12 { show(one.dot{i},0.18); } } }
par { to(one.band,opacity,0.12,0.65,smooth); show(one.meanmark,0.35); show(one.meanlbl,0.35); show(one.medianmark,0.35); show(one.medianlbl,0.35); }
par { show(one.readout,0.35); pulse(one.dots,0.75); }
wait(0.70);
}
}
step("a second variable reveals relationship") {
seq {
par {
say(phase,"2D · TWO VARIABLES",0.45,smooth);
say(narration,"Add arm span. Every student now becomes an ordered pair.",0.55,smooth);
say(principle,"The slope of the cloud reveals how two measurements move together.",0.50,smooth);
rewrite(address,`\text{student}=\textcolor{cyan}{(\text{height},\text{arm span})}`,1.00,smooth);
fade(one.band,0.45); fade(one.meanmark,0.45); fade(one.meanlbl,0.45);
fade(one.medianmark,0.45); fade(one.medianlbl,0.45); fade(one.readout,0.45);
fade(one.line,0.45); fade(one.min,0.35); fade(one.max,0.35);
}
par {
for i in 0..12 { become(one.dot{i},two.p{i},1.75,smooth); }
}
par { draw(two.line,1.05,smooth); show(two.r,0.45); }
par { pulse(one.dots,0.75); pulse(two.r,0.75); }
wait(0.70);
}
}
step("the front projection still looks two dimensional") {
seq {
say(narration,"A front view can hide depth: the 3D records still resemble the scatterplot.",0.55,smooth);
par {
fade(one.dots,0.80);
fade(two.line,0.80);
fade(two.r,0.80);
stagger(0.08) { for i in 0..12 { show(p{i},0.18); } }
show(heightAxis,0.35); show(armAxis,0.35);
draw(heightAxis,1.10,smooth); draw(armAxis,1.10,smooth);
show(heightLabel,0.30); show(armLabel,0.30);
}
wait(0.75);
}
}
step("rotate to reveal the third variable") {
seq {
par {
say(phase,"3D · THREE VARIABLES",0.45,smooth);
say(narration,"Rotate the view. Shoe size reveals variation the flat plot could not show.",0.55,smooth);
say(principle,"A third coordinate turns a scatterplot into a multivariate cloud.",0.50,smooth);
rewrite(address,`\text{student}=\textcolor{cyan}{(\text{height},\text{arm span},\text{shoe})}`,1.10,smooth);
to(dataRoom,opacity,0.13,2.50,smooth);
show(shoeAxis,0.30); draw(shoeAxis,1.20,smooth); show(shoeLabel,0.30);
view3(multivariate,"isometric",2.65,smooth,1.52);
}
par { show(frontShadow,0.30); show(depthLink,0.35); show(depthNote,0.35); pulse(p7,0.75); }
wait(0.75);
}
}
step("dimensions describe not duplicate") {
seq {
par {
show(takeaway,0.45);
say(phase,"12 STUDENTS · 36 MEASUREMENTS",0.50,smooth);
say(narration,"The sample size stayed twelve; each observation simply gained context.",0.55,smooth);
say(principle,"1D describes · 2D relates · 3D separates hidden structure",0.55,smooth);
view3(multivariate,"fit",1.60,smooth,1.58);
}
par { pulse(cloud,0.90); pulse(address,0.90); roll3(3,1.50,smooth); }
wait(1.90);
}
}
textbook-geometry-dimension-reduction
The reverse journey: a sphere reveals a great-circle section, then that circle collapses to its diameter. A 3D→2D→1D geometry lesson that makes section and projection relationships explicit.
// ============================================================================
// textbook-geometry-dimension-reduction.manic
// Sphere → central cross-section → circle → diameter (3D → 2D → 1D).
// Geo kit: point, circle2, linecircle, segment, midpoint.
// ============================================================================
title("Dimension Reduction — From Sphere to Diameter");
canvas("9:16");
template("blueprint");
watermark(manicMark,(220,175),"Made With Manic");
creator(me,"@anish2good name=Manic_Geometry tagline=Textbooks_in_motion yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social safe=reels");
socials(me);
text(kicker,(540,164),"GEOMETRY BY DIMENSION"); size(kicker,21); bold(kicker); color(kicker,cyan); hidden(kicker);
text(headline,(540,228),"A sphere hides a circle — and a line"); size(headline,36); bold(headline); hidden(headline);
text(phase,(540,300),"3D · BEGIN WITH A SPHERE"); size(phase,23); bold(phase); color(phase,gold); hidden(phase);
text(narration,(540,1328),"A sphere extends in three independent directions."); size(narration,26); bold(narration); wrap(narration,850); hidden(narration);
text(principle,(540,1392),"Its radius is 3 units in every direction."); size(principle,20); color(principle,dim); hidden(principle);
equation(formula,(540,1488),`x^2+y^2+z^2=\textcolor{gold}{3^2}`,43); hidden(formula);
text(takeaway,(540,1572),"FIX A COORDINATE · LOSE A DIMENSION · KEEP THE MEASURE"); size(takeaway,18); bold(takeaway); color(takeaway,gold); hidden(takeaway);
// Settled isometric composition of the complete sphere inside the Creator
// media region. Starting here prevents an opening camera correction.
camera3((40.05,-39.35,23.41),(3.15,-2.45,-6.79),40);
sphere3(solid,(0,0,0),3); color(solid,blue); hidden(solid); tag(solid,spatial);
finish3(solid,"material=glass shading=smooth mesh=0.25 depth=0.36 shadow=0.16");
// A thin xz-plane begins outside the sphere and travels to y=0.
cube3(slicePlane,(0,-5,0),(7,0.08,7)); color(slicePlane,magenta); hidden(slicePlane); tag(slicePlane,spatial);
finish3(slicePlane,"material=glass shading=flat mesh=0.10 depth=0.18 shadow=0.06");
curve3(greatCircle,"3*cos(t)","0","3*sin(t)",(0,6.2832)); color(greatCircle,magenta); thick(greatCircle,0.065); untraced(greatCircle); hidden(greatCircle); tag(greatCircle,spatial);
// Screen-space geo construction aligned with the settled front projection.
let gx=518;
let gy=890;
let gr=145;
point(O,(gx,gy),"O"); hidden(O); hidden(O.label);
point(A,(gx+gr,gy),"R"); hidden(A); hidden(A.label);
circle2(section,O,A); color(section,magenta); stroke(section,4); untraced(section);
segment(radius2d,O,A); color(radius2d,gold); stroke(radius2d,4); untraced(radius2d);
// Two hidden points define a horizontal secant. The geo kit computes its exact
// circle intersections `ends0` and `ends1`, then derives their midpoint M.
point(lineLeft,(gx-gr-55,gy)); hidden(lineLeft);
point(lineRight,(gx+gr+55,gy)); hidden(lineRight);
linecircle(ends,lineLeft,lineRight,O,A);
hidden(ends0); hidden(ends1);
label(ends0,"A",(-18,-22)); hidden(ends0.label);
label(ends1,"B",(18,-22)); hidden(ends1.label);
segment(diameter,ends0,ends1); color(diameter,cyan); stroke(diameter,6); untraced(diameter);
midpoint(M,ends0,ends1); color(M,lime); hidden(M);
label(M,"M",(0,-24)); hidden(M.label);
step("the full three dimensional object") {
seq {
par { show(kicker,0.35); show(headline,0.55); show(phase,0.40); show(narration,0.45); show(principle,0.40); show(formula,0.55); }
show(solid,0.85);
par { pulse(solid,0.85); roll3(4,1.45,smooth); }
wait(0.70);
}
}
step("fix one coordinate with a plane") {
seq {
par {
say(phase,"3D → 2D · TAKE A CENTRAL SLICE",0.45,smooth);
say(narration,"Move a plane to the centre and fix y=0.",0.55,smooth);
say(principle,"Only points shared by the sphere and plane survive.",0.50,smooth);
rewrite(formula,`y=0\quad\Longrightarrow\quad x^2+z^2=\textcolor{gold}{3^2}`,1.05,smooth);
show(slicePlane,0.35);
}
par {
shift3(slicePlane,(0,5,0),2.10,smooth);
show(greatCircle,0.25); draw(greatCircle,2.10,smooth);
}
par { pulse(greatCircle,0.75); to(solid,opacity,0.04,0.65,smooth); fade(slicePlane,0.65); }
// A generous margin makes the spatial circle settle at the same on-screen
// radius as the following geo-kit circle, avoiding a dimensional size jump.
view3(greatCircle,"front",1.65,smooth,3.10);
wait(0.60);
}
}
step("the cross section becomes a geo circle") {
seq {
par {
say(phase,"2D · THE GREAT CIRCLE",0.45,smooth);
say(narration,"The surviving cross-section is a circle of radius 3.",0.55,smooth);
say(principle,"The geo kit binds the circle to centre O and radius point R.",0.50,smooth);
rewrite(formula,`x^2+z^2=9\quad\Longleftrightarrow\quad r=\textcolor{gold}{3}`,1.00,smooth);
fade(greatCircle,0.85);
draw(section,1.30,smooth);
}
par { show(O,0.25); show(O.label,0.25); show(A,0.25); show(A.label,0.25); draw(radius2d,0.85,smooth); }
par { pulse(section,0.75); pulse(radius2d,0.75); }
wait(0.70);
}
}
step("a line finds the diameter") {
seq {
par {
say(phase,"2D → 1D · INTERSECT WITH A LINE",0.45,smooth);
say(narration,"A line through the centre meets the circle at exactly two points.",0.55,smooth);
say(principle,"The constructed segment AB is the circle's widest chord.",0.50,smooth);
rewrite(formula,`AB=2r=2(3)`,0.95,smooth);
}
par { show(ends0,0.25); show(ends1,0.25); show(ends0.label,0.25); show(ends1.label,0.25); draw(diameter,1.35,smooth); }
par { fade(radius2d,0.45); fade(A,0.35); fade(A.label,0.35); show(M,0.30); show(M.label,0.30); pulse(M,0.70); }
wait(0.70);
}
}
step("only one measurable direction remains") {
seq {
par {
show(takeaway,0.45);
say(phase,"1D · THE DIAMETER",0.50,smooth);
say(narration,"Remove the surrounding circle. One segment—and one length—remains.",0.55,smooth);
say(principle,"3D sphere → 2D section → 1D diameter",0.55,smooth);
rewrite(formula,`\boxed{AB=\textcolor{gold}{6}\text{ units}}`,1.05,smooth);
fade(section,0.75); fade(O,0.40); fade(O.label,0.40);
}
par { pulse(diameter,0.90); pulse(formula,0.90); }
wait(2.00);
}
}
textbook-watermelon-sections
A paper-style spatial lesson that turns sphere sections into a continuous story: horizontal and vertical great-circle cuts make two halves, then perpendicular cuts separate one quarter from the three-quarter remainder. The section faces are bounded parametric surfaces, so the authored geometry is exact rather than a flat overlay.
// ============================================================================
// textbook-watermelon-sections.manic
// Animated reconstruction of a textbook sketch: horizontal and vertical
// sphere sections, then two perpendicular cuts producing 1/4 + 3/4.
// ============================================================================
title("Watermelon Sections — Slicing a Sphere");
canvas("9:16");
template("paper");
watermark(manicMark,(220,175),"Made With Manic");
creator(me,"@anish2good name=Manic_Geometry tagline=Textbooks_in_motion yt=zarigatongy x=@anish2good web=8gwifi.org/manic accent=cyan secondary=magenta footer=social safe=reels");
socials(me);
text(kicker,(540,164),"SECTIONS OF A SPHERE"); size(kicker,21); bold(kicker); color(kicker,cyan); hidden(kicker);
text(headline,(540,228),"How a watermelon becomes ½, ¼, and ¾"); size(headline,36); bold(headline); hidden(headline);
text(phase,(540,300),"THE TWO GREAT CIRCLES"); size(phase,23); bold(phase); color(phase,gold); hidden(phase);
text(narration,(540,1328),"A central plane cuts a sphere in a great circle."); size(narration,26); bold(narration); wrap(narration,850); hidden(narration);
text(principle,(540,1392),"Horizontal and vertical sections have the same radius."); size(principle,20); color(principle,dim); hidden(principle);
equation(formula,(540,1488),`x^2+y^2+z^2=R^2`,43); hidden(formula);
text(result,(540,1572),"ONE SPHERE · TWO PERPENDICULAR SECTION PLANES"); size(result,18); bold(result); color(result,gold); hidden(result);
// Stable isometric framing in the portrait Creator media region.
camera3((40.05,-39.35,23.41),(3.15,-2.45,-6.79),40);
// Shared textbook guides: the horizontal ring lies in z=0 and the vertical
// ring lies in y=0. Each chapter owns a duplicate so transitions stay simple.
// --------------------------------------------------------------------------
// Chapter 1 — horizontal cut, two hemispheres
// --------------------------------------------------------------------------
sphere3(hGuide,(0,0,0),3); color(hGuide,blue); hidden(hGuide); tag(hGuide,hScene);
finish3(hGuide,"material=glass shading=smooth mesh=0.18 depth=0.28 shadow=0.12");
curve3(hRing,"3*cos(t)","3*sin(t)","0",(0,6.2832)); color(hRing,cyan); thick(hRing,0.055); untraced(hRing); hidden(hRing); tag(hRing,hScene);
curve3(hVerticalRing,"3*cos(t)","0","3*sin(t)",(0,6.2832)); color(hVerticalRing,dim); thick(hVerticalRing,0.035); untraced(hVerticalRing); hidden(hVerticalRing); tag(hVerticalRing,hScene);
cube3(hPlane,(0,0,4.5),(7,7,0.08)); color(hPlane,cyan); hidden(hPlane); tag(hPlane,hScene);
finish3(hPlane,"material=glass shading=flat mesh=0.08 depth=0.14 shadow=0.04");
param3(hTopShell,
"3*sin(u)*cos(v)","3*sin(u)*sin(v)","3*cos(u)",
(0,1.5708),(0,6.2832),34);
color(hTopShell,lime); hidden(hTopShell); tag(hTopShell,hTop); tag(hTopShell,hScene);
finish3(hTopShell,"material=glass shading=smooth mesh=0.12 depth=0.24 shadow=0.10");
param3(hBottomShell,
"3*sin(u)*cos(v)","3*sin(u)*sin(v)","3*cos(u)",
(1.5708,3.1416),(0,6.2832),34);
color(hBottomShell,lime); hidden(hBottomShell); tag(hBottomShell,hBottom); tag(hBottomShell,hScene);
finish3(hBottomShell,"material=glass shading=smooth mesh=0.12 depth=0.24 shadow=0.10");
param3(hTopFace,"3*u*cos(v)","3*u*sin(v)","0",(0,1),(0,6.2832),30);
color(hTopFace,magenta); hidden(hTopFace); tag(hTopFace,hTop); tag(hTopFace,hScene);
finish3(hTopFace,"material=glass shading=flat mesh=0.30 depth=0.22 shadow=0.08");
param3(hBottomFace,"3*u*cos(v)","3*u*sin(v)","0",(0,1),(0,6.2832),30);
color(hBottomFace,magenta); hidden(hBottomFace); tag(hBottomFace,hBottom); tag(hBottomFace,hScene);
finish3(hBottomFace,"material=glass shading=flat mesh=0.30 depth=0.22 shadow=0.08");
// --------------------------------------------------------------------------
// Chapter 2 — vertical cut, front and back hemispheres
// --------------------------------------------------------------------------
sphere3(vGuide,(0,0,0),3); color(vGuide,blue); hidden(vGuide); tag(vGuide,vScene);
finish3(vGuide,"material=glass shading=smooth mesh=0.18 depth=0.28 shadow=0.12");
curve3(vRing,"3*cos(t)","0","3*sin(t)",(0,6.2832)); color(vRing,lime); thick(vRing,0.055); untraced(vRing); hidden(vRing); tag(vRing,vScene);
curve3(vHorizontalRing,"3*cos(t)","3*sin(t)","0",(0,6.2832)); color(vHorizontalRing,dim); thick(vHorizontalRing,0.035); untraced(vHorizontalRing); hidden(vHorizontalRing); tag(vHorizontalRing,vScene);
cube3(vPlane,(0,-4.5,0),(7,0.08,7)); color(vPlane,lime); hidden(vPlane); tag(vPlane,vScene);
finish3(vPlane,"material=glass shading=flat mesh=0.08 depth=0.14 shadow=0.04");
param3(vFrontShell,
"3*sin(u)*cos(v)","3*sin(u)*sin(v)","3*cos(u)",
(0,3.1416),(3.1416,6.2832),34);
color(vFrontShell,lime); hidden(vFrontShell); tag(vFrontShell,vFront); tag(vFrontShell,vScene);
finish3(vFrontShell,"material=glass shading=smooth mesh=0.12 depth=0.24 shadow=0.10");
param3(vBackShell,
"3*sin(u)*cos(v)","3*sin(u)*sin(v)","3*cos(u)",
(0,3.1416),(0,3.1416),34);
color(vBackShell,lime); hidden(vBackShell); tag(vBackShell,vBack); tag(vBackShell,vScene);
finish3(vBackShell,"material=glass shading=smooth mesh=0.12 depth=0.24 shadow=0.10");
param3(vFrontFace,"3*u*cos(v)","0","3*u*sin(v)",(0,1),(0,6.2832),30);
color(vFrontFace,cyan); hidden(vFrontFace); tag(vFrontFace,vFront); tag(vFrontFace,vScene);
finish3(vFrontFace,"material=glass shading=flat mesh=0.30 depth=0.22 shadow=0.08");
param3(vBackFace,"3*u*cos(v)","0","3*u*sin(v)",(0,1),(0,6.2832),30);
color(vBackFace,cyan); hidden(vBackFace); tag(vBackFace,vBack); tag(vBackFace,vScene);
finish3(vBackFace,"material=glass shading=flat mesh=0.30 depth=0.22 shadow=0.08");
// --------------------------------------------------------------------------
// Chapter 3 — perpendicular cuts, quarter removed and three quarters remain
// --------------------------------------------------------------------------
sphere3(qGuide,(0,0,0),3); color(qGuide,blue); hidden(qGuide); tag(qGuide,qScene);
finish3(qGuide,"material=glass shading=smooth mesh=0.18 depth=0.28 shadow=0.12");
curve3(qHRing,"3*cos(t)","3*sin(t)","0",(0,6.2832)); color(qHRing,cyan); thick(qHRing,0.045); untraced(qHRing); hidden(qHRing); tag(qHRing,qScene);
curve3(qVRing,"3*cos(t)","0","3*sin(t)",(0,6.2832)); color(qVRing,lime); thick(qVRing,0.045); untraced(qVRing); hidden(qVRing); tag(qVRing,qScene);
cube3(qHPlane,(0,0,4.5),(7,7,0.07)); color(qHPlane,cyan); hidden(qHPlane); tag(qHPlane,qScene);
cube3(qVPlane,(0,-4.5,0),(7,0.07,7)); color(qVPlane,lime); hidden(qVPlane); tag(qVPlane,qScene);
finish3(qHPlane,"material=glass shading=flat mesh=0.06 depth=0.12 shadow=0.03");
finish3(qVPlane,"material=glass shading=flat mesh=0.06 depth=0.12 shadow=0.03");
// Removed top-front quarter: one spherical patch plus two semicircular faces.
param3(qShell,
"3*sin(u)*cos(v)","3*sin(u)*sin(v)","3*cos(u)",
(0,1.5708),(3.1416,6.2832),32);
color(qShell,lime); hidden(qShell); tag(qShell,quarterPiece); tag(qShell,qScene);
finish3(qShell,"material=glass shading=smooth mesh=0.14 depth=0.26 shadow=0.12");
param3(qHFace,"3*u*cos(v)","3*u*sin(v)","0",(0,1),(3.1416,6.2832),28);
color(qHFace,gold); hidden(qHFace); tag(qHFace,quarterPiece); tag(qHFace,qScene);
finish3(qHFace,"material=glass shading=flat mesh=0.28 depth=0.22 shadow=0.08");
param3(qVFace,"3*u*cos(v)","0","3*u*sin(v)",(0,1),(0,3.1416),28);
color(qVFace,gold); hidden(qVFace); tag(qVFace,quarterPiece); tag(qVFace,qScene);
finish3(qVFace,"material=glass shading=flat mesh=0.28 depth=0.22 shadow=0.08");
// Remaining 3/4: top-back quarter plus the complete bottom hemisphere.
param3(rTopBack,
"3*sin(u)*cos(v)","3*sin(u)*sin(v)","3*cos(u)",
(0,1.5708),(0,3.1416),32);
color(rTopBack,lime); hidden(rTopBack); tag(rTopBack,remainder); tag(rTopBack,qScene);
finish3(rTopBack,"material=glass shading=smooth mesh=0.14 depth=0.26 shadow=0.12");
param3(rBottom,
"3*sin(u)*cos(v)","3*sin(u)*sin(v)","3*cos(u)",
(1.5708,3.1416),(0,6.2832),34);
color(rBottom,lime); hidden(rBottom); tag(rBottom,remainder); tag(rBottom,qScene);
finish3(rBottom,"material=glass shading=smooth mesh=0.14 depth=0.26 shadow=0.12");
param3(rHFace,"3*u*cos(v)","3*u*sin(v)","0",(0,1),(3.1416,6.2832),28);
color(rHFace,gold); hidden(rHFace); tag(rHFace,remainder); tag(rHFace,qScene);
finish3(rHFace,"material=glass shading=flat mesh=0.28 depth=0.22 shadow=0.08");
param3(rVFace,"3*u*cos(v)","0","3*u*sin(v)",(0,1),(0,3.1416),28);
color(rVFace,gold); hidden(rVFace); tag(rVFace,remainder); tag(rVFace,qScene);
finish3(rVFace,"material=glass shading=flat mesh=0.28 depth=0.22 shadow=0.08");
step("read the two central sections") {
seq {
par { show(kicker,0.35); show(headline,0.55); show(phase,0.40); show(narration,0.45); show(principle,0.40); show(formula,0.55); show(result,0.45); }
to(hGuide,opacity,0.28,0.80,smooth);
par { show(hRing,0.25); draw(hRing,1.20,smooth); show(hVerticalRing,0.25); draw(hVerticalRing,1.20,smooth); }
par { pulse(hRing,0.70); pulse(hVerticalRing,0.70); roll3(3,1.30,smooth); }
wait(0.65);
}
}
step("horizontal cut makes two halves") {
seq {
par {
say(phase,"HORIZONTAL SECTION",0.45,smooth);
say(narration,"Slide a horizontal plane through the centre: z=0.",0.55,smooth);
say(principle,"The intersection is a horizontal great circle.",0.50,smooth);
rewrite(formula,`z=0\quad\Longrightarrow\quad x^2+y^2=R^2`,1.00,smooth);
say(result,"CUT → TWO ½-WATERMELONS",0.45,smooth);
show(hPlane,0.30);
}
par { shift3(hPlane,(0,0,-4.5),2.00,smooth); pulse(hRing,0.75); }
par {
fade(hGuide,0.55); fade(hPlane,0.45); fade(hRing,0.45); fade(hVerticalRing,0.45);
show(hTopShell,0.40); show(hBottomShell,0.40);
to(hTopFace,opacity,0.74,0.50,smooth); to(hBottomFace,opacity,0.74,0.50,smooth);
}
par { shift3(hTop,(-3.8,0,0.7),1.85,smooth); shift3(hBottom,(1.5,0,-0.2),1.85,smooth); }
par { pulse(hTopFace,0.80); pulse(hBottomFace,0.80); }
wait(0.75);
}
}
step("vertical cut makes two halves") {
seq {
par {
fade(hScene,0.65);
say(phase,"VERTICAL SECTION",0.45,smooth);
say(narration,"Turn the cutting plane upright and pass it through y=0.",0.55,smooth);
say(principle,"Its section is the same-size circle, seen vertically.",0.50,smooth);
rewrite(formula,`y=0\quad\Longrightarrow\quad x^2+z^2=R^2`,1.00,smooth);
say(result,"ANOTHER CUT → TWO ½-WATERMELONS",0.45,smooth);
}
par { to(vGuide,opacity,0.26,0.70,smooth); show(vHorizontalRing,0.25); draw(vHorizontalRing,0.90,smooth); show(vRing,0.25); draw(vRing,1.10,smooth); }
par { show(vPlane,0.30); shift3(vPlane,(0,4.5,0),2.00,smooth); pulse(vRing,0.75); }
par {
fade(vGuide,0.55); fade(vPlane,0.45); fade(vRing,0.45); fade(vHorizontalRing,0.45);
show(vFrontShell,0.40); show(vBackShell,0.40);
to(vFrontFace,opacity,0.74,0.50,smooth); to(vBackFace,opacity,0.74,0.50,smooth);
}
par { shift3(vFront,(-3.8,0,0.4),1.85,smooth); shift3(vBack,(1.5,0,-0.1),1.85,smooth); }
wait(0.75);
}
}
step("perpendicular cuts remove one quarter") {
seq {
par {
fade(vScene,0.65);
say(phase,"TWO PERPENDICULAR CUTS",0.45,smooth);
say(narration,"Now combine the horizontal and vertical central planes.",0.55,smooth);
say(principle,"Each cut halves one independent direction.",0.50,smooth);
rewrite(formula,`\frac12\times\frac12=\textcolor{gold}{\frac14}`,1.00,smooth);
say(result,"¼ CUT OUT · ¾ REMAINS",0.45,smooth);
}
par { to(qGuide,opacity,0.24,0.65,smooth); show(qHRing,0.25); draw(qHRing,0.90,smooth); show(qVRing,0.25); draw(qVRing,0.90,smooth); }
seq {
par { show(qHPlane,0.25); shift3(qHPlane,(0,0,-4.5),1.35,smooth); }
par { show(qVPlane,0.25); shift3(qVPlane,(0,4.5,0),1.35,smooth); }
}
par {
fade(qGuide,0.50); fade(qHPlane,0.40); fade(qVPlane,0.40); fade(qHRing,0.40); fade(qVRing,0.40);
show(qShell,0.35); to(qHFace,opacity,0.78,0.45,smooth); to(qVFace,opacity,0.78,0.45,smooth);
show(rTopBack,0.35); show(rBottom,0.35); to(rHFace,opacity,0.70,0.45,smooth); to(rVFace,opacity,0.70,0.45,smooth);
}
par { shift3(quarterPiece,(-3.8,0,0.8),1.90,smooth); shift3(remainder,(1.4,0,-0.15),1.90,smooth); }
wait(0.70);
}
}
step("compare the pieces") {
seq {
par {
say(phase,"FRACTIONS ARE GEOMETRY",0.50,smooth);
say(narration,"The removed corner is one quarter; the connected remainder is three quarters.",0.55,smooth);
say(principle,"Horizontal section · vertical section · quarter volume",0.55,smooth);
rewrite(formula,`1-\frac14=\textcolor{gold}{\frac34}`,1.00,smooth);
show(result,0.25); say(result,"¼ PIECE + ¾ WATERMELON = 1 WHOLE",0.50,smooth);
}
par { pulse(quarterPiece,0.90); pulse(remainder,0.90); roll3(3,1.50,smooth); }
wait(2.00);
}
}
three_d
Cubes, spheres, arrows, a curve, a surface and solids together — the 3D basics on one stage.
title("3D coordinate space");
canvas(1280, 720);
template("terminal");
camera3((8, -10, 6), (0, 0, 1), 45);
grid3(floor, (0, 0, 0), 5, 1);
color(floor, dim);
axes3(world, (0, 0, 0), 4);
cube3(box, (0, 0, 1), (2, 2, 2));
color(box, magenta);
sphere3(ball, (-2, 1, 1), 0.7);
color(ball, lime);
arrow3(vector, (0, 0, 0), (2.5, 2, 3));
// a parametric helix (curve3), drawn on as a shaded tube
curve3(spiral, "cos(t)*2.6", "sin(t)*2.6", "t*0.32", (0, 12));
color(spiral, gold);
thick(spiral, 0.08);
// a height-field surface z = f(x,y) (surface3), filled + flat-shaded
surface3(wave, "0.6*sin(x)*cos(y)", (-3.5, 3.5), (-3.5, 3.5), 24);
color(wave, magenta);
opacity(wave, 0.5);
// filled, flat-shaded solids (prism3 / pyramid3)
prism3(hex, (-3.6, -2.4, 1.2), 6, 0.9, 2.2);
pyramid3(cone, (3.6, -2.4, 1.4), 22, 1.0, 2.6);
// a 2D label glued to the moving sphere — tracks it as the camera orbits
text(balltag, (0, 0), "ball");
color(balltag, gold);
pin3(balltag, ball);
par {
rotate3(box, (0, 0, 360), 4, linear);
orbit3(70, 28, 11, 4, smooth);
move3(ball, (2, -1, 2), 4, inout);
}
solids3
Filled, shaded solids: a prism, a cone, and a lathed vase.
title("3D solids");
canvas(1280, 720);
template("terminal");
camera3((9, -11, 7), (0, 0, 1), 45);
grid3(floor, (0, 0, 0), 6, 1);
color(floor, dim);
axes3(world, (0, 0, 0), 3);
// hexagonal prism
prism3(hex, (-4, 0, 1.2), 6, 1.1, 2.4);
color(hex, cyan);
// a cone (a pyramid with many sides)
pyramid3(cone, (0, 0, 1.4), 28, 1.2, 2.8);
color(cone, magenta);
// a lathed vase: radius profile r(t) swept around the vertical axis
revolve3(vase, (4, 0, 1.5), "0.7 + 0.45*sin(t*2.4)", (0, 3), 32);
color(vase, gold);
orbit3(60, 24, 17, 6, smooth);
param3
Parametric surfaces a height field can’t make — a torus, a sphere, and a Möbius strip.
// param3 — general parametric surfaces x(u,v), y(u,v), z(u,v). Unlike surface3
// (a height field z=f(x,y)) these can wrap and close, so a torus and a Möbius
// strip are just three formulas each.
//
// manic examples/param3.manic
// manic examples/param3.manic --record out --fps 60
title("parametric surfaces");
canvas("16:9");
template("terminal");
camera3((11, -13, 8.5), (0, 0, 1.4), 42);
grid3(floor, (0, 0, 0), 7, 1);
color(floor, dim);
// torus (left)
param3(torus,
"0 - 4.8 + (2 + 0.7*cos(v))*cos(u)",
"(2 + 0.7*cos(v))*sin(u)",
"1.5 + 0.7*sin(v)",
(0, 6.283), (0, 6.283), 40);
color(torus, cyan);
// parametric sphere (centre)
param3(ball,
"1.3*sin(v)*cos(u)",
"1.3*sin(v)*sin(u)",
"1.6 + 1.3*cos(v)",
(0, 6.283), (0, 3.1416), 28);
color(ball, magenta);
// Möbius strip (right)
param3(mobius,
"4.8 + (1.7 + v*cos(u/2))*cos(u)",
"(1.7 + v*cos(u/2))*sin(u)",
"1.6 + v*sin(u/2)",
(0, 6.283), (0 - 0.6, 0.6), 60);
color(mobius, gold);
show(floor, 0.3);
par { show(torus, 0.7); show(ball, 0.7); show(mobius, 0.7); }
orbit3(60, 24, 15, 5.0, smooth);
wait(0.4);
extrude3
Lifting flat shapes into solids, including a boolean cut-out (a plate with a hole) and an L-beam.
// Extrude & CSG — turn 2D shapes into 3D solids. `extrude3` sweeps any 2D
// fillable shape (or a boolean region) straight up; extruding a union/
// difference/intersect gives constructive-solid-geometry solids. The 2D
// source shapes are auto-hidden — they're just the cross-section recipe.
//
// manic examples/extrude3.manic
// manic examples/extrude3.manic --record out --fps 60
title("Extrude & CSG");
canvas(1280, 720);
template("terminal");
camera3((9, -11, 7), (0, 0, 0.8), 45);
grid3(floor, (0, 0, 0), 6, 1);
color(floor, dim);
axes3(world, (0, 0, 0), 3);
// CSG: a square plate MINUS a circular hole → an extruded plate-with-a-hole
rect(plate, (0, 0), 3, 3);
circle(hole, (0, 0), 0.9);
difference(cut, plate, hole);
extrude3(block, cut, 1.0, (-3.5, 0, 0.6));
color(block, cyan);
// arbitrary concave polygon: a union of two rects → an L-beam, extruded
rect(la, (0, 0), 2.4, 0.8);
rect(lb, (-0.8, 0.8), 0.8, 2.4);
union(lshape, la, lb);
extrude3(ell, lshape, 0.9, (3.5, 0, 0.55));
color(ell, magenta);
// a plain shape extrudes just as happily (a hexagon via a sector sweep)
sector(hexface, (0, 0), 1.2, 0, 360);
extrude3(disc, hexface, 0.5, (0, 3.5, 0.3));
color(disc, gold);
orbit3(60, 24, 17, 8, smooth);
morph3
Morphing across families — a cube into a sphere, a saddle into a bowl, a helix into a ring.
// 3D morphing (morph3) — set a 3D entity up to become another shape, then
// blend with `to(id, morph, 1, dur)`. Works across three families:
// • solids — a cube becomes a sphere (reparameterised spherically)
// • surfaces — a rippling saddle settles into a bowl (filled + shaded)
// • curves — a helix unwinds into a flat ring
//
// manic examples/morph3.manic
// manic examples/morph3.manic --record out --fps 60
title("3D morph");
canvas("16:9");
template("terminal");
camera3((11, -13, 8), (0, 0, 0.8), 42);
grid3(floor, (0, 0, 0), 7, 1);
color(floor, dim);
// solid: cube -> sphere
cube3(box, (-5, 0, 1.4), (2.4, 2.4, 2.4));
color(box, cyan);
sphere3(ball, (0, 0, 0), 1.5);
hidden(ball);
morph3(box, ball);
// surface: rippling saddle -> smooth bowl
surface3(saddle, "0.8*sin(x*1.3)*cos(y*1.3)", (-2.3, 2.3), (-2.3, 2.3), 24);
color(saddle, gold);
surface3(bowl, "0.16*(x*x + y*y) - 1", (-2.3, 2.3), (-2.3, 2.3), 24);
hidden(bowl);
morph3(saddle, bowl);
// curve: helix -> flat ring
curve3(helix, "5 + 1.5*cos(t)", "1.5*sin(t)", "0.2*t", (0, 18));
color(helix, magenta);
thick(helix, 0.06);
curve3(ring, "5 + 1.5*cos(t)", "1.5*sin(t)", "1.8", (0, 6.283));
hidden(ring);
morph3(helix, ring);
show(floor, 0.3);
par { show(box, 0.5); show(saddle, 0.5); show(helix, 0.5); }
wait(0.7);
par {
to(box, morph, 1, 3.0, smooth);
to(saddle, morph, 1, 3.0, smooth);
to(helix, morph, 1, 3.0, smooth);
orbit3(48, 25, 21, 3.0, smooth);
}
wait(0.8);
linear-algebra-3d — the essence, in 3D
The 3D companion to the linear-algebra lesson: one matrix
[[1,0,0],[0,3,1],[0,1,3]] (det 8; eigenvalues 1, 2, 4) seen two ways on an
orbiting stage — first as a transformation (the unit cube → a parallelepiped
whose volume is the determinant), then through its eigenvectors (the invariant
axes that only stretch). Start here for 3D.
// ============================================================================
// linear-algebra-3d.manic — Linear algebra in 3D (a lesson)
// ----------------------------------------------------------------------------
// The 3-D companion to linear-algebra.manic. One matrix, two lenses, one
// orbiting stage:
// 1. a 3x3 matrix TRANSFORMS space — the unit cube becomes a parallelepiped
// whose VOLUME is the determinant (linmap3)
// 2. its EIGENVECTORS are the invariant axes that only stretch (eigen3)
//
// Both use the same A = [[1,0,0],[0,3,1],[0,1,3]] (det = 8; eigenvalues 1,2,4),
// so the two views describe the very same matrix.
// ============================================================================
title("Linear algebra in 3D");
canvas("16:9");
camera3((12, 9.5, 10), (0.4, 1.6, 1.6), 40);
grid3(floor, (0, 0, 0), 4, 1); color(floor, dim); opacity(floor, 0.3);
// A = [[1,0,0],[0,3,1],[0,1,3]] — det 8, eigenvalues 1, 2, 4
linmap3(lm, (0, 0, 0), 1, 0, 0, 0, 3, 1, 0, 1, 3);
eigen3(ev, (0, 0, 0), 1, 0, 0, 0, 3, 1, 0, 1, 3);
// the eigen-axes wait until chapter 2
hidden(ev.axis0); hidden(ev.axis1); hidden(ev.axis2);
hidden(ev.l0); hidden(ev.l1); hidden(ev.l2);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
// ---- 1. the matrix as a transformation of space ----
section("A matrix in 3D");
say(cap, "a 3x3 matrix sends the unit cube to a parallelepiped — its columns", 0.5);
orbit3(50, 26, 13, 4, smooth);
say(cap, "the volume it encloses is the determinant: det = 8, so space grew 8x", 0.5);
orbit3(116, 30, 13, 4, smooth);
// clear the transformation view
fade(lm, 0.6); fade(lm.ref, 0.6);
fade(lm.i, 0.6); fade(lm.j, 0.6); fade(lm.k, 0.6);
fade(lm.li, 0.6); fade(lm.lj, 0.6); fade(lm.lk, 0.6); fade(lm.val, 0.6);
wait(0.3);
// ---- 2. the same matrix, through its eigenvectors ----
say(cap, "", 0.2);
section("Eigenvectors");
say(cap, "three special directions only STRETCH when A acts — the eigenvectors", 0.5);
show(ev.axis0, 0.5); show(ev.axis1, 0.5); show(ev.axis2, 0.5);
show(ev.l0, 0.4); show(ev.l1, 0.4); show(ev.l2, 0.4);
orbit3(58, 24, 7, 4, smooth);
say(cap, "their stretch factors are the eigenvalues: 1, 2 and 4", 0.5);
orbit3(122, 32, 6.5, 4.5, smooth);
wait(0.5);
linear-map3
Linear algebra in 3D: a 3×3 matrix deforms the unit cube into a parallelepiped,
with basis arrows i/j/k landing on the matrix’s columns and the enclosed volume
labelled as the determinant (linmap3). The 3D echo of linear-map.
// ============================================================================
// linear-map3.manic — a 3x3 matrix, in 3D (the 3-D echo of linear-map)
// ----------------------------------------------------------------------------
// `linmap3(id, (cx,cy,cz), a,b,c,d,e,f,g,h,i, [color])` applies the 3x3 matrix
// [[a,b,c],[d,e,f],[g,h,i]] to space: the unit cube (faint wireframe) becomes a
// parallelepiped, with basis arrows i/j/k landing on the matrix's COLUMNS. The
// volume it encloses IS the determinant (labelled) — it flips colour when the
// determinant is negative (orientation reversed) and collapses flat at det = 0.
//
// TO ADAPT: change the nine numbers. Try a shear (1,0.6,0, 0,1,0, 0,0,1) → det 1
// (volume unchanged), or swap two columns to make det negative (a reflection).
// ============================================================================
title("A 3x3 matrix, in 3D");
canvas("16:9");
camera3((7, 5.5, 6), (1, 1, 1), 40);
grid3(floor, (0, 0, 0), 4, 1); color(floor, dim); opacity(floor, 0.4);
// M = [[1.5,0.5,0],[0,1.5,0.5],[0.5,0,1.5]] — a skew + stretch, det = 3.5
linmap3(lm, (0, 0, 0), 1.5, 0.5, 0, 0, 1.5, 0.5, 0.5, 0, 1.5);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "a 3x3 matrix sends the unit cube to a parallelepiped", 0.5);
orbit3(58, 26, 10, 4, smooth);
say(cap, "i, j, k land on its columns — where the three axes are sent", 0.5);
orbit3(116, 32, 10, 4, smooth);
say(cap, "the volume enclosed IS the determinant: det = 3.5, so space grew 3.5x", 0.5);
orbit3(60, 20, 9, 4.5, smooth);
wait(0.5);
eigen3
The real eigenvectors of a 3×3 matrix, in 3D: the invariant lines through the
origin that only stretch (by λ) when the matrix acts (eigen3). The 3D echo of
eigen. A symmetric matrix gives three perpendicular real eigen-axes; a rotation
leaves one real axis and two complex eigenvalues.
// ============================================================================
// eigen3.manic — eigenvectors of a 3x3 matrix (the 3-D echo of eigen)
// ----------------------------------------------------------------------------
// `eigen3(id, (cx,cy,cz), a,b,c,d,e,f,g,h,i, [color])` draws the real
// EIGENVECTORS of the 3x3 [[a,b,c],[d,e,f],[g,h,i]] as invariant lines through
// the origin — the directions a vector on them only STRETCHES (by λ), never
// turns. A real 3x3 always has at least one real eigenvector; a rotation leaves
// two complex eigenvalues, which are noted.
//
// TO ADAPT: change the nine numbers. A symmetric matrix (as here) has three
// perpendicular real eigen-axes; a rotation like (0,-1,0, 1,0,0, 0,0,1) shows
// one real axis (the axis of rotation) + a "2 complex" note.
// ============================================================================
title("Eigenvectors of a 3x3 matrix");
canvas("16:9");
camera3((7, 5.5, 6), (0, 0, 0), 42);
grid3(floor, (0, 0, 0), 3, 1); color(floor, dim); opacity(floor, 0.35);
// symmetric M = [[1,0,0],[0,3,1],[0,1,3]] — three real eigenvalues: 1, 2, 4
eigen3(ev, (0, 0, 0), 1, 0, 0, 0, 3, 1, 0, 1, 3);
text(cap, (cx, h - 46), ""); size(cap, 22); color(cap, dim); display(cap);
say(cap, "when the matrix acts, most directions turn — but a few only stretch", 0.5);
orbit3(52, 24, 10, 4, smooth);
say(cap, "those invariant lines are the eigenvectors; the stretch factor is lambda", 0.5);
orbit3(122, 34, 10, 4, smooth);
say(cap, "this symmetric 3x3 has three real eigenvalues: 1, 2 and 4", 0.5);
orbit3(58, 22, 9, 4.5, smooth);
wait(0.5);
matrix3
A 3×3×3 block of cubes, with a shear matrix M and its inverse M⁻¹ applied and undone.
// A 3 × 3 × 3 block of 27 cubes, then a matrix operation: a 3×3 matrix is a
// linear map of space. We apply a shear M to every cell, then its inverse
// M^-1 — which sends the block back exactly, because M^-1 M = I.
//
// manic examples/matrix3.manic
// manic examples/matrix3.manic --record out --fps 60
title("3 × 3 × 3 · M and M^-1");
canvas("16:9");
template("terminal");
camera3((7.5, -9, 6), (2.4, 0, 2.1), 42);
grid3(floor, (0, 0, 0), 6, 1);
color(floor, dim);
let s = 1.3; // cell spacing (cubes are 1 wide → 0.3 gaps)
let zc = 2.1; // height of the block's centre
let a = 0.7; // shear amount: M sends x → x + a·z
text(cap, (cx, h - 42), "");
display(cap); color(cap, fg); size(cap, 21); hidden(cap);
// --- 27 cubes, three x-slices coloured cyan / magenta / lime ---
for j in 0..3 { for k in 0..3 {
cube3(xa{j}{k}, (0 - s, (j - 1)*s, zc + (k - 1)*s), (1, 1, 1));
color(xa{j}{k}, cyan); hidden(xa{j}{k});
} }
for j in 0..3 { for k in 0..3 {
cube3(xb{j}{k}, (0, (j - 1)*s, zc + (k - 1)*s), (1, 1, 1));
color(xb{j}{k}, magenta); hidden(xb{j}{k});
} }
for j in 0..3 { for k in 0..3 {
cube3(xc{j}{k}, (s, (j - 1)*s, zc + (k - 1)*s), (1, 1, 1));
color(xc{j}{k}, lime); hidden(xc{j}{k});
} }
// --- the matrices, as monospace text on the right ---
text(mLabel, (995, 232), "M =");
text(m0, (1035, 274), "[ 1 0 0.7 ]");
text(m1, (1035, 310), "[ 0 1 0 ]");
text(m2, (1035, 346), "[ 0 0 1 ]");
text(iLabel, (995, 446), "M^-1 =");
text(i0, (1035, 488), "[ 1 0 -0.7 ]");
text(i1, (1035, 524), "[ 0 1 0 ]");
text(i2, (1035, 560), "[ 0 0 1 ]");
display(mLabel); display(m0); display(m1); display(m2);
display(iLabel); display(i0); display(i1); display(i2);
color(mLabel, cyan); color(m0, cyan); color(m1, cyan); color(m2, cyan);
color(iLabel, gold); color(i0, gold); color(i1, gold); color(i2, gold);
size(mLabel, 24); size(m0, 22); size(m1, 22); size(m2, 22);
size(iLabel, 24); size(i0, 22); size(i1, 22); size(i2, 22);
hidden(mLabel); hidden(m0); hidden(m1); hidden(m2);
hidden(iLabel); hidden(i0); hidden(i1); hidden(i2);
// ------------------------------ script ------------------------------
show(floor, 0.3);
show(cap, 0.3);
say(cap, "A 3 by 3 by 3 block — 27 cells.", 0.7);
stagger(0.04) {
for j in 0..3 { for k in 0..3 { show(xa{j}{k}, 0.3); } }
for j in 0..3 { for k in 0..3 { show(xb{j}{k}, 0.3); } }
for j in 0..3 { for k in 0..3 { show(xc{j}{k}, 0.3); } }
}
orbit3(38, 22, 13, 1.6, smooth);
say(cap, "A 3×3 matrix M is a linear map of space. This M shears x by height z.", 1.0);
stagger(0.12) { show(mLabel, 0.3); show(m0, 0.3); show(m1, 0.3); show(m2, 0.3); }
// apply M: x' = x + a·(z - zc)
par {
for j in 0..3 { for k in 0..3 {
move3(xa{j}{k}, (0 - s + a*(k - 1)*s, (j - 1)*s, zc + (k - 1)*s), 1.8, smooth);
} }
for j in 0..3 { for k in 0..3 {
move3(xb{j}{k}, (a*(k - 1)*s, (j - 1)*s, zc + (k - 1)*s), 1.8, smooth);
} }
for j in 0..3 { for k in 0..3 {
move3(xc{j}{k}, (s + a*(k - 1)*s, (j - 1)*s, zc + (k - 1)*s), 1.8, smooth);
} }
}
say(cap, "Applying M slants the whole block — the higher a cell, the more it shifts.", 1.0);
stagger(0.12) { show(iLabel, 0.3); show(i0, 0.3); show(i1, 0.3); show(i2, 0.3); }
say(cap, "The inverse M^-1 just negates the shear.", 0.8);
// apply M^-1: sends every cell back to where it started
par {
for j in 0..3 { for k in 0..3 {
move3(xa{j}{k}, (0 - s, (j - 1)*s, zc + (k - 1)*s), 1.8, smooth);
} }
for j in 0..3 { for k in 0..3 {
move3(xb{j}{k}, (0, (j - 1)*s, zc + (k - 1)*s), 1.8, smooth);
} }
for j in 0..3 { for k in 0..3 {
move3(xc{j}{k}, (s, (j - 1)*s, zc + (k - 1)*s), 1.8, smooth);
} }
}
say(cap, "M^-1 sends every cell home: M^-1 M = I, the identity.", 1.0);
orbit3(70, 26, 13, 2.4, smooth);
wait(0.6);
double-integral3
Multivariable calculus: the volume under a surface as a limit of finer and finer columns — a double integral, made solid. The coarse blocks refine until they hug the surface.
title("The double integral — volume as a limit of finer columns");
canvas("16:9");
camera3((9, -11, 9), (0, 0, 0.7), 40);
axes3(world, (0, 0, 0), 3);
color(world, dim);
text(cap, (cx, h - 54), "coarse columns");
size(cap, 22); color(cap, cyan); bold(cap); display(cap); hidden(cap);
// the surface, kept faint so the columns beneath it read
surface3(hill, "1.6*exp(-(x*x + y*y)*0.28)", (-3, 3), (-3, 3), 30);
color(hill, cyan); opacity(hill, 0.22); untraced(hill);
// the volume under it, at three resolutions — each finer sum hugs the surface
volume3(v1, hill, 3);
color(v1, lime); hidden(v1);
volume3(v2, hill, 7);
color(v2, lime); hidden(v2);
volume3(v3, hill, 16);
color(v3, lime); hidden(v3);
// ---- timeline ----
show(world, 0.5);
draw(hill, 1.4);
wait(0.3);
// 3x3: a crude estimate
show(cap, 0.4);
show(v1, 0.7);
wait(1.2);
// 7x7: closer
fade(v1, 0.5);
say(cap, "finer: the sum gets closer");
show(v2, 0.7);
wait(1.2);
// 16x16: the columns fill the true volume under the surface
fade(v2, 0.5);
say(cap, "finer still -> the true volume (a double integral)");
show(v3, 0.8);
orbit3(70, 34, 16, 4.5);
multivariable3
Calculus on a surface: a smooth hill, its tangent plane and the gradient arrow at a chosen point, inspected with an orbiting camera.
title("Calculus on a surface — gradient, tangent plane, volume");
canvas("16:9");
camera3((8, -10, 7), (0, 0, 0.8), 42);
axes3(world, (0, 0, 0), 3);
color(world, dim);
// a smooth hill: z = 1.6 e^(-(x^2 + y^2)/3.6)
surface3(hill, "1.6*exp(-(x*x + y*y)*0.28)", (-3, 3), (-3, 3), 28);
color(hill, cyan); untraced(hill);
// the tangent plane at a point on the slope
tangentplane3(tp, hill, 1.1, -0.7);
color(tp, magenta); hidden(tp);
// the gradient there — an arrow pointing straight up the steepest slope
gradient3(grad, hill, 1.1, -0.7);
color(grad, gold); hidden(grad);
// ---- timeline ----
show(world, 0.6);
draw(hill, 1.8);
wait(0.4);
show(tp, 0.6);
show(grad, 0.6);
orbit3(70, 30, 15, 4.5);
volume3
The volume under a surface represented as solid 3D Riemann-sum columns, turning a double integral into visible geometry.
title("Volume under a surface — a double integral, made solid");
canvas("16:9");
camera3((9, -11, 8), (0, 0, 0.6), 40);
axes3(world, (0, 0, 0), 3);
color(world, dim);
// a smooth bump
surface3(hill, "1.6*exp(-(x*x + y*y)*0.28)", (-3, 3), (-3, 3), 28);
color(hill, cyan); untraced(hill);
// the volume beneath it, as a grid of columns (a 3D Riemann sum)
volume3(vol, hill, 11);
color(vol, lime); hidden(vol);
// ---- timeline ----
show(world, 0.6);
draw(hill, 1.6);
wait(0.4);
show(vol, 1.0);
orbit3(70, 32, 16, 4.5);
heightmap3
The Grid Kit → 3D bridge: heightmap3(land, grid, "z(x,y,h)") lifts a 2-D grid’s per-cell
state into a surface3-style terrain mesh. A seeded Wave Function Collapse settles a map,
then its walls rise into an island terrain a camera orbits — the grid kit stays entirely
3D-unaware.
// heightmap3 — a Grid Kit → 3D bridge. A 2D grid's per-cell state is lifted into a
// surface3-style terrain mesh. The grid kit stays entirely 3D-unaware; heightmap3
// lives on the 3D side and just reads the grid's cells. Here a seeded Wave Function
// Collapse settles a map, then its walls rise into an island terrain a camera orbits.
title("Heightmap3 — A Grid Becomes Terrain");
canvas("16:9");
template("neon");
// A grid-kit map: `collapse` pre-simulates a seeded WFC settle (clustered walls).
grid(mz, (cx, cy), 18, 14, 30);
collapse(mz, "islands", 5);
hidden(mz.cells); hidden(mz.lines); // we only want the 3D terrain, not the flat grid
// Lift it: h = 1 for a filled cell, 0 otherwise. The formula raises the walls and
// blends in organic `fbm` noise for a natural roll — grid value AND noise compose.
camera3((0, -12, 7), (0, 0, 0.7), 32);
heightmap3(land, mz, "h*1.5 + fbm(x*1.1, y*1.1)*0.85 + 0.1", 7);
color(land, cyan);
// A slow turntable so the terrain reads in 3D.
orbit3(70, 34, 13, 4.5, smooth);
orbit3(200, 30, 13, 4.5, smooth);
orbit3(330, 36, 13, 4.5, smooth);
heightmap3-world
The creative payoff: a grid-kit WFC map settles in 2-D, then the very same grid lifts into
a 3-D world as the camera tilts down — one grid, two dimensions. h (the cell value) is a
third formula variable added to the expression engine for exactly this.
// From Flat Map to 3D World — the creative payoff of the Grid→3D bridge. A grid-kit
// Wave Function Collapse settles a map in 2D; then the very same grid is lifted by
// heightmap3 into a 3D terrain that rises as the camera tilts down. One grid, two
// dimensions — the grid kit never learns about 3D.
title("From Flat Map to 3D World");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mk, (w*0.86, h*0.06), "Made With Manic");
// The 2D map (grid kit): a seeded WFC settle.
grid(world, (cx, cy), 20, 16, 30);
collapse(world, "maze", 11);
// The 3D terrain from the SAME grid — near top-down at first, so it reads flat like
// the map, then the tilt reveals its height. Hidden until the lift.
camera3((0, -1.6, 20), (0, 0, 0), 24);
heightmap3(land, world, "h*1.9 + fbm(x*1.2, y*1.2)*0.9 + 0.1", 8);
color(land, cyan);
hidden(land);
text(cap, (cx, h*0.9), "a flat 2D map…");
size(cap, 30*u); color(cap, dim); wrap(cap, w*0.7);
// 1 — the map settles in 2D (Wave Function Collapse).
step("settle") {
par {
say(cap, "a flat 2D map, generated cell by cell…");
run(world, 16, 3.4);
}
}
wait(0.4);
// 2 — the same grid lifts into 3D: fade the flat map, reveal the terrain (still
// near top-down, so it still reads flat — the map has just gained a third axis).
step("lift") {
par {
say(cap, "…is secretly a 3D world");
fade(world.cells, 0.6);
fade(world.lines, 0.6);
show(land, 0.7);
}
}
// 3 — pull back and tilt so the WHOLE world is in frame at once.
step("reveal") {
orbit3(25, 38, 27, 3.8, smooth);
}
wait(0.4);
// 4 — a slow rotation around the full terrain.
step("rotate") {
seq {
say(cap, "one grid — 2D pathfinding AND 3D terrain, bridged by manic");
orbit3(170, 34, 27, 5.2, smooth);
}
}
// 5 — then fly in close and low over the peaks. That's manic.
step("zoom") {
orbit3(235, 15, 10, 4.6, smooth);
}
wait(1.2);
noise-terrain
Procedural generation from a single formula: noise(x,y) and fbm(x,y) (fractal Brownian
motion) are now formula functions beside sin/cos, so surface3(land, "fbm(x*0.9,y*0.9)*2.4")
sculpts an organic fractal landscape the camera tours — no new kit, just two functions the
shared expression evaluator now understands.
// Fractal Terrain — procedural generation from a single formula. `noise(x,y)` and
// `fbm(x,y)` (fractal Brownian motion — five octaves of smooth value noise) are now
// formula functions, right beside sin/cos, so any `surface3`/`heightmap3`/`plot`
// formula can be organic. Here fbm alone sculpts a whole landscape the camera tours.
title("Fractal Terrain — Noise in a Formula");
canvas("16:9");
template("neon");
camera3((0, -13, 6.5), (0, 0, 0.3), 28);
surface3(land, "fbm(x*0.9, y*0.9)*2.4", (-4, 4), (-4, 4), 72);
color(land, cyan);
// A slow turntable so the fractal relief reads in 3D.
orbit3(60, 30, 13, 4.5, smooth);
orbit3(190, 26, 13, 4.5, smooth);
orbit3(320, 32, 13, 4.5, smooth);
Diagrams
Animated diagrams — not another static boxes-and-arrows generator. Declare an architecture (or a flowchart) with auto-positioned nodes and directed connections — geometry is optional, so a diagram auto-fits the canvas and, when it grows dense, scales itself down as one to stay inside the frame (you never touch a coordinate) — then move one persistent request through the graph. A flowchart ranks its nodes top-down and runs: a token walks the process and takes a branch. It also speaks C4 — c4(id, level) with person/system/container/component nodes draws Simon Brown’s model in the conventional outline style, tiers people over internals over externals, auto-splits a dense tier into a balanced grid, and zooms from Context to Container to Component as a request flows through it. Node artwork comes from a string kind (aws:lambda, gcp:bigquery, onprem:redis, k8s:pod — 17 providers, see icon reference & aliases); paths are coloured by relationship. The kit never infers behaviour — the creator authors it with route, flow, and ordinary verbs.
Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).
systems-foundation
Provider-neutral: only native client/gateway/service/cache/database archetypes — reveals cold topology, sends one persistent request forward, and returns the same identity over separately directed paths, with no cloud assets.
title("Systems Foundation — One Request, One Return");
canvas("16:9");
template("mono");
watermark(mark, (160, 54), "Made With Manic");
text(kicker, (cx, 54), "PROVIDER-NEUTRAL SYSTEMS");
text(headline, (cx, 104), "Structure is not behaviour");
text(caption, (cx, h-58), "Cold paths show what is possible. One persistent dot shows what actually happened.");
size(kicker, 18); color(kicker, dim); bold(kicker);
size(headline, 34); bold(headline);
size(caption, 20); color(caption, dim); wrap(caption, w*0.86);
architecture(platform, (cx, cy+15), w*0.88, h*0.58);
node(user, platform, "client", "User");
node(edge, platform, "gateway", "Gateway");
node(api, platform, "service", "API Service");
node(cache, platform, "cache", "Cache");
node(db, platform, "database", "Database");
connect(toEdge, user, edge);
connect(toApi, edge, api);
connect(toCache, api, cache);
// One semantic connection: Manic chooses node-boundary ports and keeps the
// packet continuous across every internal orthogonal segment.
connect(toDb, cache, db, orthogonal, right, top);
connect(dbReturn, db, api, 95);
connect(apiReturn, api, edge, 75);
connect(edgeReturn, edge, user, 55);
message(packet, user, "GET");
hidden(kicker); hidden(headline); hidden(caption);
untraced(platform.connections);
hidden(packet);
step("structure") {
par {
show(kicker, 0.35);
show(headline, 0.45);
show(caption, 0.45);
draw(platform.connections, 1.20, smooth);
}
}
wait(0.45);
step("continuous-round-trip") {
par {
show(packet, 0.20);
say(caption, "One dot moves continuously end to end. Every route hand-off preserves its identity.", 0.35);
seq {
route(packet, toEdge, 0.70, linear);
route(packet, toApi, 0.70, linear);
route(packet, toCache, 0.70, linear);
route(packet, toDb, 0.70, linear);
par {
recolor(packet, magenta, 0.30);
route(packet, dbReturn, 0.80, linear);
}
route(packet, apiReturn, 0.75, linear);
route(packet, edgeReturn, 0.75, linear);
}
}
}
wait(0.45);
step("proof") {
par {
pulse(packet, 0.70);
say(caption, "The same dot is back at the User: one identity, seven connected journeys, zero animation gaps.", 0.40);
}
}
wait(1.10);
systems-architecture-poc
The first story: one Buy request travels Browser → CloudFront → API Gateway → Lambda → DynamoDB → SQS, auto-laid horizontally or vertically by the responsive region.
title("Systems Kit PoC — What Happens When You Click Buy?");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.07), "Made With Manic");
text(kicker, (cx, h*0.075), "SYSTEMS KIT · REQUEST JOURNEY");
text(headline, (cx, h*0.155), "What happens when you click Buy?");
text(caption, (cx, h*0.88), "One request keeps its identity while the architecture responds.");
size(kicker, 18*u); color(kicker, dim); bold(kicker);
size(headline, 34*u); bold(headline); wrap(headline, w*0.84);
size(caption, 19*u); color(caption, dim); wrap(caption, w*0.82);
architecture(shop, (cx, h*0.49), w*0.84, h*0.58);
node(browser, shop, "client", "Browser");
node(edge, shop, "aws:cloudfront", "CloudFront");
node(api, shop, "aws:api-gateway", "API Gateway");
node(checkout, shop, "aws:lambda", "Checkout");
node(orders, shop, "aws:dynamodb", "Orders");
node(events, shop, "aws:sqs", "Events");
connect(toEdge, browser, edge);
connect(toApi, edge, api);
connect(toCheckout, api, checkout);
connect(toOrders, checkout, orders);
connect(toEvents, orders, events);
request(order, browser, "BUY");
hidden(shop.nodes);
untraced(shop.connections);
hidden(order.parts);
step("architecture") {
stagger(0.10) {
show(browser, 0.30); show(edge, 0.30); show(api, 0.30);
show(checkout, 0.30); show(orders, 0.30); show(events, 0.30);
}
}
wait(0.25);
step("request") {
par {
show(order.parts, 0.30);
draw(shop.connections, 0.90);
say(caption, "The browser sends one request into the edge network.", 0.35);
}
}
wait(0.20);
step("edge") {
par {
route(order, toEdge, 0.90, smooth);
say(caption, "CloudFront receives it first, close to the customer.", 0.35);
}
}
step("api") {
par {
route(order, toApi, 0.90, smooth);
say(caption, "API Gateway validates and routes the operation.", 0.35);
}
}
step("compute") {
par {
route(order, toCheckout, 0.90, smooth);
say(caption, "Lambda executes the checkout logic.", 0.35);
}
}
step("persist") {
par {
route(order, toOrders, 0.90, smooth);
say(caption, "DynamoDB persists the order.", 0.35);
}
}
step("event") {
par {
route(order, toEvents, 0.90, smooth);
say(caption, "SQS decouples the work that can happen next.", 0.35);
}
}
wait(0.30);
step("takeaway") {
par {
pulse(shop.nodes, 0.75);
say(caption, "Architecture becomes understandable when we follow behavior—not only boxes and arrows.", 0.45);
}
}
wait(1.20);
systems-arrow-patterns
The connection-grammar reference: one-way, parallel, round-trip, port-aware orthogonal, fan-out, and diagonal-duplex lanes.
title("Systems Foundation — Arrow Patterns");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
let pw = w*0.27;
let ph = h*0.25;
let x1 = w*0.18;
let x2 = w*0.50;
let x3 = w*0.82;
let y1 = h*0.34;
let y2 = h*0.68;
watermark(mark, (w*0.09, h*0.045), "Made With Manic");
text(kicker, (cx, h*0.045), "SYSTEMS FOUNDATION · DIRECTION IS VISIBLE");
text(headline, (cx, h*0.12), "One arrow grammar, many delivery stories");
text(caption, (cx, h*0.925), "Arrows describe paths. seq, par, travel, and flow describe what moves through them.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 31*u); bold(headline); wrap(headline, w*0.86);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.88);
rect(p1, (x1,y1), pw,ph); outlined(p1); color(p1, dim); stroke(p1, 2*u);
rect(p2, (x2,y1), pw,ph); outlined(p2); color(p2, dim); stroke(p2, 2*u);
rect(p3, (x3,y1), pw,ph); outlined(p3); color(p3, dim); stroke(p3, 2*u);
rect(p4, (x1,y2), pw,ph); outlined(p4); color(p4, dim); stroke(p4, 2*u);
rect(p5, (x2,y2), pw,ph); outlined(p5); color(p5, dim); stroke(p5, 2*u);
rect(p6, (x3,y2), pw,ph); outlined(p6); color(p6, dim); stroke(p6, 2*u);
text(t1, (x1,y1-ph*0.36), "ONE WAY");
text(t2, (x2,y1-ph*0.36), "ROUND TRIP");
text(t3, (x3,y1-ph*0.36), "ORTHOGONAL");
text(t4, (x1,y2-ph*0.36), "VERTICAL PORTS");
text(t5, (x2,y2-ph*0.36), "FAN OUT");
text(t6, (x3,y2-ph*0.36), "DIAGONAL DUPLEX");
size(t1, 16*u); size(t2, 16*u); size(t3, 16*u);
size(t4, 16*u); size(t5, 16*u); size(t6, 16*u);
color(t1, dim); color(t2, dim); color(t3, dim);
color(t4, dim); color(t5, dim); color(t6, dim);
bold(t1); bold(t2); bold(t3); bold(t4); bold(t5); bold(t6);
// 1 — one source, one destination.
arrow(one, (x1-pw*0.34,y1+ph*0.08), (x1+pw*0.34,y1+ph*0.08));
color(one, cyan); stroke(one, 4*u); untraced(one);
circle(oneDot, (x1-pw*0.34,y1+ph*0.08), 8*u); color(oneDot, cyan); hidden(oneDot);
// 2 — request and response remain separate, honest directions.
arrow(outbound, (x2-pw*0.34,y1), (x2+pw*0.34,y1));
arrow(inbound, (x2+pw*0.34,y1+ph*0.17), (x2-pw*0.34,y1+ph*0.17));
color(outbound, cyan); color(inbound, magenta);
stroke(outbound, 4*u); stroke(inbound, 4*u);
untraced(outbound); untraced(inbound);
circle(requestDot, (x2-pw*0.34,y1), 8*u); color(requestDot, cyan); hidden(requestDot);
circle(responseDot, (x2+pw*0.34,y1+ph*0.17), 8*u); color(responseDot, magenta); hidden(responseDot);
// 3 — generic geometry can draw a stepped path; Systems nodes use one
// `connect(path, a, b, orthogonal)` declaration for the same routed identity.
line(elbowA, (x3-pw*0.34,y1-ph*0.02), (x3,y1-ph*0.02));
line(elbowB, (x3,y1-ph*0.02), (x3,y1+ph*0.20));
arrow(elbowC, (x3,y1+ph*0.20), (x3+pw*0.34,y1+ph*0.20));
color(elbowA, gold); color(elbowB, gold); color(elbowC, gold);
stroke(elbowA, 4*u); stroke(elbowB, 4*u); stroke(elbowC, 4*u);
untraced(elbowA); untraced(elbowB); untraced(elbowC);
circle(elbowDot, (x3-pw*0.34,y1-ph*0.02), 8*u); color(elbowDot, gold); hidden(elbowDot);
// 4 — ports can face vertically without changing motion semantics.
arrow(downLane, (x1-pw*0.12,y2-ph*0.20), (x1-pw*0.12,y2+ph*0.25));
arrow(upLane, (x1+pw*0.12,y2+ph*0.25), (x1+pw*0.12,y2-ph*0.20));
color(downLane, cyan); color(upLane, magenta);
stroke(downLane, 4*u); stroke(upLane, 4*u);
untraced(downLane); untraced(upLane);
circle(downDot, (x1-pw*0.12,y2-ph*0.20), 8*u); color(downDot, cyan); hidden(downDot);
circle(upDot, (x1+pw*0.12,y2+ph*0.25), 8*u); color(upDot, magenta); hidden(upDot);
// 5 — fan-out is three explicitly authored deliveries, not inferred broadcast.
arrow(fanA, (x2-pw*0.34,y2+ph*0.04), (x2+pw*0.34,y2-ph*0.22));
arrow(fanB, (x2-pw*0.34,y2+ph*0.04), (x2+pw*0.34,y2+ph*0.04));
arrow(fanC, (x2-pw*0.34,y2+ph*0.04), (x2+pw*0.34,y2+ph*0.30));
color(fanA, cyan); color(fanB, cyan); color(fanC, cyan);
stroke(fanA, 3*u); stroke(fanB, 3*u); stroke(fanC, 3*u);
untraced(fanA); untraced(fanB); untraced(fanC);
circle(fanDotA, (x2-pw*0.34,y2+ph*0.04), 7*u); color(fanDotA, cyan); hidden(fanDotA);
circle(fanDotB, (x2-pw*0.34,y2+ph*0.04), 7*u); color(fanDotB, cyan); hidden(fanDotB);
circle(fanDotC, (x2-pw*0.34,y2+ph*0.04), 7*u); color(fanDotC, cyan); hidden(fanDotC);
// 6 — diagonal request/return lanes use different styling and directions.
arrow(diagOut, (x3-pw*0.32,y2+ph*0.24), (x3+pw*0.27,y2-ph*0.22));
arrow(diagBack, (x3+pw*0.34,y2-ph*0.12), (x3-pw*0.25,y2+ph*0.34));
color(diagOut, cyan); color(diagBack, magenta); dashed(diagBack);
stroke(diagOut, 4*u); stroke(diagBack, 4*u);
untraced(diagOut); untraced(diagBack);
circle(diagRequest, (x3-pw*0.32,y2+ph*0.24), 8*u); color(diagRequest, cyan); hidden(diagRequest);
circle(diagResponse, (x3+pw*0.34,y2-ph*0.12), 8*u); color(diagResponse, magenta); hidden(diagResponse);
tag(p1, cards); tag(p2, cards); tag(p3, cards); tag(p4, cards); tag(p5, cards); tag(p6, cards);
tag(t1, cards); tag(t2, cards); tag(t3, cards); tag(t4, cards); tag(t5, cards); tag(t6, cards);
tag(one, paths); tag(outbound, paths); tag(inbound, paths);
tag(elbowA, paths); tag(elbowB, paths); tag(elbowC, paths);
tag(downLane, paths); tag(upLane, paths);
tag(fanA, paths); tag(fanB, paths); tag(fanC, paths);
tag(diagOut, paths); tag(diagBack, paths);
hidden(kicker); hidden(headline); hidden(caption); hidden(cards);
step("arrow-grammar") {
par {
show(kicker, 0.30);
show(headline, 0.45);
show(caption, 0.40);
show(cards, 0.45);
draw(paths, 1.10, smooth);
}
}
wait(0.35);
step("traffic") {
par {
show(oneDot, 0.15); travel(oneDot, one, 1.20, linear); flow(one, 1.20);
seq {
show(requestDot, 0.15); travel(requestDot, outbound, 0.65, linear);
show(responseDot, 0.15); travel(responseDot, inbound, 0.65, linear);
}
seq {
show(elbowDot, 0.15);
travel(elbowDot, elbowA, 0.40, linear);
travel(elbowDot, elbowB, 0.35, linear);
travel(elbowDot, elbowC, 0.45, linear);
}
par {
show(downDot, 0.15); travel(downDot, downLane, 1.20, linear);
show(upDot, 0.15); travel(upDot, upLane, 1.20, linear);
}
par {
show(fanDotA, 0.15); travel(fanDotA, fanA, 1.20, linear);
show(fanDotB, 0.15); travel(fanDotB, fanB, 1.20, linear);
show(fanDotC, 0.15); travel(fanDotC, fanC, 1.20, linear);
}
par {
show(diagRequest, 0.15); travel(diagRequest, diagOut, 1.20, linear);
show(diagResponse, 0.15); travel(diagResponse, diagBack, 1.20, linear);
}
}
}
wait(0.55);
step("creator-controls-semantics") {
par {
pulse(paths, 0.75);
say(caption, "The same paths can mean request, response, queue delivery, broadcast, or pure motion—the creator decides.", 0.40);
}
}
wait(1.20);
microservices-platform
Auto-fit showcase: Route 53 → load balancer → gateway, three availability zones of ECS services, a replicated RDS database cluster, and CloudWatch monitoring — declared with zero coordinates. Add a tier or a zone and the whole diagram reflows and scales itself down to the frame; paths coloured by relationship (request · write · replication · telemetry).
title("A Microservices Platform — Laid Out Automatically");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.055), "DIAGRAMS · AUTO-FIT · NO COORDINATES");
text(headline, (cx, h*0.12), "Add a tier. It re-fits itself.");
text(caption, (cx, h*0.95), "DNS, load balancing, three AZs of services, a database cluster, and monitoring — all auto-laid.");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 26*u); bold(headline); wrap(headline, w*0.86);
size(caption, 17*u); color(caption, dim); wrap(caption, w*0.86);
// No geometry: the diagram auto-fits. Route 53, an edge load balancer, three
// availability zones, a replicated database cluster and monitoring all reflow
// to fit — and when the content would overflow, the whole diagram scales down
// as one. The author types no coordinate.
architecture(platform);
node(dns, platform, "aws:route53", "route 53");
node(lb, platform, "aws:elb", "load balancer");
node(gateway, platform, "aws:api-gateway", "gateway");
cluster(region, platform, "REGION us-east-1");
cluster(azA, region, "AZ-A");
node(a1, azA, "aws:ecs", "svc"); node(a2, azA, "aws:ecs", "svc"); node(a3, azA, "aws:ecs", "svc");
cluster(azB, region, "AZ-B");
node(b1, azB, "aws:ecs", "svc"); node(b2, azB, "aws:ecs", "svc"); node(b3, azB, "aws:ecs", "svc");
cluster(azC, region, "AZ-C");
node(c1, azC, "aws:ecs", "svc"); node(c2, azC, "aws:ecs", "svc"); node(c3, azC, "aws:ecs", "svc");
cluster(database, platform, "DATABASE CLUSTER");
node(primary, database, "aws:rds", "primary");
node(replica1, database, "aws:rds", "replica");
node(replica2, database, "aws:rds", "replica");
node(cache, database, "aws:elasticache", "cache");
cluster(observability, platform, "MONITORING");
node(metrics, observability, "aws:cloudwatch", "metrics");
node(logs, observability, "aws:cloudwatch", "logs");
node(alarms, observability, "aws:cloudwatch", "alarms");
connect(dnsToLb, dns, lb); // resolve, then hit the edge load balancer
connect(lbToGw, lb, gateway); // balanced across the API gateway
connect(toA, gateway, azA); // gateway fans out to each zone's services
connect(toB, gateway, azB);
connect(toC, gateway, azC);
connect(toWrite, a1, primary); // a representative service writes the primary
connect(toRead, a1, replica1); // reads served from a replica
connect(toCache, a1, cache);
connect(repl1, primary, replica1); // primary replicates to both replicas
connect(repl2, primary, replica2);
connect(toMetrics, b1, metrics); // services emit telemetry to monitoring
color(dnsToLb, cyan); color(dnsToLb.hot, cyan); // request path
color(lbToGw, cyan); color(lbToGw.hot, cyan);
color(toA, cyan); color(toA.hot, cyan);
color(toB, cyan); color(toB.hot, cyan);
color(toC, cyan); color(toC.hot, cyan);
color(toRead, cyan); color(toRead.hot, cyan);
color(toCache, cyan); color(toCache.hot, cyan);
color(toWrite, magenta); color(toWrite.hot, magenta); // write path
color(repl1, lime); color(repl1.hot, lime); // replication
color(repl2, lime); color(repl2.hot, lime);
color(toMetrics, gold); color(toMetrics.hot, gold); // telemetry
request(call, dns, "GET /order");
hidden(platform.nodes);
hidden(region.parts); hidden(azA.parts); hidden(azB.parts); hidden(azC.parts);
hidden(database.parts); hidden(observability.parts);
hidden(call.parts);
untraced(dnsToLb); untraced(lbToGw);
untraced(toA); untraced(toB); untraced(toC);
untraced(toWrite); untraced(toRead); untraced(toCache);
untraced(repl1); untraced(repl2); untraced(toMetrics);
step("edge") {
par {
stagger(0.08) { show(dns, 0.4); show(lb, 0.4); show(gateway, 0.4); }
say(caption, "Route 53 resolves the name, an edge load balancer spreads the traffic, the gateway takes it from there.", 0.4);
}
}
wait(0.2);
step("reveal-zones") {
par {
show(region.parts, 0.4);
show(azA.parts, 0.4); stagger(0.05) { show(a1,0.3); show(a2,0.3); show(a3,0.3); }
show(azB.parts, 0.4); stagger(0.05) { show(b1,0.3); show(b2,0.3); show(b3,0.3); }
show(azC.parts, 0.4); stagger(0.05) { show(c1,0.3); show(c2,0.3); show(c3,0.3); }
say(caption, "Three availability zones, each with its own services — nested clusters, auto-laid.", 0.4);
}
}
wait(0.2);
step("state-and-monitoring") {
par {
show(database.parts, 0.4); show(primary, 0.4); show(replica1, 0.4); show(replica2, 0.4); show(cache, 0.4);
show(observability.parts, 0.4); show(metrics, 0.4); show(logs, 0.4); show(alarms, 0.4);
say(caption, "A replicated database cluster and monitoring join in — the whole diagram re-fits.", 0.4);
}
}
wait(0.25);
step("topology") {
par {
stagger(0.06) {
draw(dnsToLb,0.4); draw(lbToGw,0.4);
draw(toA,0.4); draw(toB,0.4); draw(toC,0.4);
draw(toWrite,0.4); draw(toRead,0.4); draw(toCache,0.4);
draw(repl1,0.4); draw(repl2,0.4); draw(toMetrics,0.4);
}
say(caption, "Paths coloured by relationship: request cyan, writes magenta, replication lime, telemetry gold.", 0.4);
}
}
wait(0.3);
step("one-request") {
par {
seq {
show(call.parts, 0.2);
route(call, dnsToLb, 0.7, smooth);
route(call, lbToGw, 0.7, smooth);
route(call, toA, 0.7, smooth);
route(call, toWrite, 0.7, smooth);
}
say(caption, "One order: Route 53 → load balancer → gateway → an AZ-A service → the primary database.", 0.4);
}
}
wait(0.3);
step("takeaway") {
par {
pulse(region.parts, 0.7);
pulse(database.parts, 0.7);
say(caption, "Add a tier, a zone, a replica — you never touch a coordinate. That is manic.", 0.45);
}
}
wait(1.4);
factorial-flowchart
A flowchart that runs: flowchart(fc) auto-lays seven shape-nodes (terminator/io/process/decision) top-down with no coordinates, then a token walks the factorial loop — taking the yes branch, looping back, and exiting to the end. Node shapes are string kinds; branches are coloured and annotated yes/no/loop, on a clean paper theme with a colour legend.
title("A Flowchart That Runs — the Factorial Loop");
canvas("16:9");
template("paper");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.06), "DIAGRAMS · FLOWCHART · AUTO-LAID");
text(headline, (cx, h*0.12), "A flowchart that runs");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 26*u); bold(headline); wrap(headline, w*0.86);
// `flowchart(fc)` ranks nodes by their connections, auto-fits, and auto-orients:
// a shallow flow like this stays top-down on its own. No coordinates, no direction
// to choose. Node shapes are string kinds.
flowchart(fc);
node(start, fc, "terminator", "start");
node(rd, fc, "io", "read n");
node(init, fc, "process", "f=1 i=1");
node(dec, fc, "decision", "i <= n?");
node(body, fc, "process", "f=f*i i=i+1");
node(out, fc, "io", "print f");
node(fin, fc, "terminator", "end");
connect(e1, start, rd);
connect(e2, rd, init);
connect(e3, init, dec);
connect(e4, dec, body); // keep looping
connect(e5, dec, out); // exit
connect(e6, body, dec, orthogonal, right, right); // loop back up the side
connect(e7, out, fin);
color(e4, cyan); color(e4.hot, cyan);
color(e5, lime); color(e5.hot, lime);
color(e6, gold); color(e6.hot, gold);
annotate(e4, "yes"); annotate(e5, "no"); annotate(e6, "loop");
// A colour key stands in for narration — the diagram explains itself.
text(k1, (w*0.34, h*0.93), "— continue"); size(k1, 14*u); color(k1, cyan); bold(k1);
text(k2, (w*0.50, h*0.93), "— exit"); size(k2, 14*u); color(k2, lime); bold(k2);
text(k3, (w*0.66, h*0.93), "— loop back"); size(k3, 14*u); color(k3, gold); bold(k3);
request(tok, start, "n = 3");
hidden(fc.nodes);
hidden(tok.parts);
step("shape") {
stagger(0.07) {
show(start, 0.3); show(rd, 0.3); show(init, 0.3); show(dec, 0.3);
show(body, 0.3); show(out, 0.3); show(fin, 0.3);
}
}
wait(0.2);
step("run") {
seq {
show(tok.parts, 0.2);
route(tok, e1, 0.4, smooth); route(tok, e2, 0.4, smooth); route(tok, e3, 0.4, smooth);
route(tok, e4, 0.4, smooth); route(tok, e6, 0.5, smooth);
route(tok, e4, 0.4, smooth); route(tok, e6, 0.5, smooth);
route(tok, e4, 0.4, smooth); route(tok, e6, 0.5, smooth);
route(tok, e5, 0.4, smooth); route(tok, e7, 0.4, smooth);
}
}
wait(0.3);
step("takeaway") {
seq { pulse(fin, 0.7); pulse(fin, 0.7); }
}
wait(1.0);
complex-flowchart
A big pipeline that builds itself, then runs: a 24-node CI/CD flow with 7 decisions and feedback loops auto-wraps into readable side-by-side columns (long loops routed around the perimeter). It first draws box-by-box in flow order, then pairs of commits race the pipeline in parallel to different outcomes — ship, rollback-to-start, held — twice. All flowchart + route + par, no coordinates.
title("Deployment Pipeline — Many Decisions & Loops");
// Stress test: 7 decisions and several back-edges — some loop to an earlier step,
// one loops all the way back to `start`. `flowchart` still auto-wraps into
// side-by-side columns; forward edges are clean elbows, backward edges (loops,
// column wraps) arc as curves. No coordinates.
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.055), "DIAGRAMS · FLOWCHART · DECISIONS & LOOPS");
text(headline, (cx, h*0.12), "Seven decisions, five loops, one chart");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 23*u); bold(headline); wrap(headline, w*0.86);
flowchart(cd, auto, 40);
node(start, cd, "terminator", "start");
node(checkout, cd, "io", "checkout");
node(install, cd, "process", "install deps");
node(build, cd, "process", "build");
node(bok, cd, "decision", "build ok?");
node(fix, cd, "process", "fix build");
node(unit, cd, "process", "unit tests");
node(uok, cd, "decision", "tests pass?");
node(debug, cd, "process", "debug");
node(integ, cd, "process", "integration");
node(iok, cd, "decision", "integ ok?");
node(scan, cd, "process", "security scan");
node(sok, cd, "decision", "scan clean?");
node(patch, cd, "process", "patch deps");
node(stage, cd, "process", "deploy staging");
node(smoke, cd, "process", "smoke test");
node(smok, cd, "decision", "smoke ok?");
node(approve, cd, "decision", "approve prod?");
node(hold, cd, "terminator", "held");
node(roll, cd, "process", "rollback");
node(prod, cd, "process", "deploy prod");
node(health, cd, "decision", "healthy?");
node(notify, cd, "process", "notify team");
node(done, cd, "terminator", "shipped");
connect(e1, start, checkout);
connect(e2, checkout, install);
connect(e3, install, build);
connect(e4, build, bok);
connect(e5, bok, unit); // yes
connect(e6, bok, fix); // no
connect(e7, fix, build); // loop -> build
connect(e8, unit, uok);
connect(e9, uok, integ); // yes
connect(e10, uok, debug); // no
connect(e11, debug, build); // loop -> build
connect(e12, integ, iok);
connect(e13, iok, scan); // yes
connect(e14, iok, debug); // no -> debug (which loops to build)
connect(e15, scan, sok);
connect(e16, sok, stage); // yes
connect(e17, sok, patch); // no
connect(e18, patch, scan); // loop -> scan
connect(e19, stage, smoke);
connect(e20, smoke, smok);
connect(e21, smok, approve); // yes
connect(e22, smok, roll); // no
connect(e23, roll, start); // loop -> start (all the way back)
connect(e24, approve, prod); // yes
connect(e25, approve, hold); // no
connect(e26, prod, health);
connect(e27, health, notify); // yes
connect(e28, health, roll); // no -> rollback
connect(e29, notify, done);
// forward = proceed, back-edges = loops/failures
color(e6, magenta); color(e6.hot, magenta);
color(e10, magenta); color(e10.hot, magenta);
color(e14, magenta); color(e14.hot, magenta);
color(e17, magenta); color(e17.hot, magenta);
color(e22, magenta); color(e22.hot, magenta);
color(e28, magenta); color(e28.hot, magenta);
color(e7, gold); color(e7.hot, gold);
color(e11, gold); color(e11.hot, gold);
color(e18, gold); color(e18.hot, gold);
color(e23, gold); color(e23.hot, gold);
annotate(e5, "yes"); annotate(e6, "no");
annotate(e9, "yes"); annotate(e10, "no");
annotate(e16, "yes"); annotate(e17, "no");
annotate(e21, "yes"); annotate(e22, "no");
annotate(e24, "yes"); annotate(e25, "no");
annotate(e27, "yes"); annotate(e28, "no");
text(k1, (w*0.36, h*0.94), "— proceed"); size(k1, 13*u); color(k1, fg); bold(k1);
text(k2, (w*0.52, h*0.94), "— reject"); size(k2, 13*u); color(k2, magenta); bold(k2);
text(k3, (w*0.66, h*0.94), "— loop back"); size(k3, 13*u); color(k3, gold); bold(k3);
// Two tokens per round, released together — they share the pipeline, then split
// at a decision and reach different ends. Two rounds, then done.
request(a1, start, "PR-A"); request(b1, start, "PR-B"); // round 1
request(a2, start, "PR-C"); request(b2, start, "PR-D"); // round 2
hidden(cd.nodes);
untraced(cd.connections); // edges start undrawn — we draw them as we build
hidden(a1.parts); hidden(b1.parts); hidden(a2.parts); hidden(b2.parts);
// Build the chart the way you'd draw it: each box appears in flow order and the
// edge into it draws first, so you watch the whole pipeline take shape — then the
// tokens run it. Best way to learn how a flowchart is put together.
step("build-chart") {
seq {
show(start, 0.3);
draw(e1, 0.14); show(checkout, 0.18);
draw(e2, 0.14); show(install, 0.18);
draw(e3, 0.14); show(build, 0.18);
draw(e4, 0.14); show(bok, 0.18);
draw(e5, 0.14); show(unit, 0.18);
draw(e6, 0.14); show(fix, 0.18);
draw(e8, 0.14); show(uok, 0.18);
draw(e9, 0.14); show(integ, 0.18);
draw(e10, 0.14); show(debug, 0.18);
draw(e12, 0.14); show(iok, 0.18);
draw(e13, 0.14); show(scan, 0.18);
draw(e14, 0.14); // iok no → debug (already shown)
draw(e15, 0.14); show(sok, 0.18);
draw(e16, 0.14); show(stage, 0.18);
draw(e17, 0.14); show(patch, 0.18);
draw(e19, 0.14); show(smoke, 0.18);
draw(e20, 0.14); show(smok, 0.18);
draw(e21, 0.14); show(approve, 0.18);
draw(e22, 0.14); show(roll, 0.18);
draw(e24, 0.14); show(prod, 0.18);
draw(e25, 0.14); show(hold, 0.18);
draw(e26, 0.14); show(health, 0.18);
draw(e27, 0.14); show(notify, 0.18);
draw(e28, 0.14); // health no → roll (already shown)
draw(e29, 0.14); show(done, 0.18);
// feedback loops complete the picture last
draw(e7, 0.22); draw(e11, 0.22); draw(e18, 0.22); draw(e23, 0.35);
}
}
wait(0.5);
// Round 1 — two PRs race the pipeline in parallel. They ride together until the
// smoke test, where A ships and B fails and rolls back around to the start.
step("round-1") {
par {
seq {
show(a1.parts, 0.2);
route(a1, e1, 0.22, smooth); route(a1, e2, 0.22, smooth); route(a1, e3, 0.22, smooth);
route(a1, e4, 0.22, smooth); route(a1, e5, 0.22, smooth); route(a1, e8, 0.22, smooth);
route(a1, e9, 0.22, smooth); route(a1, e12, 0.22, smooth); route(a1, e13, 0.22, smooth);
route(a1, e15, 0.22, smooth); route(a1, e16, 0.22, smooth); route(a1, e19, 0.22, smooth);
route(a1, e20, 0.22, smooth); route(a1, e21, 0.22, smooth); route(a1, e24, 0.22, smooth);
route(a1, e26, 0.22, smooth); route(a1, e27, 0.22, smooth); route(a1, e29, 0.3, smooth);
}
seq {
show(b1.parts, 0.2);
route(b1, e1, 0.22, smooth); route(b1, e2, 0.22, smooth); route(b1, e3, 0.22, smooth);
route(b1, e4, 0.22, smooth); route(b1, e5, 0.22, smooth); route(b1, e8, 0.22, smooth);
route(b1, e9, 0.22, smooth); route(b1, e12, 0.22, smooth); route(b1, e13, 0.22, smooth);
route(b1, e15, 0.22, smooth); route(b1, e16, 0.22, smooth); route(b1, e19, 0.22, smooth);
route(b1, e20, 0.22, smooth);
route(b1, e22, 0.35, smooth); // smoke ok? no → rollback
route(b1, e23, 0.8, smooth); // rollback → start (around the perimeter)
}
}
}
wait(0.6);
step("clear-1") {
par { fade(a1.parts, 0.3); fade(b1.parts, 0.3); to(cd.hotpaths, trace, 0, 0.4); }
}
wait(0.3);
// Round 2 — another pair. They split at the unit-test gate: C carries on but is
// held at approval, while D fails its tests and loops back to rebuild.
step("round-2") {
par {
seq {
show(a2.parts, 0.2);
route(a2, e1, 0.22, smooth); route(a2, e2, 0.22, smooth); route(a2, e3, 0.22, smooth);
route(a2, e4, 0.22, smooth); route(a2, e5, 0.22, smooth); route(a2, e8, 0.22, smooth);
route(a2, e9, 0.22, smooth); route(a2, e12, 0.22, smooth); route(a2, e13, 0.22, smooth);
route(a2, e15, 0.22, smooth); route(a2, e16, 0.22, smooth); route(a2, e19, 0.22, smooth);
route(a2, e20, 0.22, smooth); route(a2, e21, 0.25, smooth);
route(a2, e25, 0.35, smooth); // approve prod? no → held
}
seq {
show(b2.parts, 0.2);
route(b2, e1, 0.22, smooth); route(b2, e2, 0.22, smooth); route(b2, e3, 0.22, smooth);
route(b2, e4, 0.22, smooth); route(b2, e5, 0.22, smooth); route(b2, e8, 0.22, smooth);
route(b2, e10, 0.35, smooth); // tests pass? no → debug
route(b2, e11, 0.6, smooth); // debug → build (loop back)
}
}
}
wait(0.6);
step("clear-2") {
par { fade(a2.parts, 0.3); fade(b2.parts, 0.3); to(cd.hotpaths, trace, 0, 0.4); }
}
wait(1.0);
c4-internet-banking
C4 Level 1 — System Context: the Internet Banking System in its world. A customer (a person, drawn as a box with a head), the system itself, and two external systems (e-mail, mainframe), joined by labelled relationships. Outline styling, [Type] tags and people-top tiers, all auto-laid with no coordinates.
title("Internet Banking — C4 System Context");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.055), "DIAGRAMS · C4 · SYSTEM CONTEXT");
text(headline, (cx, h*0.12), "Internet Banking — the big picture");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 24*u); bold(headline); wrap(headline, w*0.86);
// `c4(id, level)` — inside it, node kinds are C4 elements. The extra args are the
// box's description and (for containers) technology. No coordinates.
c4(bank, context);
node(cust, bank, "person", "Personal Banking Customer", "A customer of the bank with personal accounts");
node(ibs, bank, "system", "Internet Banking System", "Lets customers view accounts and make payments");
node(mail, bank, "external", "E-mail System", "Sends notifications to customers");
node(core, bank, "external", "Mainframe Banking System", "Stores core banking information");
connect(r1, cust, ibs); annotate(r1, "Views accounts, makes payments");
connect(r2, ibs, core); annotate(r2, "Reads/writes [XML/HTTPS]");
connect(r3, ibs, mail); annotate(r3, "Sends e-mail [SMTP]");
color(r1, cyan); color(r1.hot, cyan);
color(r2, cyan); color(r2.hot, cyan);
color(r3, gold); color(r3.hot, gold);
c4-internet-banking-containers
C4 Level 2 — Containers: zoom inside the system to a single-page app, an API application and a database, each carrying its technology in a [Container: tech] tag; the mainframe stays external. Same c4 container, one level down.
title("Internet Banking — C4 Containers");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.055), "DIAGRAMS · C4 · CONTAINER");
text(headline, (cx, h*0.12), "Inside the Internet Banking System");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 24*u); bold(headline); wrap(headline, w*0.86);
// Level 2 — zoom into the system: its containers, each with a technology. The
// technology is the optional 6th `node` argument, shown in the `[Container: …]` tag.
c4(bank, container);
node(cust, bank, "person", "Customer", "A retail banking customer");
node(spa, bank, "container", "Single-Page App", "Account summary and payments in the browser", "JavaScript / Angular");
node(api, bank, "container", "API Application", "Banking functionality via a JSON/HTTPS API", "Java / Spring Boot");
node(db, bank, "container", "Database", "Stores users, accounts and payments", "Oracle");
node(core, bank, "external", "Mainframe Banking System", "Stores core banking information");
connect(r1, cust, spa); annotate(r1, "Uses [HTTPS]");
connect(r2, spa, api); annotate(r2, "Calls [JSON/HTTPS]");
connect(r3, api, db); annotate(r3, "Reads/writes [JDBC]");
connect(r4, api, core); annotate(r4, "Uses [XML/HTTPS]");
color(r1, cyan); color(r1.hot, cyan);
color(r2, cyan); color(r2.hot, cyan);
color(r3, cyan); color(r3.hot, cyan);
color(r4, gold); color(r4.hot, gold);
c4-internet-banking-components
C4 Level 3 — Components: inside the API application, sign-in and accounts controllers, a security component and a mainframe facade — declared so related pairs sit adjacent, so the Uses and Reads/writes edges never cross an intervening box.
title("Internet Banking — C4 Components");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.055), "DIAGRAMS · C4 · COMPONENT");
text(headline, (cx, h*0.12), "Inside the API Application");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 24*u); bold(headline); wrap(headline, w*0.86);
// Level 3 — zoom into a container: its components. `component` is a C4 node kind;
// the dependencies it talks to sit below as externals.
// Declaration order = left-to-right in the tier, so keep each related pair
// adjacent (sign-in beside security, accounts beside the facade) — their "Uses"
// edge then sits in the gap instead of crossing an intervening box.
c4(api, component);
node(signin, api, "component", "Sign-In Controller", "Lets users sign in", "Spring MVC");
node(security, api, "component", "Security Component", "Authentication and access control", "Spring Bean");
node(accounts, api, "component", "Accounts Controller", "Provides account summaries", "Spring MVC");
node(facade, api, "component", "Mainframe Facade", "A facade over the mainframe", "Spring Bean");
node(db, api, "external", "Database", "Stores users, accounts and payments");
node(mf, api, "external", "Mainframe Banking System", "Stores core banking information");
connect(c1, signin, security); annotate(c1, "Uses");
connect(c2, accounts, facade); annotate(c2, "Uses");
connect(c3, security, db); annotate(c3, "Reads/writes [JDBC]");
connect(c4x, facade, mf); annotate(c4x, "Uses [XML/HTTPS]");
color(c1, cyan); color(c1.hot, cyan);
color(c2, cyan); color(c2.hot, cyan);
color(c3, cyan); color(c3.hot, cyan);
color(c4x, gold); color(c4x.hot, gold);
c4-zoom
The C4 differentiator — it moves. One diagram zooms from System Context into its Containers: zoom into the centred system, fade the surroundings, then reveal the containers — author-composed with zoom/fade/show and sticky chrome, no new vocabulary.
title("C4 — Zoom From Context Into Containers");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic"); sticky(mark);
text(kicker, (cx, h*0.055), "DIAGRAMS · C4 · CONTEXT → CONTAINER");
size(kicker, 15*u); color(kicker, dim); bold(kicker); sticky(kicker);
text(cap, (cx, h*0.93), "One system in its world.");
size(cap, 16*u); color(cap, dim); wrap(cap, w*0.86); sticky(cap);
// ---- Level 1: System Context (visible first) ----
c4(ctx, context);
node(cust, ctx, "person", "Customer", "A retail banking customer");
node(ibs, ctx, "system", "Internet Banking System", "View accounts, make payments");
node(mail, ctx, "external", "E-mail System", "Sends notifications");
node(core, ctx, "external", "Mainframe", "Core banking information");
connect(x1, cust, ibs); annotate(x1, "Uses");
connect(x2, ibs, mail); annotate(x2, "Sends e-mail [SMTP]");
connect(x3, ibs, core); annotate(x3, "Reads/writes [XML/HTTPS]");
color(x1, cyan); color(x1.hot, cyan);
color(x2, gold); color(x2.hot, gold);
color(x3, cyan); color(x3.hot, cyan);
// ---- Level 2: Containers (hidden until we zoom in) ----
c4(cnt, container);
node(pcust, cnt, "person", "Customer", "A retail banking customer");
node(spa, cnt, "container", "Single-Page App", "Account UI in the browser", "JavaScript / Angular");
node(api, cnt, "container", "API Application", "Banking functionality (JSON/HTTPS)", "Java / Spring Boot");
node(db, cnt, "container", "Database", "Users, accounts and payments", "Oracle");
node(mf, cnt, "external", "Mainframe", "Core banking information");
connect(y1, pcust, spa); annotate(y1, "Uses [HTTPS]");
connect(y2, spa, api); annotate(y2, "Calls [JSON/HTTPS]");
connect(y3, api, db); annotate(y3, "Reads/writes [JDBC]");
connect(y4, api, mf); annotate(y4, "Uses [XML/HTTPS]");
color(y1, cyan); color(y1.hot, cyan);
color(y2, cyan); color(y2.hot, cyan);
color(y3, cyan); color(y3.hot, cyan);
color(y4, gold); color(y4.hot, gold);
// The container diagram overlaps the context one (both centred) — hide it, and its
// duplicate frame, until the zoom reveals it.
hidden(cnt);
hidden(cnt.nodes); hidden(cnt.connections); hidden(cnt.hotpaths);
wait(0.8);
// Push the camera into the centred system, dropping everything around it.
step("zoom-in") {
par {
say(cap, "Zoom into the Internet Banking System…", 0.4);
zoom(2.6, 1.1, smooth);
fade(cust, 0.6); fade(mail, 0.6); fade(core, 0.6);
fade(ctx.connections, 0.6);
}
}
wait(0.2);
// The system dissolves and its containers rise in as the camera pulls back.
step("open") {
par {
say(cap, "…and its containers are inside: SPA → API → Database.", 0.4);
fade(ibs, 0.4);
zoom(1.0, 1.0, smooth);
show(cnt.nodes, 0.7);
show(cnt.connections, 0.8);
}
}
wait(1.4);
c4-story
One system, at every altitude — a full end-to-end walkthrough. Context → zoom in → the Containers build along a GET /accounts request as it travels browser→API→database → zoom into the API → the Components build along a sign-in call → zoom back out. The flow IS the reveal, led by a moving token.
title("C4 — One System, at Every Altitude");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic"); sticky(mark);
text(kicker, (cx, h*0.055), "DIAGRAMS · C4 · CONTEXT → CONTAINER → COMPONENT");
size(kicker, 15*u); color(kicker, dim); bold(kicker); sticky(kicker);
text(cap, (cx, h*0.93), "Every system lives in a world of people and neighbours.");
size(cap, 16*u); color(cap, dim); wrap(cap, w*0.86); sticky(cap);
// ---- Level 1: System Context ----
c4(ctx, context);
node(cust, ctx, "person", "Customer", "A retail banking customer");
node(sys, ctx, "system", "Internet Banking System", "View accounts, make payments");
node(mail, ctx, "external", "E-mail System", "Sends notifications");
node(core, ctx, "external", "Mainframe", "Core banking information");
connect(cx1, cust, sys); annotate(cx1, "Uses");
connect(cx2, sys, core); annotate(cx2, "Reads/writes [XML/HTTPS]");
connect(cx3, sys, mail); annotate(cx3, "Sends e-mail [SMTP]");
color(cx1, cyan); color(cx1.hot, cyan);
color(cx2, cyan); color(cx2.hot, cyan);
color(cx3, gold); color(cx3.hot, gold);
// ---- Level 2: Containers ----
c4(cnt, container);
node(ccust, cnt, "person", "Customer", "A retail banking customer");
node(spa, cnt, "container", "Single-Page App", "Account UI in the browser", "JavaScript / Angular");
node(api, cnt, "container", "API Application", "Banking functionality (JSON/HTTPS)", "Java / Spring Boot");
node(cdb, cnt, "container", "Database", "Users, accounts, payments", "Oracle");
node(cmf, cnt, "external", "Mainframe", "Core banking information");
connect(cn1, ccust, spa); annotate(cn1, "Uses [HTTPS]");
connect(cn2, spa, api); annotate(cn2, "Calls [JSON/HTTPS]");
connect(cn3, api, cdb); annotate(cn3, "Reads/writes [JDBC]");
connect(cn4, api, cmf); annotate(cn4, "Uses [XML/HTTPS]");
color(cn1, cyan); color(cn1.hot, cyan);
color(cn2, cyan); color(cn2.hot, cyan);
color(cn3, cyan); color(cn3.hot, cyan);
color(cn4, gold); color(cn4.hot, gold);
// ---- Level 3: Components (inside the API) ----
// Declaration order fills the 2x2 grid row-major, so ordering the pairs as
// (signin, accounts) then (security, facade) makes two clean vertical columns:
// sign-in → security → database | accounts → facade → mainframe
c4(cmp, component);
node(signin, cmp, "component", "Sign-In Controller", "Lets users sign in", "Spring MVC");
node(acct, cmp, "component", "Accounts Controller", "Account summaries", "Spring MVC");
node(sec, cmp, "component", "Security Component", "Authentication & access", "Spring Bean");
node(fac, cmp, "component", "Mainframe Facade", "Facade over the mainframe", "Spring Bean");
node(mdb, cmp, "external", "Database", "Users, accounts, payments");
node(mmf, cmp, "external", "Mainframe", "Core banking information");
connect(cp1, signin, sec); annotate(cp1, "Uses");
connect(cp2, acct, fac); annotate(cp2, "Uses");
connect(cp3, sec, mdb); annotate(cp3, "Reads/writes [JDBC]");
connect(cp4, fac, mmf); annotate(cp4, "Uses [XML/HTTPS]");
color(cp1, cyan); color(cp1.hot, cyan);
color(cp2, cyan); color(cp2.hot, cyan);
color(cp3, cyan); color(cp3.hot, cyan);
color(cp4, gold); color(cp4.hot, gold);
// tokens: one request per level
request(req, ccust, "GET /accounts");
request(req2, signin, "authorise");
// Start at Context; everything else is hidden and overlaps the same centre.
hidden(cnt); hidden(cnt.nodes); hidden(cnt.connections); hidden(cnt.hotpaths);
hidden(cmp); hidden(cmp.nodes); hidden(cmp.connections); hidden(cmp.hotpaths);
hidden(ctx.nodes);
hidden(req.parts); hidden(req2.parts);
// 1 — the system in its world (pieces arrive one by one)
step("context") {
seq {
stagger(0.12) { show(cust, 0.4); show(sys, 0.4); show(mail, 0.4); show(core, 0.4); }
draw(ctx.connections, 0.7);
}
}
wait(0.5);
// 2 — push into the system
step("into-system") {
par {
say(cap, "Zoom into the system…", 0.4);
zoom(2.5, 1.1, smooth);
fade(cust, 0.6); fade(mail, 0.6); fade(core, 0.6); fade(ctx.connections, 0.6);
}
}
wait(0.2);
// 3 — inside, the containers ASSEMBLE as the request travels through them.
// The dot leads: each box + its edge materialise just as the call arrives.
step("containers") {
seq {
par {
say(cap, "Inside — a request builds the containers as it travels.", 0.4);
fade(sys, 0.4);
zoom(1.0, 1.0, smooth);
}
par { show(ccust, 0.4); show(req.parts, 0.25); }
par { show(cn1, 0.6); show(cn1.text.bg, 0.6); show(cn1.text, 0.6); show(spa, 0.5); route(req, cn1, 0.6, smooth); }
par { show(cn2, 0.6); show(cn2.text.bg, 0.6); show(cn2.text, 0.6); show(api, 0.5); route(req, cn2, 0.6, smooth); }
par { show(cn3, 0.7); show(cn3.text.bg, 0.7); show(cn3.text, 0.7); show(cdb, 0.5); route(req, cn3, 0.7, smooth); }
par { show(cmf, 0.5); show(cn4, 0.6); show(cn4.text.bg, 0.6); show(cn4.text, 0.6); }
}
}
wait(0.5);
// 4 — push into the API container
step("into-api") {
par {
say(cap, "Zoom into the API…", 0.4);
zoom(2.4, 1.1, smooth);
fade(ccust, 0.6); fade(spa, 0.6); fade(cdb, 0.6); fade(cmf, 0.6);
fade(cnt.connections, 0.6); fade(req.parts, 0.4);
}
}
wait(0.2);
// 5 — inside the API, the components ASSEMBLE as the sign-in call travels.
step("components") {
seq {
par {
say(cap, "…inside, a sign-in call builds the components it touches.", 0.4);
fade(api, 0.4);
zoom(1.0, 1.0, smooth);
}
par { show(signin, 0.4); show(req2.parts, 0.25); }
par { show(cp1, 0.6); show(cp1.text.bg, 0.6); show(cp1.text, 0.6); show(sec, 0.5); route(req2, cp1, 0.6, smooth); }
par { show(cp3, 0.7); show(cp3.text.bg, 0.7); show(cp3.text, 0.7); show(mdb, 0.5); route(req2, cp3, 0.7, smooth); }
par {
show(acct, 0.5); show(fac, 0.5); show(mmf, 0.5);
show(cp2, 0.6); show(cp2.text.bg, 0.6); show(cp2.text, 0.6);
show(cp4, 0.6); show(cp4.text.bg, 0.6); show(cp4.text, 0.6);
}
}
}
wait(0.5);
// 8 — climb back to the containers (fade components out, pull back, reveal)
step("back-to-containers") {
seq {
par {
say(cap, "Zoom back out to the containers…", 0.4);
fade(cmp.nodes, 0.4); fade(cmp.connections, 0.4); fade(req2.parts, 0.3);
zoom(0.8, 0.5, smooth);
}
par {
zoom(1.0, 0.6, smooth);
show(ccust, 0.5); show(spa, 0.5); show(api, 0.5); show(cdb, 0.5); show(cmf, 0.5);
show(cnt.connections, 0.5);
}
}
}
wait(0.4);
// 9 — and out to the whole system in its world
step("back-to-context") {
seq {
par {
say(cap, "…and out to the whole system, at every altitude. That is manic.", 0.5);
fade(ccust, 0.4); fade(spa, 0.4); fade(api, 0.4); fade(cdb, 0.4); fade(cmf, 0.4);
fade(cnt.connections, 0.4);
zoom(0.8, 0.5, smooth);
}
par {
zoom(1.0, 0.6, smooth);
show(cust, 0.6); show(sys, 0.6); show(mail, 0.6); show(core, 0.6); show(ctx.connections, 0.6);
}
}
}
wait(1.6);
c4-test
The canonical bigbank Container diagram, translated straight from the Python diagrams library. Five containers auto-split into a balanced grid, and the long notification edge from the e-mail system back to the customer routes around the margin instead of bisecting the diagram — dense C4, still readable.
title("Container diagram — Internet Banking System");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.055), "DIAGRAMS · C4 · CONTAINER");
text(headline, (cx, h*0.12), "Container diagram for Internet Banking System");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 22*u); bold(headline); wrap(headline, w*0.86);
// One c4 container diagram. Tiers do the grouping — person on top, the internal
// containers in the middle (they auto-split into balanced sub-rows), external
// systems on the bottom. Declaration order = left→right / row-major, so keep
// related boxes adjacent (web beside spa, spa's path down to api beside db).
c4(ib, container);
// person — top tier
node(cust, ib, "person", "Personal Banking Customer", "A customer of the bank, with personal bank accounts");
// internal containers — middle tier
node(web, ib, "container", "Web Application", "Delivers static content and the Internet banking SPA", "Java / Spring MVC");
node(spa, ib, "container", "Single-Page Application", "Internet banking in the web browser", "JavaScript / Angular");
node(mob, ib, "container", "Mobile App", "A subset of the banking functionality", "Xamarin");
node(api, ib, "container", "API Application", "Banking functionality via a JSON/HTTPS API", "Java / Spring MVC");
node(db, ib, "container", "Database", "Stores users, credentials and access logs", "Oracle");
// external systems — bottom tier
node(email, ib, "external", "E-mail System", "The internal Microsoft Exchange system");
node(mf, ib, "external", "Mainframe Banking System", "Core banking: customers, accounts, transactions");
connect(e1, cust, web); annotate(e1, "Visits bigbank.com/ib [HTTPS]");
connect(e2, cust, spa); annotate(e2, "Views balances, makes payments");
connect(e3, cust, mob); annotate(e3, "Views balances, makes payments");
connect(e4, web, spa); annotate(e4, "Delivers to the browser");
connect(e5, spa, api); annotate(e5, "API calls [JSON/HTTPS]");
connect(e6, mob, api); annotate(e6, "API calls [JSON/HTTPS]");
connect(e7, api, db); annotate(e7, "Reads/writes [JDBC]");
connect(e8, api, email); annotate(e8, "Sends e-mail [SMTP]");
connect(e9, api, mf); annotate(e9, "API calls [XML/HTTPS]");
connect(e10, email, cust); annotate(e10, "Sends e-mails to");
color(e1, cyan); color(e1.hot, cyan);
color(e2, cyan); color(e2.hot, cyan);
color(e3, cyan); color(e3.hot, cyan);
color(e4, cyan); color(e4.hot, cyan);
color(e5, cyan); color(e5.hot, cyan);
color(e6, cyan); color(e6.hot, cyan);
color(e7, cyan); color(e7.hot, cyan);
color(e8, gold); color(e8.hot, gold);
color(e9, gold); color(e9.hot, gold);
color(e10, gold); color(e10.hot, gold);
systems-rabbitmq-consumers
Authored one-of-many delivery: messages 101/102/103 explicitly select different workers — the kit never infers RabbitMQ or round-robin behaviour.
title("RabbitMQ Consumers — One Message, One Worker");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.14, h*0.035), "Made With Manic");
text(kicker, (cx, h*0.055), "SYSTEMS KIT · COMPETING CONSUMERS");
text(headline, (cx, h*0.125), "One queue. Three workers. One owner per message.");
text(caption, (cx, h*0.94), "Cold paths show every legal route; the moving job shows the route selected now.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);
if h > 1.25*w {
architecture(runtime, (cx, h*0.52), w*0.84, h*0.70);
}
else {
architecture(runtime, (cx, h*0.52), w*0.90, h*0.68);
}
node(queue, runtime, "queue", "RabbitMQ Queue");
cluster(consumers, runtime, "RABBITMQ CONSUMERS");
node(worker1, consumers, "service", "Pod Worker 1");
node(worker2, consumers, "service", "Pod Worker 2");
node(worker3, consumers, "service", "Pod Worker 3");
node(database, runtime, "database", "Database");
// Every lane is structural possibility. Explicit ports keep the upper and
// lower routes orthogonal without changing the semantic connection identity.
connect(queueToWorker1, queue, worker1, orthogonal, right, left);
connect(queueToWorker2, queue, worker2);
connect(queueToWorker3, queue, worker3, orthogonal, right, left);
connect(worker1ToDatabase, worker1, database, orthogonal, right, left);
connect(worker2ToDatabase, worker2, database);
connect(worker3ToDatabase, worker3, database, orthogonal, right, left);
message(job101, queue, "101");
message(job102, queue, "102");
message(job103, queue, "103");
hidden(kicker); hidden(headline); hidden(caption);
hidden(runtime.nodes); hidden(consumers.parts);
hidden(job101.parts); hidden(job102.parts); hidden(job103.parts);
untraced(runtime.connections);
step("queue-and-consumer-pool") {
par {
show(kicker, 0.30); show(headline, 0.45); show(caption, 0.40);
show(queue, 0.35);
show(consumers.parts, 0.45);
stagger(0.10) {
show(worker1, 0.32);
show(worker2, 0.32);
show(worker3, 0.32);
}
say(caption, "The queue and three workers are structure only. No delivery policy is inferred from their icons.", 0.40);
}
}
wait(0.25);
step("shared-database") {
par {
show(database, 0.38);
draw(runtime.connections, 1.10, smooth);
say(caption, "Each worker can consume from RabbitMQ and write its result to the same database.", 0.40);
}
}
wait(0.35);
step("message-101-selects-worker-2") {
par {
seq {
show(job101.parts, 0.18);
route(job101, queueToWorker2, 0.90, linear);
route(job101, worker2ToDatabase, 0.90, linear);
}
say(caption, "Message 101 is delivered to Worker 2 only. The upper and lower consumer routes stay cold.", 0.40);
}
}
wait(0.35);
step("message-102-selects-worker-1") {
par {
fade(job101.parts, 0.20);
fade(queueToWorker2.hot, 0.25); fade(worker2ToDatabase.hot, 0.25);
seq {
show(job102.parts, 0.18);
route(job102, queueToWorker1, 1.05, linear);
route(job102, worker1ToDatabase, 1.05, linear);
}
say(caption, "The next message can select Worker 1. Orthogonal turns preserve the same moving identity.", 0.40);
}
}
wait(0.35);
step("message-103-selects-worker-3") {
par {
fade(job102.parts, 0.20);
fade(queueToWorker1.hot, 0.25); fade(worker1ToDatabase.hot, 0.25);
seq {
show(job103.parts, 0.18);
route(job103, queueToWorker3, 1.05, linear);
route(job103, worker3ToDatabase, 1.05, linear);
}
say(caption, "Message 103 selects Worker 3: three jobs were shared, never broadcast as three copies.", 0.40);
}
}
wait(0.45);
step("takeaway") {
par {
pulse(queue, 0.65); pulse(consumers.parts, 0.75); pulse(database, 0.65);
say(caption, "Manic does not guess RabbitMQ semantics—the creator explicitly authors each selected delivery.", 0.45);
}
}
wait(1.30);
aws-three-tier-web-application
Presentation / Application / Data as three responsive tiers; one request travels Route 53 → CloudFront → ELB → ECS → ElastiCache → RDS.
title("Designing and Building a Three-Tier Web Application Architecture on AWS");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "AWS ARCHITECTURE · THREE TIERS");
text(headline, (cx, h*0.145), "Designing a Three-Tier Web Application");
text(caption, (cx, h*0.91), "Each tier has one responsibility—and the request keeps moving.");
size(kicker, 17*u); color(kicker, dim); bold(kicker);
size(headline, 32*u); bold(headline); wrap(headline, w*0.86);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.84);
if h > 1.25*w {
architecture(presentation, (cx, h*0.30), w*0.72, h*0.18);
architecture(application, (cx, h*0.52), w*0.72, h*0.18);
architecture(dataTier, (cx, h*0.74), w*0.72, h*0.18);
text(presentationLabel, (w*0.13, h*0.30), "1 · PRESENTATION");
text(applicationLabel, (w*0.13, h*0.52), "2 · APPLICATION");
text(dataLabel, (w*0.13, h*0.74), "3 · DATA");
}
else {
architecture(presentation, (w*0.20, h*0.50), w*0.27, h*0.55);
architecture(application, (w*0.50, h*0.50), w*0.27, h*0.55);
architecture(dataTier, (w*0.80, h*0.50), w*0.27, h*0.55);
text(presentationLabel, (w*0.20, h*0.235), "1 · PRESENTATION");
text(applicationLabel, (w*0.50, h*0.235), "2 · APPLICATION");
text(dataLabel, (w*0.80, h*0.235), "3 · DATA");
}
size(presentationLabel, 16*u); color(presentationLabel, cyan); bold(presentationLabel);
size(applicationLabel, 16*u); color(applicationLabel, magenta); bold(applicationLabel);
size(dataLabel, 16*u); color(dataLabel, gold); bold(dataLabel);
node(dns, presentation, "aws:route53", "Route 53");
node(cdn, presentation, "aws:cloudfront", "CloudFront");
node(balancer, application, "aws:elb", "Load Balancer");
node(app, application, "aws:ecs", "ECS Service");
node(cache, dataTier, "aws:elasticache", "ElastiCache");
node(database, dataTier, "aws:rds", "Amazon RDS");
connect(resolvePath, dns, cdn);
connect(toApplication, cdn, balancer);
connect(toService, balancer, app);
connect(toCache, app, cache);
connect(toDatabase, cache, database);
request(webRequest, dns, "GET /checkout");
hidden(presentation.nodes); hidden(application.nodes); hidden(dataTier.nodes);
untraced(resolvePath); untraced(toApplication); untraced(toService);
untraced(toCache); untraced(toDatabase);
hidden(webRequest.parts);
step("three-responsibilities") {
par {
show(presentation.nodes, 0.55);
say(caption, "Presentation receives users, resolves DNS, and serves content from the edge.", 0.40);
}
}
wait(0.20);
step("application-tier") {
par {
show(application.nodes, 0.55);
say(caption, "Application distributes traffic and runs stateless business logic.", 0.40);
}
}
wait(0.20);
step("data-tier") {
par {
show(dataTier.nodes, 0.55);
say(caption, "Data accelerates repeated reads and persists durable records.", 0.40);
}
}
wait(0.25);
step("connect-the-tiers") {
par {
stagger(0.10) {
draw(resolvePath, 0.35); draw(toApplication, 0.35); draw(toService, 0.35);
draw(toCache, 0.35); draw(toDatabase, 0.35);
}
show(webRequest.parts, 0.30);
say(caption, "Now follow one checkout request through the complete design.", 0.40);
}
}
wait(0.20);
step("presentation-flow") {
par {
route(webRequest, resolvePath, 0.80, smooth);
say(caption, "Route 53 resolves the domain; CloudFront brings the experience closer to users.", 0.40);
}
}
step("load-balance") {
par {
route(webRequest, toApplication, 0.95, smooth);
say(caption, "The load balancer is the controlled entry into the application tier.", 0.40);
}
}
step("run-the-application") {
par {
route(webRequest, toService, 0.80, smooth);
say(caption, "ECS runs replicated application tasks without mixing them with stored data.", 0.40);
}
}
step("read-fast") {
par {
route(webRequest, toCache, 0.95, smooth);
say(caption, "ElastiCache answers hot reads quickly and protects the database from repeated work.", 0.40);
}
}
step("persist-safely") {
par {
route(webRequest, toDatabase, 0.80, smooth);
say(caption, "Amazon RDS remains the durable source of truth for the transaction.", 0.40);
}
}
wait(0.30);
step("takeaway") {
par {
pulse(presentation.nodes, 0.70);
pulse(application.nodes, 0.70);
pulse(dataTier.nodes, 0.70);
say(caption, "Three tiers isolate delivery, computation, and state—so each can scale and evolve independently.", 0.45);
}
}
wait(1.40);
aws-event-processing-clusters-poc
Nested clusters + parallel topology: EKS → three ECS workers → SQS → three Lambda processors → S3 & Redshift, with one seeded hot path through the fan-out.
title("Event Processing on AWS — From Topology to Hot Path");
canvas("16:9");
template("mono");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.055), "AWS ARCHITECTURE · EVENT PROCESSING");
text(headline, (cx, h*0.115), "One topology. One event. One visible hot path.");
text(caption, (cx, h*0.935), "Dashed relationships show every route the architecture allows.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);
if h > 1.25*w {
architecture(eventProcessing, (cx, h*0.51), w*0.82, h*0.70);
}
else {
architecture(eventProcessing, (cx, h*0.51), w*0.92, h*0.69);
}
node(source, eventProcessing, "aws:eks", "K8s Source");
cluster(flows, eventProcessing, "EVENT FLOWS");
cluster(workers, flows, "EVENT WORKERS");
node(worker1, workers, "aws:ecs", "Worker 1");
node(worker2, workers, "aws:ecs", "Worker 2");
node(worker3, workers, "aws:ecs", "Worker 3");
node(queue, flows, "aws:sqs", "Event Queue");
cluster(processing, flows, "PROCESSING");
node(proc1, processing, "aws:lambda", "Processor 1");
node(proc2, processing, "aws:lambda", "Processor 2");
node(proc3, processing, "aws:lambda", "Processor 3");
node(store, eventProcessing, "aws:s3", "Events Store");
node(warehouse, eventProcessing, "aws:redshift", "Analytics");
connect(toWorkers, source, workers);
connect(toQueue, workers, queue);
connect(toHandlers, queue, processing);
connect(toStore, processing, store);
connect(toAnalytics, processing, warehouse);
message(event, source, "EVENT");
hidden(eventProcessing.nodes);
hidden(flows.parts); hidden(workers.parts); hidden(processing.parts);
hidden(event.parts);
untraced(toWorkers); untraced(toQueue); untraced(toHandlers);
untraced(toStore); untraced(toAnalytics);
step("source") {
par {
show(source, 0.45);
say(caption, "EKS is the source; the rest of the system is still only structure.", 0.40);
}
}
wait(0.25);
step("ownership") {
par {
show(flows.parts, 0.50);
show(workers.parts, 0.45);
stagger(0.10) { show(worker1, 0.35); show(worker2, 0.35); show(worker3, 0.35); }
show(queue, 0.40);
show(processing.parts, 0.45);
stagger(0.10) { show(proc1, 0.35); show(proc2, 0.35); show(proc3, 0.35); }
show(store, 0.40); show(warehouse, 0.40);
say(caption, "Nested boundaries reveal ownership without hiding the individual services.", 0.40);
}
}
wait(0.20);
step("possible-topology") {
par {
draw(toWorkers, 0.55);
draw(toQueue, 0.55);
draw(toHandlers, 0.55);
draw(toStore, 0.55); draw(toAnalytics, 0.55);
say(caption, "Every dashed edge is a possible relationship—not a claim that data is flowing.", 0.40);
}
}
wait(0.35);
step("runtime-hot-path") {
par {
show(event.parts, 0.25);
hotpath(event, 6.20, 27);
say(caption, "The event now chooses valid branches and travels end-to-end as one continuous hot path.", 0.40);
}
}
wait(0.35);
step("takeaway") {
par {
pulse(event.parts, 0.70);
say(caption, "Cold topology explains relationships. The moving dot proves what happened now.", 0.45);
}
}
wait(1.40);
aws-clustered-web-services
Route 53 → ELB → ECS pool with an RDS primary/replica cluster and ElastiCache; request and response follow separately coloured lanes.
title("Clustered Web Services on AWS");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.025), "Made With Manic");
text(kicker, (cx, h*0.055), "AWS ARCHITECTURE · CLUSTERED WEB SERVICES");
text(headline, (cx, h*0.115), "Requests in. Responses back.");
text(caption, (cx, h*0.935), "Dashed paths describe what is connected—not what is moving now.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);
if h > 1.25*w {
architecture(clusteredWeb, (cx, h*0.51), w*0.84, h*0.70);
text(requestKey, (w*0.36, h*0.225), "REQUEST");
arrow(requestSample, (w*0.48, h*0.225), (w*0.64, h*0.225));
text(responseKey, (w*0.36, h*0.265), "RESPONSE");
arrow(responseSample, (w*0.48, h*0.265), (w*0.64, h*0.265));
}
else {
architecture(clusteredWeb, (cx, h*0.51), w*0.94, h*0.69);
text(requestKey, (w*0.105, h*0.205), "REQUEST");
arrow(requestSample, (w*0.15, h*0.205), (w*0.205, h*0.205));
text(responseKey, (w*0.105, h*0.245), "RESPONSE");
arrow(responseSample, (w*0.15, h*0.245), (w*0.205, h*0.245));
}
size(requestKey, 15*u); color(requestKey, fg); bold(requestKey);
size(responseKey, 15*u); hue(responseKey, 328, 0.90, 0.62); bold(responseKey);
color(requestSample, fg); stroke(requestSample, 3*u);
hue(responseSample, 328, 0.90, 0.62); dashed(responseSample, 10*u, 7*u);
stroke(responseSample, 4*u); glow(responseSample, 0.85);
hidden(requestKey); hidden(requestSample);
hidden(responseKey); hidden(responseSample);
node(dns, clusteredWeb, "aws:route53", "dns");
node(lb, clusteredWeb, "aws:elb", "lb");
cluster(services, clusteredWeb, "SERVICES");
node(web1, services, "aws:ecs", "web1");
node(web2, services, "aws:ecs", "web2");
node(web3, services, "aws:ecs", "web3");
cluster(database, clusteredWeb, "DB CLUSTER");
node(dbPrimary, database, "aws:rds", "userdb");
node(dbReplica, database, "aws:rds", "userdb ro");
node(memcached, clusteredWeb, "aws:elasticache", "memcached");
connect(dnsToLb, dns, lb);
connect(lbToServices, lb, services);
connect(servicesToDb, services, dbPrimary);
connect(servicesToCache, services, memcached, 145*u);
connect(dbToServices, dbPrimary, services, 42*u);
connect(servicesToLb, services, lb, 42*u);
connect(cacheToWeb2, memcached, web2, 145*u);
hue(dbToServices.hot, 328, 0.90, 0.62);
hue(servicesToLb.hot, 328, 0.90, 0.62);
hue(cacheToWeb2.hot, 328, 0.90, 0.62);
dashed(dbToServices.hot, 10*u, 7*u);
dashed(servicesToLb.hot, 10*u, 7*u);
dashed(cacheToWeb2.hot, 10*u, 7*u);
stroke(dbToServices.hot, 4.5*u); glow(dbToServices.hot, 0.95);
stroke(servicesToLb.hot, 4.5*u); glow(servicesToLb.hot, 0.95);
stroke(cacheToWeb2.hot, 4.5*u); glow(cacheToWeb2.hot, 0.95);
link(replication, dbPrimary.card, dbReplica.card);
dashed(replication); color(replication, dim); stroke(replication, 2.5*u);
message(pageRequest, dns, "GET");
message(cacheRead, web2, "READ");
hidden(clusteredWeb.nodes);
hidden(services.parts); hidden(database.parts);
hidden(pageRequest.parts); hidden(cacheRead.parts);
untraced(dnsToLb); untraced(lbToServices);
untraced(servicesToDb); untraced(servicesToCache);
untraced(dbToServices); untraced(servicesToLb); untraced(cacheToWeb2);
untraced(replication);
step("entry") {
par {
show(dns, 0.40);
show(lb, 0.40);
say(caption, "Route 53 and the load balancer form the public entry into the design.", 0.40);
}
}
wait(0.20);
step("service-pool") {
par {
show(services.parts, 0.45);
stagger(0.10) {
show(web1, 0.35);
show(web2, 0.35);
show(web3, 0.35);
}
say(caption, "Three ECS services share one ownership boundary; no runtime choice is implied yet.", 0.40);
}
}
wait(0.20);
step("state") {
par {
show(database.parts, 0.45);
show(dbPrimary, 0.40);
show(dbReplica, 0.40);
show(memcached, 0.40);
say(caption, "The database cluster and cache complete the static architecture.", 0.40);
}
}
wait(0.20);
step("possible-topology") {
par {
draw(dnsToLb, 0.40);
draw(lbToServices, 0.60);
draw(servicesToDb, 0.60);
draw(servicesToCache, 0.60);
draw(replication, 0.45);
show(requestKey, 0.35); show(requestSample, 0.35);
show(responseKey, 0.35); show(responseSample, 0.35);
say(caption, "Every dashed lane is possible. The next moving object will identify the path actually used.", 0.40);
}
}
wait(0.30);
step("request-to-database") {
par {
seq {
show(pageRequest.parts, 0.20);
route(pageRequest, dnsToLb, 0.65, smooth);
route(pageRequest, lbToServices, 0.90, smooth);
route(pageRequest, servicesToDb, 0.90, smooth);
}
say(caption, "This request follows one authored lane: dns → lb → web1 → userdb.", 0.40);
}
}
wait(0.35);
step("database-response") {
par {
seq {
par {
say(pageRequest.label, "200 OK", 0.25);
to(pageRequest.parts, hue, 328, 0.25, smooth);
}
route(pageRequest, dbToServices, 0.90, smooth);
route(pageRequest, servicesToLb, 0.90, smooth);
}
say(caption, "The same identity returns userdb → web1 → lb. DNS resolved the name; it is not the HTTP response path.", 0.40);
}
}
wait(0.35);
step("cache-round-trip") {
par {
seq {
show(cacheRead.parts, 0.20);
route(cacheRead, servicesToCache, 1.05, smooth);
par {
say(cacheRead.label, "HIT", 0.25);
to(cacheRead.parts, hue, 328, 0.25, smooth);
}
route(cacheRead, cacheToWeb2, 1.05, smooth);
}
say(caption, "A separate cache read travels web2 → memcached, becomes HIT, and returns to that same service.", 0.40);
}
}
wait(0.35);
step("replica-relationship") {
par {
flow(replication, 3.20, both, continuous);
say(caption, "The neutral database link can carry a two-way visual pulse only because the creator asked for it.", 0.40);
}
}
wait(0.35);
step("takeaway") {
par {
pulse(services.parts, 0.70);
pulse(database.parts, 0.70);
say(caption, "Architecture stays readable; objects, paths, timing, and composition tell the runtime story.", 0.45);
}
}
wait(1.40);
gcp-clustered-web-services
The same clustered-web story on Google Cloud: Cloud DNS → Load Balancing → GKE pool → Cloud SQL cluster + Memorystore — a pure provider swap.
title("Clustered Web Services on Google Cloud");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.025), "Made With Manic");
text(kicker, (cx, h*0.055), "GOOGLE CLOUD ARCHITECTURE · CLUSTERED WEB SERVICES");
text(headline, (cx, h*0.115), "Requests in. Responses back.");
text(caption, (cx, h*0.935), "Dashed paths describe what is connected—not what is moving now.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);
if h > 1.25*w {
architecture(clusteredWeb, (cx, h*0.51), w*0.84, h*0.70);
text(requestKey, (w*0.36, h*0.225), "REQUEST");
arrow(requestSample, (w*0.48, h*0.225), (w*0.64, h*0.225));
text(responseKey, (w*0.36, h*0.265), "RESPONSE");
arrow(responseSample, (w*0.48, h*0.265), (w*0.64, h*0.265));
}
else {
architecture(clusteredWeb, (cx, h*0.51), w*0.94, h*0.69);
text(requestKey, (w*0.105, h*0.205), "REQUEST");
arrow(requestSample, (w*0.15, h*0.205), (w*0.205, h*0.205));
text(responseKey, (w*0.105, h*0.245), "RESPONSE");
arrow(responseSample, (w*0.15, h*0.245), (w*0.205, h*0.245));
}
size(requestKey, 15*u); color(requestKey, fg); bold(requestKey);
size(responseKey, 15*u); hue(responseKey, 328, 0.90, 0.62); bold(responseKey);
color(requestSample, fg); stroke(requestSample, 3*u);
hue(responseSample, 328, 0.90, 0.62); dashed(responseSample, 10*u, 7*u);
stroke(responseSample, 4*u); glow(responseSample, 0.85);
hidden(requestKey); hidden(requestSample);
hidden(responseKey); hidden(responseSample);
node(dns, clusteredWeb, "gcp:dns", "cloud dns");
node(lb, clusteredWeb, "gcp:load-balancing", "load balancing");
cluster(services, clusteredWeb, "SERVICES");
node(web1, services, "gcp:kubernetes-engine", "web1");
node(web2, services, "gcp:kubernetes-engine", "web2");
node(web3, services, "gcp:kubernetes-engine", "web3");
cluster(database, clusteredWeb, "DB CLUSTER");
node(dbPrimary, database, "gcp:sql", "userdb");
node(dbReplica, database, "gcp:sql", "userdb ro");
node(cache, clusteredWeb, "gcp:memorystore", "memorystore");
connect(dnsToLb, dns, lb);
connect(lbToServices, lb, services);
connect(servicesToDb, services, dbPrimary);
connect(servicesToCache, services, cache, 145*u);
connect(dbToServices, dbPrimary, services, 42*u);
connect(servicesToLb, services, lb, 42*u);
connect(cacheToWeb2, cache, web2, 145*u);
hue(dbToServices.hot, 328, 0.90, 0.62);
hue(servicesToLb.hot, 328, 0.90, 0.62);
hue(cacheToWeb2.hot, 328, 0.90, 0.62);
dashed(dbToServices.hot, 10*u, 7*u);
dashed(servicesToLb.hot, 10*u, 7*u);
dashed(cacheToWeb2.hot, 10*u, 7*u);
stroke(dbToServices.hot, 4.5*u); glow(dbToServices.hot, 0.95);
stroke(servicesToLb.hot, 4.5*u); glow(servicesToLb.hot, 0.95);
stroke(cacheToWeb2.hot, 4.5*u); glow(cacheToWeb2.hot, 0.95);
link(replication, dbPrimary.card, dbReplica.card);
dashed(replication); color(replication, dim); stroke(replication, 2.5*u);
message(pageRequest, dns, "GET");
message(cacheRead, web2, "READ");
hidden(clusteredWeb.nodes);
hidden(services.parts); hidden(database.parts);
hidden(pageRequest.parts); hidden(cacheRead.parts);
untraced(dnsToLb); untraced(lbToServices);
untraced(servicesToDb); untraced(servicesToCache);
untraced(dbToServices); untraced(servicesToLb); untraced(cacheToWeb2);
untraced(replication);
step("entry") {
par {
show(dns, 0.40);
show(lb, 0.40);
say(caption, "Cloud DNS and Cloud Load Balancing form the public entry into the design.", 0.40);
}
}
wait(0.20);
step("service-pool") {
par {
show(services.parts, 0.45);
stagger(0.10) {
show(web1, 0.35);
show(web2, 0.35);
show(web3, 0.35);
}
say(caption, "Three GKE services share one ownership boundary; no runtime choice is implied yet.", 0.40);
}
}
wait(0.20);
step("state") {
par {
show(database.parts, 0.45);
show(dbPrimary, 0.40);
show(dbReplica, 0.40);
show(cache, 0.40);
say(caption, "The Cloud SQL cluster and Memorystore complete the static architecture.", 0.40);
}
}
wait(0.20);
step("possible-topology") {
par {
draw(dnsToLb, 0.40);
draw(lbToServices, 0.60);
draw(servicesToDb, 0.60);
draw(servicesToCache, 0.60);
draw(replication, 0.45);
show(requestKey, 0.35); show(requestSample, 0.35);
show(responseKey, 0.35); show(responseSample, 0.35);
say(caption, "Every dashed lane is possible. The next moving object will identify the path actually used.", 0.40);
}
}
wait(0.30);
step("request-to-database") {
par {
seq {
show(pageRequest.parts, 0.20);
route(pageRequest, dnsToLb, 0.65, smooth);
route(pageRequest, lbToServices, 0.90, smooth);
route(pageRequest, servicesToDb, 0.90, smooth);
}
say(caption, "This request follows one authored lane: dns → lb → web1 → userdb.", 0.40);
}
}
wait(0.35);
step("database-response") {
par {
seq {
par {
say(pageRequest.label, "200 OK", 0.25);
to(pageRequest.parts, hue, 328, 0.25, smooth);
}
route(pageRequest, dbToServices, 0.90, smooth);
route(pageRequest, servicesToLb, 0.90, smooth);
}
say(caption, "The same identity returns userdb → web1 → lb. Cloud DNS resolved the name; it is not the HTTP response path.", 0.40);
}
}
wait(0.35);
step("cache-round-trip") {
par {
seq {
show(cacheRead.parts, 0.20);
route(cacheRead, servicesToCache, 1.05, smooth);
par {
say(cacheRead.label, "HIT", 0.25);
to(cacheRead.parts, hue, 328, 0.25, smooth);
}
route(cacheRead, cacheToWeb2, 1.05, smooth);
}
say(caption, "A separate cache read travels web2 → memorystore, becomes HIT, and returns to that same service.", 0.40);
}
}
wait(0.35);
step("replica-relationship") {
par {
flow(replication, 3.20, both, continuous);
say(caption, "The neutral database link can carry a two-way visual pulse only because the creator asked for it.", 0.40);
}
}
wait(0.35);
step("takeaway") {
par {
pulse(services.parts, 0.70);
pulse(database.parts, 0.70);
say(caption, "Architecture stays readable; objects, paths, timing, and composition tell the runtime story.", 0.45);
}
}
wait(1.40);
gcp-message-collecting
GCP IoT: three IoT Core devices publish to Pub/Sub, and Dataflow fans out to a data lake, a processing branch, and a serverless branch — three-level nested clusters.
title("Message Collecting on Google Cloud — IoT to Data Lake");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.045), "Made With Manic");
text(kicker, (cx, h*0.05), "GOOGLE CLOUD · IOT MESSAGE COLLECTING");
text(headline, (cx, h*0.11), "Devices publish once. The pipeline fans out.");
text(caption, (cx, h*0.94), "Dashed relationships show every route the architecture allows.");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 26*u); bold(headline); wrap(headline, w*0.88);
size(caption, 17*u); color(caption, dim); wrap(caption, w*0.86);
architecture(collect, (cx, h*0.52), w*0.96, h*0.72);
cluster(sourceData, collect, "SOURCE OF DATA");
node(core1, sourceData, "gcp:iot-core", "core1");
node(core2, sourceData, "gcp:iot-core", "core2");
node(core3, sourceData, "gcp:iot-core", "core3");
node(pubsub, collect, "gcp:pubsub", "pubsub");
cluster(targets, collect, "TARGETS");
cluster(dataFlow, targets, "DATA FLOW");
node(df, dataFlow, "gcp:dataflow", "data flow");
cluster(dataLake, targets, "DATA LAKE");
node(bq, dataLake, "gcp:bigquery", "bq");
node(gcs, dataLake, "gcp:storage", "storage");
cluster(eventDriven, targets, "EVENT DRIVEN");
cluster(processing, eventDriven, "PROCESSING");
node(engine, processing, "gcp:app-engine", "engine");
node(bigtable, processing, "gcp:bigtable", "bigtable");
cluster(serverless, eventDriven, "SERVERLESS");
node(func, serverless, "gcp:functions", "func");
node(appengine, serverless, "gcp:app-engine", "appengine");
// --- topology ---
connect(toPubsub, sourceData, pubsub); // [core1,core2,core3] >> pubsub
connect(toFlow, pubsub, df); // pubsub >> flow
connect(toBq, df, bq); // flow >> bq
connect(toGcs, df, gcs); // flow >> storage
connect(toEngine, df, engine); // flow >> engine
connect(engineToBt, engine, bigtable); // engine >> bigtable
connect(toFunc, df, func); // flow >> func
connect(funcToApp, func, appengine); // func >> appengine
// --- colour every path by its RELATIONSHIP (cold dashed line + hot overlay) ---
color(toPubsub, cyan); color(toPubsub.hot, cyan);
color(toFlow, cyan); color(toFlow.hot, cyan);
color(toBq, gold); color(toBq.hot, gold);
color(toGcs, gold); color(toGcs.hot, gold);
color(toEngine, magenta); color(toEngine.hot, magenta);
color(engineToBt, magenta);color(engineToBt.hot, magenta);
color(toFunc, lime); color(toFunc.hot, lime);
color(funcToApp, lime); color(funcToApp.hot, lime);
// legend
text(legIn, (w*0.22, h*0.175), "INGEST"); size(legIn, 13); color(legIn, cyan); bold(legIn);
text(legLake, (w*0.36, h*0.175), "DATA LAKE"); size(legLake, 13); color(legLake, gold); bold(legLake);
text(legProc, (w*0.54, h*0.175), "PROCESSING"); size(legProc, 13); color(legProc, magenta); bold(legProc);
text(legSls, (w*0.72, h*0.175), "SERVERLESS"); size(legSls, 13); color(legSls, lime); bold(legSls);
hidden(legIn); hidden(legLake); hidden(legProc); hidden(legSls);
message(msg, pubsub, "msg");
hidden(collect.nodes);
hidden(sourceData.parts); hidden(targets.parts); hidden(dataFlow.parts);
hidden(dataLake.parts); hidden(eventDriven.parts); hidden(processing.parts); hidden(serverless.parts);
hidden(msg.parts);
untraced(toPubsub); untraced(toFlow); untraced(toBq); untraced(toGcs);
untraced(toEngine); untraced(engineToBt); untraced(toFunc); untraced(funcToApp);
step("devices") {
par {
show(sourceData.parts, 0.45);
stagger(0.10) { show(core1, 0.35); show(core2, 0.35); show(core3, 0.35); }
show(pubsub, 0.40);
say(caption, "Three IoT Core devices publish into one Pub/Sub topic.", 0.40);
}
}
wait(0.20);
step("targets") {
par {
show(targets.parts, 0.5);
show(dataFlow.parts, 0.4); show(df, 0.4);
show(dataLake.parts, 0.4); show(bq, 0.4); show(gcs, 0.4);
show(eventDriven.parts, 0.4);
show(processing.parts, 0.4); show(engine, 0.4); show(bigtable, 0.4);
show(serverless.parts, 0.4); show(func, 0.4); show(appengine, 0.4);
say(caption, "Dataflow is the hub; targets nest into a data lake, a processing branch, and a serverless branch.", 0.40);
}
}
wait(0.25);
step("possible-topology") {
par {
stagger(0.07) {
draw(toPubsub, 0.4); draw(toFlow, 0.4); draw(toBq, 0.4); draw(toGcs, 0.4);
draw(toEngine, 0.4); draw(engineToBt, 0.4); draw(toFunc, 0.4); draw(funcToApp, 0.4);
}
show(legIn, 0.3); show(legLake, 0.3); show(legProc, 0.3); show(legSls, 0.3);
say(caption, "Every dashed edge is a possible relationship—not a claim that data is flowing.", 0.40);
}
}
wait(0.30);
step("collect") {
par {
seq {
show(msg.parts, 0.2);
route(msg, toFlow, 0.9, smooth);
}
say(caption, "A published message reaches Dataflow through Pub/Sub.", 0.40);
}
}
wait(0.25);
step("fan-out") {
par {
flow(toBq, 1.0); flow(toGcs, 1.0);
seq { flow(toEngine, 0.9); flow(engineToBt, 0.9); }
seq { flow(toFunc, 0.9); flow(funcToApp, 0.9); }
say(caption, "Dataflow fans out: BigQuery + storage (lake), App Engine → Bigtable, and Functions → App Engine.", 0.40);
}
}
wait(0.30);
step("takeaway") {
par {
pulse(targets.parts, 0.70);
say(caption, "One publish; many destinations—each branch owns its own runtime and storage.", 0.45);
}
}
wait(1.40);
k8s-three-tier
A three-tier app on Kubernetes: Ingress → web Deployment → api Service/Deployment → Redis + a PostgreSQL StatefulSet, following one checkout request.
title("A Three-Tier Application on Kubernetes — Following One Request");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "KUBERNETES · THREE TIERS · ONE DATA FLOW");
text(headline, (cx, h*0.145), "A Three-Tier App on Kubernetes");
text(caption, (cx, h*0.91), "Ingress in, pods compute, state persists — and the request keeps moving.");
size(kicker, 17*u); color(kicker, dim); bold(kicker);
size(headline, 32*u); bold(headline); wrap(headline, w*0.86);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.84);
if h > 1.25*w {
architecture(presentation, (cx, h*0.30), w*0.72, h*0.18);
architecture(application, (cx, h*0.52), w*0.72, h*0.18);
architecture(dataTier, (cx, h*0.74), w*0.72, h*0.18);
text(presentationLabel, (w*0.13, h*0.30), "1 · PRESENTATION");
text(applicationLabel, (w*0.13, h*0.52), "2 · APPLICATION");
text(dataLabel, (w*0.13, h*0.74), "3 · DATA");
}
else {
architecture(presentation, (w*0.20, h*0.50), w*0.27, h*0.55);
architecture(application, (w*0.50, h*0.50), w*0.27, h*0.55);
architecture(dataTier, (w*0.80, h*0.50), w*0.27, h*0.55);
text(presentationLabel, (w*0.20, h*0.235), "1 · PRESENTATION");
text(applicationLabel, (w*0.50, h*0.235), "2 · APPLICATION");
text(dataLabel, (w*0.80, h*0.235), "3 · DATA");
}
size(presentationLabel, 16*u); color(presentationLabel, cyan); bold(presentationLabel);
size(applicationLabel, 16*u); color(applicationLabel, magenta); bold(applicationLabel);
size(dataLabel, 16*u); color(dataLabel, gold); bold(dataLabel);
node(ingress, presentation, "k8s:ing", "Ingress");
node(web, presentation, "k8s:deploy", "web Deployment x3");
node(apisvc, application, "k8s:svc", "api Service");
node(api, application, "k8s:deploy", "api Deployment x3");
node(cache, dataTier, "onprem:redis", "Redis");
node(database, dataTier, "onprem:postgresql", "PostgreSQL (StatefulSet)");
connect(toWeb, ingress, web);
connect(toApiSvc, web, apisvc);
connect(toApi, apisvc, api);
connect(toCache, api, cache);
connect(toDatabase, cache, database);
request(userRequest, ingress, "GET /checkout");
// --- colour every path by its RELATIONSHIP (cold dashed line + hot overlay) ---
color(toWeb, cyan); color(toWeb.hot, cyan);
color(toApiSvc, cyan); color(toApiSvc.hot, cyan);
color(toApi, cyan); color(toApi.hot, cyan);
color(toCache, magenta); color(toCache.hot, magenta);
color(toDatabase, gold); color(toDatabase.hot, gold);
// legend
text(legReq, (w*0.30, h*0.175), "REQUEST"); size(legReq, 13); color(legReq, cyan); bold(legReq);
text(legCache, (w*0.48, h*0.175), "CACHE"); size(legCache, 13); color(legCache, magenta); bold(legCache);
text(legDb, (w*0.62, h*0.175), "PERSIST"); size(legDb, 13); color(legDb, gold); bold(legDb);
hidden(legReq); hidden(legCache); hidden(legDb);
hidden(presentation.nodes); hidden(application.nodes); hidden(dataTier.nodes);
untraced(toWeb); untraced(toApiSvc); untraced(toApi);
untraced(toCache); untraced(toDatabase);
hidden(userRequest.parts);
step("presentation-tier") {
par {
show(presentation.nodes, 0.55);
say(caption, "Presentation: the Ingress terminates traffic and routes it to the web pods.", 0.40);
}
}
wait(0.20);
step("application-tier") {
par {
show(application.nodes, 0.55);
say(caption, "Application: a Service load-balances across stateless api pods that scale on demand.", 0.40);
}
}
wait(0.20);
step("data-tier") {
par {
show(dataTier.nodes, 0.55);
say(caption, "Data: Redis serves hot reads; a PostgreSQL StatefulSet holds durable state.", 0.40);
}
}
wait(0.25);
step("connect-the-tiers") {
par {
stagger(0.10) {
draw(toWeb, 0.35); draw(toApiSvc, 0.35); draw(toApi, 0.35);
draw(toCache, 0.35); draw(toDatabase, 0.35);
}
show(userRequest.parts, 0.30);
show(legReq, 0.3); show(legCache, 0.3); show(legDb, 0.3);
say(caption, "Now follow one checkout request through the whole cluster.", 0.40);
}
}
wait(0.20);
step("ingress-to-web") {
par {
route(userRequest, toWeb, 0.85, smooth);
say(caption, "The Ingress forwards the request to a web pod via the Service.", 0.40);
}
}
step("web-to-api") {
par {
route(userRequest, toApiSvc, 0.90, smooth);
say(caption, "The web tier calls the api Service — one controlled entry into business logic.", 0.40);
}
}
step("into-a-pod") {
par {
route(userRequest, toApi, 0.80, smooth);
say(caption, "The Service load-balances into one replicated api pod.", 0.40);
}
}
step("read-fast") {
par {
route(userRequest, toCache, 0.90, smooth);
say(caption, "Redis answers the hot read first, shielding the database from repeated work.", 0.40);
}
}
step("persist-safely") {
par {
route(userRequest, toDatabase, 0.85, smooth);
say(caption, "On a miss, the PostgreSQL StatefulSet is the durable source of truth.", 0.40);
}
}
wait(0.30);
step("takeaway") {
par {
pulse(presentation.nodes, 0.70);
pulse(application.nodes, 0.70);
pulse(dataTier.nodes, 0.70);
say(caption, "Ingress, Services, Deployments and a StatefulSet keep delivery, compute, and state independent.", 0.45);
}
}
wait(1.40);
k8s-stateful-architecture
A StatefulSet’s storage: a Service, three pods, their PVCs, and the PV/StorageClass that provision them — generated with a for loop, coloured by access/ownership/provisioning.
title("A Stateful Set on Kubernetes — Pods, Claims, and Volumes");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.045), "Made With Manic");
text(kicker, (cx, h*0.05), "KUBERNETES · STATEFUL ARCHITECTURE");
text(headline, (cx, h*0.11), "Every replica keeps its own storage.");
text(caption, (cx, h*0.94), "Dashed relationships show every route the architecture allows.");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 26*u); bold(headline); wrap(headline, w*0.88);
size(caption, 17*u); color(caption, dim); wrap(caption, w*0.86);
architecture(stateful, (cx, h*0.52), w*0.94, h*0.72);
cluster(apps, stateful, "APPS");
node(svc, apps, "k8s:svc", "svc");
node(sts, apps, "k8s:sts", "sts");
cluster(pods, stateful, "PODS");
for i in 0..3 {
node(pod{i}, pods, "k8s:pod", "pod");
}
cluster(claims, stateful, "CLAIMS");
for i in 0..3 {
node(pvc{i}, claims, "k8s:pvc", "pvc");
}
cluster(storage, stateful, "STORAGE");
node(pv, storage, "k8s:pv", "pv");
node(sc, storage, "k8s:sc", "sc");
// topology: svc >> pod >> pvc, pod - sts - pvc, and sc >> pv >> pvc (provisioning)
for i in 0..3 {
connect(svcPod{i}, svc, pod{i});
connect(podPvc{i}, pod{i}, pvc{i});
connect(podSts{i}, pod{i}, sts);
connect(stsPvc{i}, sts, pvc{i});
connect(pvPvc{i}, pv, pvc{i});
}
connect(scPv, sc, pv);
// --- colour every path by its RELATIONSHIP (cold dashed line + hot overlay) ---
for i in 0..3 {
color(svcPod{i}, cyan); // ACCESS
color(podPvc{i}, cyan);
color(podSts{i}, magenta); // OWNERSHIP
color(stsPvc{i}, magenta);
color(pvPvc{i}, gold); // PROVISIONING
}
color(scPv, gold); color(scPv.hot, gold);
text(legAcc, (w*0.30, h*0.175), "ACCESS"); size(legAcc, 13); color(legAcc, cyan); bold(legAcc);
text(legOwn, (w*0.46, h*0.175), "OWNERSHIP"); size(legOwn, 13); color(legOwn, magenta); bold(legOwn);
text(legProv, (w*0.64, h*0.175), "PROVISIONING"); size(legProv, 13); color(legProv, gold); bold(legProv);
hidden(legAcc); hidden(legOwn); hidden(legProv);
hidden(stateful.nodes);
hidden(apps.parts); hidden(pods.parts); hidden(claims.parts); hidden(storage.parts);
for i in 0..3 {
untraced(svcPod{i}); untraced(podPvc{i}); untraced(podSts{i});
untraced(stsPvc{i}); untraced(pvPvc{i});
}
untraced(scPv);
step("apps") {
par {
show(apps.parts, 0.5);
show(svc, 0.4); show(sts, 0.4);
show(pods.parts, 0.4);
stagger(0.08) { show(pod0, 0.35); show(pod1, 0.35); show(pod2, 0.35); }
show(claims.parts, 0.4);
stagger(0.08) { show(pvc0, 0.35); show(pvc1, 0.35); show(pvc2, 0.35); }
say(caption, "A Service fronts three pods; a StatefulSet gives each pod a stable identity and claim.", 0.40);
}
}
wait(0.20);
step("storage") {
par {
show(storage.parts, 0.45);
show(pv, 0.4); show(sc, 0.4);
say(caption, "A StorageClass provisions PersistentVolumes that back each PersistentVolumeClaim.", 0.40);
}
}
wait(0.25);
step("possible-topology") {
par {
stagger(0.05) {
draw(svcPod0, 0.35); draw(svcPod1, 0.35); draw(svcPod2, 0.35);
draw(podPvc0, 0.35); draw(podPvc1, 0.35); draw(podPvc2, 0.35);
draw(podSts0, 0.35); draw(podSts1, 0.35); draw(podSts2, 0.35);
draw(stsPvc0, 0.35); draw(stsPvc1, 0.35); draw(stsPvc2, 0.35);
draw(scPv, 0.35);
draw(pvPvc0, 0.35); draw(pvPvc1, 0.35); draw(pvPvc2, 0.35);
}
show(legAcc, 0.3); show(legOwn, 0.3); show(legProv, 0.3);
say(caption, "Every dashed edge is a relationship: routing, ownership, and storage binding together.", 0.40);
}
}
wait(0.30);
step("app-access") {
par {
stagger(0.12) {
seq { flow(svcPod0, 0.7); flow(podPvc0, 0.7); }
seq { flow(svcPod1, 0.7); flow(podPvc1, 0.7); }
seq { flow(svcPod2, 0.7); flow(podPvc2, 0.7); }
}
say(caption, "Traffic: the Service reaches a pod, which reads and writes its own claim.", 0.40);
}
}
wait(0.25);
step("provisioning") {
par {
seq {
flow(scPv, 0.8);
par { flow(pvPvc0, 0.8); flow(pvPvc1, 0.8); flow(pvPvc2, 0.8); }
}
say(caption, "Provisioning: the StorageClass creates PVs that bind to each claim.", 0.40);
}
}
wait(0.30);
step("takeaway") {
par {
pulse(apps.parts, 0.7);
pulse(storage.parts, 0.7);
say(caption, "Stateful means identity plus durable storage—one claim and volume per replica.", 0.45);
}
}
wait(1.40);
k8s-cluster-components
The canonical Kubernetes components diagram — control plane (api hub, c-m, c-c-m, etcd, sched) + nodes + the cloud provider — drawn AND flowed with plain primitives, no systems kit at all.
title("Kubernetes Cluster Components");
canvas("16:9");
template("neon");
// ---- boxes (drawn first, behind; hollow-ish so icons read on top) ----
rect(clusterBox, (800, 380), 880, 560); outline(clusterBox, dim); opacity(clusterBox, 0.22);
rect(cpBox, (575, 440), 400, 430); outline(cpBox, cyan); opacity(cpBox, 0.28);
rect(node1Box, (1000, 320), 400, 150); outline(node1Box, dim); opacity(node1Box, 0.35);
rect(node2Box, (1000, 520), 400, 150); outline(node2Box, dim); opacity(node2Box, 0.35);
text(clusterLabel, (470, 130), "Kubernetes cluster"); size(clusterLabel, 26); bold(clusterLabel);
text(cpLabel, (490, 250), "Control Plane"); size(cpLabel, 20); color(cpLabel, cyan); bold(cpLabel);
text(n1Label, (855, 268), "Node"); size(n1Label, 18); color(n1Label, dim); bold(n1Label);
text(n2Label, (855, 468), "Node"); size(n2Label, 18); color(n2Label, dim); bold(n2Label);
// ---- cloud provider API (outside the cluster, left) ----
ellipse(cloud, (165, 360), 210, 120); outline(cloud, blue); opacity(cloud, 0.30);
text(cloudLabel, (165, 360), "Cloud provider\nAPI"); size(cloudLabel, 20); color(cloudLabel, blue); bold(cloudLabel);
// ---- control-plane connections (api is the hub) ----
line(lCm, (615, 400), (615, 320)); stroke(lCm, 3);
line(lSched,(615, 485), (615, 560)); stroke(lSched, 3);
line(lEtcd, (590, 465), (505, 500)); stroke(lEtcd, 3);
line(lCcmApi, (505, 375), (580, 425)); stroke(lCcmApi, 3); dashed(lCcmApi, 9, 7);
arrow(aCloud, (440, 360), (270, 360)); stroke(aCloud, 3);
line(lN1, (800, 320), (660, 425)); stroke(lN1, 3);
line(lN2, (800, 520), (660, 450)); stroke(lN2, 3);
// ---- component icons (placed at the reference positions) ----
image(api, (615, 440), "asset:diagrams/k8s/controlplane/api.png", 62, 62);
image(cm, (615, 285), "asset:diagrams/k8s/controlplane/c-m.png", 58, 58);
image(sched, (615, 590), "asset:diagrams/k8s/controlplane/sched.png", 58, 58);
image(ccm, (470, 360), "asset:diagrams/k8s/controlplane/c-c-m.png", 58, 58);
image(etcd, (470, 510), "asset:diagrams/k8s/infra/etcd.png", 58, 58);
image(kubelet1, (890, 335), "asset:diagrams/k8s/controlplane/kubelet.png", 54, 54);
image(kproxy1, (1100, 335), "asset:diagrams/k8s/controlplane/k-proxy.png", 54, 54);
image(kubelet2, (890, 535), "asset:diagrams/k8s/controlplane/kubelet.png", 54, 54);
image(kproxy2, (1100, 535), "asset:diagrams/k8s/controlplane/k-proxy.png", 54, 54);
// ---- icon captions ----
text(tApi, (615, 478), "api"); size(tApi, 15);
text(tCm, (615, 320), "c-m"); size(tCm, 15);
text(tSched, (615, 625), "sched"); size(tSched, 15);
text(tCcm, (470, 395), "c-c-m"); size(tCcm, 15);
text(tEtcd, (470, 545), "etcd"); size(tEtcd, 15);
text(tKl1, (890, 370), "kubelet"); size(tKl1, 14);
text(tKp1, (1100, 370), "k-proxy"); size(tKp1, 14);
text(tKl2, (890, 570), "kubelet"); size(tKl2, 14);
text(tKp2, (1100, 570), "k-proxy"); size(tKp2, 14);
// ---- data flow: ordinary manic verbs on the hand-drawn wires (NO systems kit) ----
text(cap, (cx, h*0.95), "Every component talks through the API server."); size(cap, 20); color(cap, dim); wrap(cap, w*0.8);
dot(packet, (800, 320), 11); color(packet, lime); hidden(packet);
wait(0.5);
par {
flow(lN1, 1.0); flow(lN2, 1.0);
say(cap, "kubelets report node and pod status up to the API server", 0.4);
}
par {
flow(lCm, 1.0); flow(lSched, 1.0);
say(cap, "the scheduler and controllers act only through the API server", 0.4);
}
par {
flow(lEtcd, 1.0);
say(cap, "the API server reads and writes all cluster state in etcd", 0.4);
}
par {
flow(aCloud, 1.0);
say(cap, "the cloud-controller-manager provisions infrastructure via the cloud API", 0.4);
}
wait(0.2);
par {
show(packet, 0.2);
say(cap, "one node update: kubelet -> API server -> etcd", 0.4);
}
seq {
travel(packet, lN1, 0.9, smooth);
travel(packet, lEtcd, 0.9, smooth);
}
pulse(api, 0.7);
wait(1.4);
serverless-processing
A mixed on-prem + AWS pipeline: Kafka + Docker engines → SQS (+ dead-letter) → Lambda → S3/Redshift, with a Fluentd→Kafka→Spark tap; paths coloured by relationship.
title("Serverless Event Processing — From Kafka to Cold Storage");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.055), "SERVERLESS · EVENT PROCESSING · MIXED STACK");
text(headline, (cx, h*0.115), "Events in. Results, dead letters, and cold storage out.");
text(caption, (cx, h*0.935), "Dashed relationships show every route the architecture allows.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 27*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);
if h > 1.25*w {
architecture(pipeline, (cx, h*0.52), w*0.86, h*0.72);
}
else {
architecture(pipeline, (cx, h*0.52), w*0.94, h*0.70);
}
// --- the ingress + event source + a bold analytics tap (on-prem stack) ---
cluster(ingest, pipeline, "INGEST");
node(ingress, ingest, "onprem:fluentd", "logging");
node(source, ingest, "onprem:kafka", "Event-Sourcing");
node(spark, ingest, "onprem:spark", "Spark");
cluster(flows, pipeline, "EVENT FLOWS");
cluster(engines, flows, "DATA PROCESSING ENGINES");
node(clicks, engines, "onprem:docker", "click stream");
node(logins, engines, "onprem:docker", "user logins");
node(analytics, engines, "onprem:docker", "data analytics");
node(queue, flows, "aws:sqs", "results queue");
node(dlq, flows, "aws:sqs", "Dead Letter Queue");
cluster(processing, flows, "PROCESSING");
node(persist, processing, "aws:lambda", "persist results");
cluster(storage, flows, "STORAGE");
node(store, storage, "aws:s3", "cold storage");
node(dw, storage, "aws:redshift", "operational storage");
// --- topology (Mingrammer edges) ---
connect(parse, ingress, source); // ingress >> Edge("parse") >> source
connect(toSpark, source, spark); // source >> Edge(bold) >> Spark
connect(toEngines, source, engines); // source >> workers
connect(toQueue, engines, queue); // workers >> queue
connect(toHandlers, queue, processing); // queue >> handlers
connect(toDlq, queue, dlq, 120*u); // queue >> dlq
connect(toStore, processing, store); // handlers >> store
connect(toDw, processing, dw); // handlers >> dw
// --- colour every path by its RELATIONSHIP (cold dashed line + hot overlay) ---
color(parse, cyan); color(parse.hot, cyan);
color(toEngines, cyan); color(toEngines.hot, cyan);
color(toQueue, cyan); color(toQueue.hot, cyan);
color(toHandlers, cyan); color(toHandlers.hot, cyan);
color(toSpark, magenta); color(toSpark.hot, magenta);
color(toStore, gold); color(toStore.hot, gold);
color(toDw, gold); color(toDw.hot, gold);
color(toDlq, red); color(toDlq.hot, red);
// legend
text(legPipe, (w*0.24, h*0.175), "PIPELINE"); size(legPipe, 13); color(legPipe, cyan); bold(legPipe);
text(legAna, (w*0.40, h*0.175), "ANALYTICS"); size(legAna, 13); color(legAna, magenta); bold(legAna);
text(legStore, (w*0.58, h*0.175), "STORAGE"); size(legStore, 13); color(legStore, gold); bold(legStore);
text(legDlq, (w*0.74, h*0.175), "DEAD-LETTER"); size(legDlq, 13); color(legDlq, red); bold(legDlq);
hidden(legPipe); hidden(legAna); hidden(legStore); hidden(legDlq);
message(event, source, "event");
hidden(pipeline.nodes);
hidden(ingest.parts); hidden(flows.parts); hidden(engines.parts); hidden(processing.parts); hidden(storage.parts);
hidden(event.parts);
untraced(parse); untraced(toSpark); untraced(toEngines); untraced(toQueue);
untraced(toHandlers); untraced(toDlq); untraced(toStore); untraced(toDw);
step("ingest") {
par {
show(ingest.parts, 0.45);
show(ingress, 0.40);
show(source, 0.40);
show(spark, 0.40);
say(caption, "Fluentd parses raw logs into Kafka; a bold tap also streams the source into Spark.", 0.40);
}
}
wait(0.20);
step("event-flows") {
par {
show(flows.parts, 0.50);
show(engines.parts, 0.45);
stagger(0.10) { show(clicks, 0.35); show(logins, 0.35); show(analytics, 0.35); }
show(queue, 0.40); show(dlq, 0.40);
show(processing.parts, 0.45); show(persist, 0.40);
show(storage.parts, 0.45); show(store, 0.40); show(dw, 0.40);
say(caption, "Docker engines process the stream; a queue, a dead-letter queue, a Lambda, and storage complete it.", 0.40);
}
}
wait(0.20);
step("possible-topology") {
par {
stagger(0.08) {
draw(parse, 0.4); draw(toEngines, 0.4); draw(toQueue, 0.4); draw(toHandlers, 0.4);
draw(toDlq, 0.4); draw(toStore, 0.4); draw(toDw, 0.4); draw(toSpark, 0.4);
}
show(legPipe, 0.3); show(legAna, 0.3); show(legStore, 0.3); show(legDlq, 0.3);
say(caption, "Every dashed edge is a possible relationship—not a claim that data is flowing.", 0.40);
}
}
wait(0.30);
step("ingress-flow") {
par {
seq { flow(parse, 1.0); flow(toSpark, 1.0); }
say(caption, "Logging → parse → Kafka → Spark: the ingest-and-analytics tap.", 0.40);
}
}
wait(0.20);
step("runtime-hot-path") {
par {
show(event.parts, 0.25);
hotpath(event, 6.0, 27);
say(caption, "One event chooses valid branches: Kafka → an engine → results queue → Lambda → storage.", 0.40);
}
}
wait(0.30);
step("dead-letter") {
par {
flow(toDlq, 1.2);
say(caption, "Messages the handler cannot process are diverted to the dead-letter queue.", 0.40);
}
}
wait(0.30);
step("takeaway") {
par {
pulse(flows.parts, 0.70);
pulse(event.parts, 0.60);
say(caption, "One mixed stack—on-prem ingest, serverless compute, cloud storage—stays readable end to end.", 0.45);
}
}
wait(1.40);
onprem-advanced-web-service
The on-prem stress test with native archetypes: Nginx, gRPC, Redis/PostgreSQL HA, Fluentd→Kafka→Spark, and Prometheus/Grafana — three runtime stories on one platform.
title("Advanced On-Premises Web Service — Runtime Paths");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.14, h*0.03), "Made With Manic");
text(kicker, (cx, h*0.04), "SYSTEMS KIT · ON-PREMISES WEB SERVICE");
text(headline, (cx, h*0.125), "One platform. Three runtime stories.");
text(caption, (cx, h*0.945), "First reveal ownership; then distinguish requests, telemetry, and the analytics stream.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 30*u); bold(headline); wrap(headline, w*0.86);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);
if h > 1.25*w {
architecture(platform, (cx,h*0.52), w*0.84,h*0.72);
}
else {
architecture(platform, (cx,h*0.52), w*0.95,h*0.72);
}
node(ingress, platform, "gateway", "Nginx Ingress");
cluster(services, platform, "SERVICE CLUSTER");
node(grpc1, services, "service", "gRPC 1");
node(grpc2, services, "service", "gRPC 2");
node(grpc3, services, "service", "gRPC 3");
cluster(state, platform, "");
cluster(sessions, state, "SESSIONS HA");
node(sessionPrimary, sessions, "cache", "Redis Session");
node(sessionReplica, sessions, "cache", "Redis Replica");
cluster(databases, state, "DATABASE HA");
node(dbPrimary, databases, "database", "PostgreSQL Users");
node(dbReplica, databases, "database", "PostgreSQL Replica");
cluster(pipeline, platform, "EVENT ANALYTICS");
node(logging, pipeline, "service", "Fluentd Logging");
node(stream, pipeline, "queue", "Kafka Stream");
node(analytics, pipeline, "service", "Spark Analytics");
cluster(observability, platform, "OBSERVABILITY");
node(metrics, observability, "external", "Prometheus");
node(monitoring, observability, "external", "Grafana");
// Main request and dependency possibilities.
connect(ingressToServices, ingress, services);
connect(servicesToSessions, services, sessionPrimary, orthogonal);
connect(servicesToDatabase, services, dbPrimary, orthogonal);
connect(servicesToLogging, services, logging, orthogonal);
// The log stream remains one explicit pipeline.
connect(loggingToStream, logging, stream, orthogonal);
connect(streamToAnalytics, stream, analytics, orthogonal);
// Grafana queries Prometheus; Prometheus explicitly scrapes both replicas.
connect(dashboardQuery, monitoring, metrics, orthogonal);
connect(sessionScrape, metrics, sessionReplica, orthogonal);
connect(databaseScrape, metrics, dbReplica, orthogonal);
// HA replication is a neutral relationship, not a directed runtime claim.
link(sessionReplication, sessionPrimary.card, sessionReplica.card);
link(databaseReplication, dbPrimary.card, dbReplica.card);
dashed(sessionReplication); dashed(databaseReplication);
color(sessionReplication, dim); color(databaseReplication, dim);
stroke(sessionReplication, 2.5*u); stroke(databaseReplication, 2.5*u);
message(webRequest, ingress, "REQ");
message(sessionRead, grpc2, "S");
message(databaseRead, grpc3, "DB");
message(logEvent, grpc1, "LOG");
message(query, monitoring, "Q");
message(sessionProbe, metrics, "S");
message(databaseProbe, metrics, "D");
hidden(kicker); hidden(headline); hidden(caption);
hidden(platform.nodes);
hidden(services.parts); hidden(state.parts); hidden(sessions.parts); hidden(databases.parts);
hidden(pipeline.parts); hidden(observability.parts);
hidden(webRequest.parts); hidden(sessionRead.parts); hidden(databaseRead.parts);
hidden(logEvent.parts); hidden(query.parts); hidden(sessionProbe.parts); hidden(databaseProbe.parts);
untraced(platform.connections); untraced(sessionReplication); untraced(databaseReplication);
step("entry-and-services") {
par {
show(kicker, 0.30); show(headline, 0.45); show(caption, 0.40);
show(ingress, 0.35);
show(services.parts, 0.45);
stagger(0.08) { show(grpc1, 0.30); show(grpc2, 0.30); show(grpc3, 0.30); }
say(caption, "Nginx can reach three gRPC workers. The dashed fan-out is possibility, not a load-balancing simulation.", 0.40);
}
}
wait(0.20);
step("state-pipeline-and-monitoring") {
par {
show(state.parts, 0.45); show(sessions.parts, 0.40); show(databases.parts, 0.40);
show(sessionPrimary, 0.30); show(sessionReplica, 0.30);
show(dbPrimary, 0.30); show(dbReplica, 0.30);
show(pipeline.parts, 0.45); show(logging, 0.30); show(stream, 0.30); show(analytics, 0.30);
show(observability.parts, 0.45); show(metrics, 0.30); show(monitoring, 0.30);
say(caption, "Sessions, users, analytics, and observability keep separate ownership boundaries.", 0.40);
}
}
wait(0.20);
step("possible-topology") {
par {
draw(platform.connections, 1.10, smooth);
draw(sessionReplication, 0.55, smooth);
draw(databaseReplication, 0.55, smooth);
say(caption, "Straight, curved, and port-aware orthogonal paths still form one selectable topology.", 0.40);
}
}
wait(0.30);
step("one-ingress-request") {
par {
seq {
show(webRequest.parts, 0.18);
route(webRequest, ingressToServices, 1.00, linear);
}
say(caption, "One HTTP request selects one gRPC worker; the other possible ingress lanes remain cold.", 0.40);
}
}
wait(0.25);
step("parallel-state-dependencies") {
par {
fade(webRequest.parts, 0.20);
seq { show(sessionRead.parts, 0.18); route(sessionRead, servicesToSessions, 1.45, linear); }
seq { show(databaseRead.parts, 0.18); route(databaseRead, servicesToDatabase, 1.45, linear); }
flow(sessionReplication, 1.45, both, continuous);
flow(databaseReplication, 1.45, both, continuous);
say(caption, "Two workers can contact independent state systems while replication stays a neutral HA relationship.", 0.40);
}
}
wait(0.25);
step("logging-to-analytics") {
par {
fade(sessionRead.parts, 0.20); fade(databaseRead.parts, 0.20);
fade(servicesToSessions.hot, 0.25); fade(servicesToDatabase.hot, 0.25);
seq {
show(logEvent.parts, 0.18);
route(logEvent, servicesToLogging, 1.00, linear);
route(logEvent, loggingToStream, 0.85, linear);
route(logEvent, streamToAnalytics, 0.85, linear);
}
say(caption, "The same log event travels gRPC → Fluentd → Kafka → Spark without becoming a replacement dot.", 0.40);
}
}
wait(0.25);
step("observability") {
par {
fade(logEvent.parts, 0.20);
fade(servicesToLogging.hot, 0.25);
fade(loggingToStream.hot, 0.25);
fade(streamToAnalytics.hot, 0.25);
seq { show(query.parts, 0.18); route(query, dashboardQuery, 0.80, linear); }
seq { show(sessionProbe.parts, 0.18); route(sessionProbe, sessionScrape, 1.25, linear); }
seq { show(databaseProbe.parts, 0.18); route(databaseProbe, databaseScrape, 1.25, linear); }
say(caption, "Grafana queries Prometheus while two explicit scrape messages select the replica targets.", 0.40);
}
}
wait(0.35);
step("takeaway") {
par {
pulse(services.parts, 0.70); pulse(state.parts, 0.70);
pulse(pipeline.parts, 0.70); pulse(observability.parts, 0.70);
say(caption, "Ownership stays static. Persistent objects and authored paths explain what the platform does.", 0.45);
}
}
wait(1.30);
onprem-advanced-web-service-v2
The same platform with real provider icons and paths coloured by relationship (request · analytics · telemetry · replication).
title("Advanced On-Premises Web Service — Runtime Paths (with icons)");
canvas("16:9");
template("neon");
let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.14, h*0.03), "Made With Manic");
text(kicker, (cx, h*0.04), "SYSTEMS KIT · ON-PREMISES WEB SERVICE");
text(headline, (cx, h*0.125), "One platform. Three runtime stories.");
text(caption, (cx, h*0.945), "First reveal ownership; then distinguish requests, telemetry, and the analytics stream.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 30*u); bold(headline); wrap(headline, w*0.86);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);
if h > 1.25*w {
architecture(platform, (cx,h*0.52), w*0.84,h*0.72);
}
else {
architecture(platform, (cx,h*0.52), w*0.95,h*0.72);
}
node(ingress, platform, "onprem:nginx", "Nginx Ingress");
cluster(services, platform, "SERVICE CLUSTER");
node(grpc1, services, "onprem:server", "gRPC 1");
node(grpc2, services, "onprem:server", "gRPC 2");
node(grpc3, services, "onprem:server", "gRPC 3");
cluster(state, platform, "");
cluster(sessions, state, "SESSIONS HA");
node(sessionPrimary, sessions, "onprem:redis", "Redis Session");
node(sessionReplica, sessions, "onprem:redis", "Redis Replica");
cluster(databases, state, "DATABASE HA");
node(dbPrimary, databases, "onprem:postgresql", "PostgreSQL Users");
node(dbReplica, databases, "onprem:postgresql", "PostgreSQL Replica");
cluster(pipeline, platform, "EVENT ANALYTICS");
node(logging, pipeline, "onprem:fluentd", "Fluentd Logging");
node(stream, pipeline, "onprem:kafka", "Kafka Stream");
node(analytics, pipeline, "onprem:spark", "Spark Analytics");
cluster(observability, platform, "OBSERVABILITY");
node(metrics, observability, "onprem:prometheus", "Prometheus");
node(monitoring, observability, "onprem:grafana", "Grafana");
// Main request and dependency possibilities.
connect(ingressToServices, ingress, services);
connect(servicesToSessions, services, sessionPrimary, orthogonal);
connect(servicesToDatabase, services, dbPrimary, orthogonal);
connect(servicesToLogging, services, logging, orthogonal);
// The log stream remains one explicit pipeline.
connect(loggingToStream, logging, stream, orthogonal);
connect(streamToAnalytics, stream, analytics, orthogonal);
// Grafana queries Prometheus; Prometheus explicitly scrapes both replicas.
connect(dashboardQuery, monitoring, metrics, orthogonal);
connect(sessionScrape, metrics, sessionReplica, orthogonal);
connect(databaseScrape, metrics, dbReplica, orthogonal);
// HA replication is a neutral relationship, not a directed runtime claim.
link(sessionReplication, sessionPrimary.card, sessionReplica.card);
link(databaseReplication, dbPrimary.card, dbReplica.card);
dashed(sessionReplication); dashed(databaseReplication);
color(sessionReplication, dim); color(databaseReplication, dim);
stroke(sessionReplication, 2.5*u); stroke(databaseReplication, 2.5*u);
// --- colour every path by its RELATIONSHIP (cold dashed line + hot overlay) ---
// request path = cyan · analytics pipeline = gold · telemetry/scrape = magenta.
color(ingressToServices, cyan); color(ingressToServices.hot, cyan);
color(servicesToSessions, cyan); color(servicesToSessions.hot, cyan);
color(servicesToDatabase, cyan); color(servicesToDatabase.hot, cyan);
color(servicesToLogging, gold); color(servicesToLogging.hot, gold);
color(loggingToStream, gold); color(loggingToStream.hot, gold);
color(streamToAnalytics, gold); color(streamToAnalytics.hot, gold);
color(dashboardQuery, magenta); color(dashboardQuery.hot, magenta);
color(sessionScrape, magenta); color(sessionScrape.hot, magenta);
color(databaseScrape, magenta); color(databaseScrape.hot, magenta);
// (replication links stay neutral dim — a relationship, not a runtime path)
// legend: relationship → colour
text(legReq, (w*0.30, h*0.175), "REQUEST"); size(legReq, 13*u); color(legReq, cyan); bold(legReq);
text(legAna, (w*0.44, h*0.175), "ANALYTICS"); size(legAna, 13*u); color(legAna, gold); bold(legAna);
text(legTel, (w*0.60, h*0.175), "TELEMETRY"); size(legTel, 13*u); color(legTel, magenta); bold(legTel);
text(legRep, (w*0.76, h*0.175), "REPLICATION"); size(legRep, 13*u); color(legRep, dim); bold(legRep);
hidden(legReq); hidden(legAna); hidden(legTel); hidden(legRep);
message(webRequest, ingress, "REQ");
message(sessionRead, grpc2, "S");
message(databaseRead, grpc3, "DB");
message(logEvent, grpc1, "LOG");
message(query, monitoring, "Q");
message(sessionProbe, metrics, "S");
message(databaseProbe, metrics, "D");
hidden(kicker); hidden(headline); hidden(caption);
hidden(platform.nodes);
hidden(services.parts); hidden(state.parts); hidden(sessions.parts); hidden(databases.parts);
hidden(pipeline.parts); hidden(observability.parts);
hidden(webRequest.parts); hidden(sessionRead.parts); hidden(databaseRead.parts);
hidden(logEvent.parts); hidden(query.parts); hidden(sessionProbe.parts); hidden(databaseProbe.parts);
untraced(platform.connections); untraced(sessionReplication); untraced(databaseReplication);
step("entry-and-services") {
par {
show(kicker, 0.30); show(headline, 0.45); show(caption, 0.40);
show(ingress, 0.35);
show(services.parts, 0.45);
stagger(0.08) { show(grpc1, 0.30); show(grpc2, 0.30); show(grpc3, 0.30); }
say(caption, "Nginx can reach three gRPC workers. The dashed fan-out is possibility, not a load-balancing simulation.", 0.40);
}
}
wait(0.20);
step("state-pipeline-and-monitoring") {
par {
show(state.parts, 0.45); show(sessions.parts, 0.40); show(databases.parts, 0.40);
show(sessionPrimary, 0.30); show(sessionReplica, 0.30);
show(dbPrimary, 0.30); show(dbReplica, 0.30);
show(pipeline.parts, 0.45); show(logging, 0.30); show(stream, 0.30); show(analytics, 0.30);
show(observability.parts, 0.45); show(metrics, 0.30); show(monitoring, 0.30);
say(caption, "Sessions, users, analytics, and observability keep separate ownership boundaries.", 0.40);
}
}
wait(0.20);
step("possible-topology") {
par {
draw(platform.connections, 1.10, smooth);
draw(sessionReplication, 0.55, smooth);
draw(databaseReplication, 0.55, smooth);
show(legReq, 0.3); show(legAna, 0.3); show(legTel, 0.3); show(legRep, 0.3);
say(caption, "Each path is coloured by relationship: request, analytics, telemetry, replication.", 0.40);
}
}
wait(0.30);
step("one-ingress-request") {
par {
seq {
show(webRequest.parts, 0.18);
route(webRequest, ingressToServices, 1.00, linear);
}
say(caption, "One HTTP request selects one gRPC worker; the other possible ingress lanes remain cold.", 0.40);
}
}
wait(0.25);
step("parallel-state-dependencies") {
par {
fade(webRequest.parts, 0.20);
seq { show(sessionRead.parts, 0.18); route(sessionRead, servicesToSessions, 1.45, linear); }
seq { show(databaseRead.parts, 0.18); route(databaseRead, servicesToDatabase, 1.45, linear); }
flow(sessionReplication, 1.45, both, continuous);
flow(databaseReplication, 1.45, both, continuous);
say(caption, "Two workers can contact independent state systems while replication stays a neutral HA relationship.", 0.40);
}
}
wait(0.25);
step("logging-to-analytics") {
par {
fade(sessionRead.parts, 0.20); fade(databaseRead.parts, 0.20);
fade(servicesToSessions.hot, 0.25); fade(servicesToDatabase.hot, 0.25);
seq {
show(logEvent.parts, 0.18);
route(logEvent, servicesToLogging, 1.00, linear);
route(logEvent, loggingToStream, 0.85, linear);
route(logEvent, streamToAnalytics, 0.85, linear);
}
say(caption, "The same log event travels gRPC → Fluentd → Kafka → Spark without becoming a replacement dot.", 0.40);
}
}
wait(0.25);
step("observability") {
par {
fade(logEvent.parts, 0.20);
fade(servicesToLogging.hot, 0.25);
fade(loggingToStream.hot, 0.25);
fade(streamToAnalytics.hot, 0.25);
seq { show(query.parts, 0.18); route(query, dashboardQuery, 0.80, linear); }
seq { show(sessionProbe.parts, 0.18); route(sessionProbe, sessionScrape, 1.25, linear); }
seq { show(databaseProbe.parts, 0.18); route(databaseProbe, databaseScrape, 1.25, linear); }
say(caption, "Grafana queries Prometheus while two explicit scrape messages select the replica targets.", 0.40);
}
}
wait(0.35);
step("takeaway") {
par {
pulse(services.parts, 0.70); pulse(state.parts, 0.70);
pulse(pipeline.parts, 0.70); pulse(observability.parts, 0.70);
say(caption, "Ownership stays static. Persistent objects and authored paths explain what the platform does.", 0.45);
}
}
wait(1.30);
Troubleshooting
manic checks your scene as you type — it underlines the spot and shows the message, and where possible offers a one-click Fix. Here are the mistakes people actually hit, the message you’ll see, and the fix.
Errors the editor catches
1. unknown variable — a missing *
The #1 mistake. Two names written together are read as a single word.
# ❌ unknown variable `idx`
dot(p, (cx + idx, cy), 6);
# ✅ put a * between the two names
dot(p, (cx + i*dx, cy), 6);
A number can hug a name (2r, 3(x+1)), but two names can’t. Add *
at every name-next-to-name: i*dx, tau*i, xmid*sx, and especially
r*cos(t) / r*sin(t) — rcos/rsin are the classic trap (they mean
“radius × cos”, not a function).
2. unknown function in a plot — quote the formula
Only a short list of names work as bare words (sin, cos, tan,
sqrt, abs, exp, log, parabola, cubic, gauss, sinc, …). Anything
else — acos, tanh, log10, sums of terms — must be a “quoted formula”.
# ❌ unknown function `acos`
plot(f, (cx, cy), 80, 80, acos, (-1, 1));
# ✅ wrap it in quotes as a formula in x
plot(f, (cx, cy), 80, 80, "acos(x)", (-1, 1));
3. needs at least N argument(s) — you dropped the id (or an argument)
Every builtin’s first argument is its id — a name you pick. Modifiers and verbs need that id too.
# ❌ `size` needs at least 2 argument(s), got 1
size(30);
# ✅ say which entity
size(title, 30);
# ❌ `circle` needs at least 3 argument(s), got 2 (no radius)
circle(c, (cx, cy));
# ✅
circle(c, (cx, cy), 120);
4. no entity named X — a typo, or used too early
You referred to an id that doesn’t exist — misspelled, or used before it’s made.
text(title, (cx, 60), "Hello");
# ❌ no entity named `titel`
color(titel, cyan);
# ✅ match the id exactly
color(title, cyan);
5. unknown colour — use the palette (or hue)
Only the named palette colours work — no #ff0000.
# ❌ unknown colour `chartreuse`
color(dot, chartreuse);
# ✅ a palette colour…
color(dot, coral);
# ✅ …or a computed one, 0–360
hue(dot, 210);
Palette: cyan magenta lime gold red orange blue teal violet coral indigo mint fg dim void panel.
6. stroke is 2D-only — a 2D styler on a 3D shape
Some styling is 2D-only. On 3D shapes use the 3D equivalent.
cube3(bx, (0, 0, 1), (2, 2, 2));
# ❌ `stroke` is 2D-only; for a 3D line/arrow/curve use `thick(bx, radius)`
stroke(bx, 3);
# ✅ thickness in world units
thick(bx, 0.1);
The message names the fix. Also: hue → use color on 3D entities.
7. argument 1 should be a name — a reserved word as an id
pi, tau, e, inf, w, h, cx, cy are built-in values — you can’t
name an entity one of them.
# ❌ argument 1 of `dot` should be a name (e is Euler's number)
dot(e, (cx, cy), 6);
# ✅ pick any other name
dot(pt, (cx, cy), 6);
8. expected a statement … found ; — a stray semicolon
Blocks (par, seq, stagger, for, if) end with } — no semicolon.
# ❌ expected a statement …, found `;`
par { show(a, 1); show(b, 1); };
# ✅
par { show(a, 1); show(b, 1); }
No error — but it looks wrong
These pass the check, so watch for them yourself.
9. A curve appears all at once instead of drawing on
draw animates a shape that starts hidden. Declare it untraced first.
plot(f, (cx, cy), 80, 80, "sin(x)", (0, 6));
# ❌ f is already fully shown — draw does nothing visible
draw(f, 2);
# ✅ hide the line, then draw traces it on
untraced(f);
draw(f, 2);
10. Things land off-screen
The canvas is a fixed logical size — 1280×720 for 16:9 — not your
video’s pixel size. Position with cx, cy, w, h, never hard-coded pixels.
# ❌ 1700 is past the right edge (width is 1280)
text(t, (1700, 300), "hi");
# ✅ relative to the centre
text(t, (cx + 200, 300), "hi");
11. A matrix/table cell with a comma
Cells are single tokens split by spaces or commas, so a cell can’t contain
a comma — (0,0) silently becomes two cells and the grid comes out malformed.
# ❌ no error, but the row breaks apart
matrix(m, "(0,0) (1,1)", (cx, cy));
# ✅ one token per cell
matrix(m, "0 1; 2 3", (cx, cy));
Rules of thumb: put a * between names · quote any formula that isn’t a
bare-word function · give every entity an id · use cx/cy/w/h for
position · untraced + draw to trace a curve on.