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.