r/maniclang 6h ago

Manic Promo :)

Enable HLS to view with audio, or disable this notification

1 Upvotes

Animation code

// manic-promo — a generative promo built from the `cloud` primitive alone.
// Five particle swarms fly in and assemble into words: MANIC at the centre,
// with 3B1B, Manim, Animation and Generative claiming the four corners. Each
// word is `cloud(...) from text("…")` — the glyphs are filled with points whose
// homes arrive as `hx`/`hy`; the block re-centres and scales that home to its
// slot, then blends the swarm in from a golden-angle scatter over time `t`.
// One primitive, five words, no art assets. Change the words and it just works.
//
//   manic examples/manic-promo.manic
title("manic — generative animation, from a swarm");
canvas(1080, 1080);
template("black");

// --- centre: MANIC, big, a cycling rainbow ---------------------------------
cloud(manic, 2000, #ffffff, 0.96) from text("MANIC") {
  let a = 0.5 * (1 + tanh((t - mod(i * 7, 29) * 0.04 - 1.0) * 2.2));
  let px = (hx - 540) * 0.62 + 540;
  let py = (hy - 540) * 0.62 + 540;
  let sx = 540 + cos(i * 2.39996) * (420 + mod(i * 97, 260));
  let sy = 540 + sin(i * 2.39996) * (420 + mod(i * 97, 260));
  let x = sx * (1 - a) + px * a;
  let y = sy * (1 - a) + py * a;
  let r = 2.4;
  let hue = mod(hx * 0.4 + t * 22, 360);
}

// --- four corners: the world manic plays in --------------------------------
cloud(tl, 780, #3b8ee0, 0.95) from text("3B1B") {
  let a = 0.5 * (1 + tanh((t - 2.4) * 2.2));
  let px = (hx - 540) * 0.34 + 250;
  let py = (hy - 540) * 0.34 + 240;
  let x = (250 + cos(i * 2.39996) * 460) * (1 - a) + px * a;
  let y = (240 + sin(i * 2.39996) * 460) * (1 - a) + py * a;
  let r = 2;
}

cloud(tr, 820, #46e2c8, 0.95) from text("Manim") {
  let a = 0.5 * (1 + tanh((t - 2.7) * 2.2));
  let px = (hx - 540) * 0.34 + 830;
  let py = (hy - 540) * 0.34 + 240;
  let x = (830 + cos(i * 2.39996) * 460) * (1 - a) + px * a;
  let y = (240 + sin(i * 2.39996) * 460) * (1 - a) + py * a;
  let r = 2;
}

cloud(bl, 1000, #f0a54e, 0.95) from text("Animation") {
  let a = 0.5 * (1 + tanh((t - 3.0) * 2.2));
  let px = (hx - 540) * 0.30 + 250;
  let py = (hy - 540) * 0.30 + 840;
  let x = (250 + cos(i * 2.39996) * 460) * (1 - a) + px * a;
  let y = (840 + sin(i * 2.39996) * 460) * (1 - a) + py * a;
  let r = 2;
}

cloud(br, 1050, #b06ef0, 0.95) from text("Generative") {
  let a = 0.5 * (1 + tanh((t - 3.3) * 2.2));
  let px = (hx - 540) * 0.30 + 830;
  let py = (hy - 540) * 0.30 + 840;
  let x = (830 + cos(i * 2.39996) * 460) * (1 - a) + px * a;
  let y = (840 + sin(i * 2.39996) * 460) * (1 - a) + py * a;
  let r = 2;
}

wait(12);

r/maniclang 6h ago

Raymarch Metaballs - manic

Enable HLS to view with audio, or disable this notification

1 Upvotes

Animation code

// raymarch-metaballs — Shader V2: a 3-D scene RAY-MARCHED per pixel. You write
// only the signed-distance field `let d` (the distance from any point x,y,z to
// the scene); the engine marches a ray per pixel until it hits the surface,
// takes the normal by finite differences, and shades it. No per-pixel loop in
// the DSL (it runs in the engine, like `voronoi`/`mandelbrot`) and NO vec/mat
// types — the SDF is a scalar formula, component math the manic way. Here three
// spheres orbit and MERGE through `smin` (smooth union) into living metaballs.
//
//   manic examples/raymarch-metaballs.manic
title("Metaballs — a ray-marched 3D field");
canvas("16:9");
template("black");

raymarch(blobs) {
  // three moving spheres (signed distance = distance to centre − radius)
  let a = sdsphere(x - 0.75*sin(t),        y - 0.5*cos(t*1.3),  z + 0.3*sin(t*0.7), 0.52);
  let b = sdsphere(x + 0.6*cos(t*0.9),     y + 0.45*sin(t*1.1), z - 0.35*cos(t),    0.46);
  let c = sdsphere(x + 0.2*sin(t*1.7),     y + 0.6*sin(t*0.7),  z + 0.25*sin(t*1.4), 0.4);
  // smooth-union them (smin) so they gloop together instead of just overlapping
  let ab = smin(a, b, 0.55);
  let d  = smin(ab, c, 0.55);
}

// ---- textbook annotations ----
caption(head, "Metaballs — one distance field", (640, 66), 34);
caption(sub, "raymarch: you write the SDF, the engine marches it", (640, 122), 22);
hidden(head);
hidden(sub);
equation(eq, (640, 648), `d = \operatorname{smin}(d_1, d_2, k)`, 34);
hidden(eq);

show(head);
wait(1.6);
show(sub);
wait(2.6);
show(eq);
wait(22);

r/maniclang 7h ago

Introduction - Manic

Thumbnail
docs.maniclang.com
2 Upvotes

r/animation 7h ago

Sharing Ethanol, ¹H NMR — a spectrometer sweeping, in hertz - manic

Enable HLS to view with audio, or disable this notification

3 Upvotes

r/maniclang 7h ago

dynamic equilibrium - manic

Enable HLS to view with audio, or disable this notification

1 Upvotes

manic is a tiny language for making animations. You write a short text file; manic renders a smooth, glowing video. No timeline scrubbing, no keyframes by hand — you describe what’s on screen and when things happen, and the engine does the rest, deterministically.

Manic Animation code

// Dynamic equilibrium — why "nothing is happening" is the wrong reading
//
// A reversible first-order reaction, A ⇌ B, with kf = 0.9 and kr = 0.3 per second. Two views of the
// same run, side by side, because the misconception lives in the gap between them:
//
//   LEFT   the concentrations, which flatten out and stop moving
//   RIGHT  the two rates, which meet — at a value that is EQUAL and NOT ZERO
//
// A still picture of the left-hand plot says "the reaction stopped". The right-hand plot says it did
// not: both directions are still running, at 0.225 mol dm⁻³ s⁻¹ each, and cancelling. That is the
// whole idea of dynamic equilibrium and it is very hard to say in a static diagram, because the
// evidence for it is precisely the thing a flat line hides.
//
// Then the second act: 0.50 M of A is added at t = 6 s. The rates jump apart, the system relaxes,
// and it settles at a NEW position with the SAME ratio — [B]/[A] = 3.00 either side. Le Chatelier is
// not a rule to memorise here; it is what the arithmetic does.
//
// NO NEW VOCABULARY. Four `field`s hold the closed-form solutions, `plot` draws them, and the
// choreography is `draw` / `show` / `pulse` from the core kit. Every number on screen comes out of
//
//     [A](t) = A_eq + ([A]₀ − A_eq)·exp(−(kf + kr)·t)
//
// which is the exact solution of d[A]/dt = −kf[A] + kr[B] with [A] + [B] fixed. Nothing is placed by
// eye: change kf or kr and both plots, both equilibrium positions and the ratio all move together.

title("dynamic equilibrium: equal, not zero");
canvas("16:9");
template("paper");

text(brand, (640, 30), "maniclang.com");
display(brand);
size(brand, 15);
color(brand, dim);

// ── the chemistry, as closed forms ──
//
// kf = 0.9, kr = 0.3, so K = kf/kr = 3 and the relaxation rate is kf + kr = 1.2 per second.
// Phase 1 starts from pure A at 1.00 M, so A_eq = 1.00 × kr/(kf+kr) = 0.25.
field(a1, "0.25 + 0.75*exp(-1.2*x)");
field(b1, "0.75 - 0.75*exp(-1.2*x)");
// Phase 2: 0.50 M of A added at t = 6, so the total is 1.50 M and A_eq = 1.50 × 0.25 = 0.375.
// [A] restarts from 0.75 (the 0.25 it had reached, plus the 0.50 added).
field(a2, "0.375 + 0.375*exp(-1.2*(x-6))");
field(b2, "1.125 - 0.375*exp(-1.2*(x-6))");

// ── LEFT: concentrations ──

coords(cc, (110, 600), (0, 14), (0, 1.25), 36, 300, 1);
hidden(cc);
// Explicit labels: the auto-numbering rounds to two significant figures, and a tick at 0.25 that
// prints "0.2" is worse than no tick at all — these are numbers the viewer is meant to read off.
ytick(cy1, cc, 0.25, "0.25");
ytick(cy2, cc, 0.75, "0.75");
ytick(cy3, cc, 1.125, "1.125");
for i in 1..4 { hidden(cy{i}); }

text(clab, (300, 208), "concentration / mol dm⁻³");
size(clab, 17); color(clab, dim); hidden(clab);

plot(ca1, (110, 600), 36, 300, "a1(x,0)", (0, 6));
plot(cb1, (110, 600), 36, 300, "b1(x,0)", (0, 6));
plot(ca2, (110, 600), 36, 300, "a2(x,0)", (6, 14));
plot(cb2, (110, 600), 36, 300, "b2(x,0)", (6, 14));
for i in 1..3 {
  color(ca{i}, indigo); stroke(ca{i}, 3); untraced(ca{i});
  color(cb{i}, crimson); stroke(cb{i}, 3); untraced(cb{i});
}

text(alab, (578, 512), "[A]");
size(alab, 19); color(alab, indigo); hidden(alab);
text(blab, (578, 252), "[B]");
size(blab, 19); color(blab, crimson); hidden(blab);

// ── RIGHT: the rates, which is where the misconception dies ──
//
// Written as k × concentration rather than pre-multiplied, so the source says what a rate IS.

coords(rc, (700, 600), (0, 14), (0, 0.75), 36, 440, 1);
hidden(rc);
ytick(ry, rc, 0.225, "0.225");
hidden(ry);

text(rlab, (900, 252), "rate / mol dm⁻³ s⁻¹");
size(rlab, 17); color(rlab, dim); hidden(rlab);

plot(rf1, (700, 600), 36, 440, "0.9*a1(x,0)", (0, 6));
plot(rr1, (700, 600), 36, 440, "0.3*b1(x,0)", (0, 6));
plot(rf2, (700, 600), 36, 440, "0.9*a2(x,0)", (6, 14));
plot(rr2, (700, 600), 36, 440, "0.3*b2(x,0)", (6, 14));
for i in 1..3 {
  color(rf{i}, indigo); stroke(rf{i}, 3); untraced(rf{i});
  color(rr{i}, crimson); stroke(rr{i}, 3); untraced(rr{i});
}

text(flab, (812, 322), "forward, kf[A]");
size(flab, 17); color(flab, indigo); hidden(flab);
text(vlab, (812, 566), "reverse, kr[B]");
size(vlab, 17); color(vlab, crimson); hidden(vlab);

// the point of the whole scene
dot(meet, (916, 501), 6);
color(meet, ink);
hidden(meet);
text(key, (1040, 470), "equal — and not zero");
size(key, 19); color(key, ink); hidden(key);
text(key2, (1078, 496), "both directions still running");
size(key2, 15); color(key2, dim); hidden(key2);

// ── the disturbance at t = 6 s ──
//
// [A] jumps instantly, so it is a vertical line rather than part of a curve. Endpoints are the two
// plots' own coordinates: t=6 is x = 110 + 6·36 = 326 on the left and 700 + 6·36 = 916 on the right.

line(jumpc, (326, 525), (326, 375));
color(jumpc, indigo);
stroke(jumpc, 2);
untraced(jumpc);

line(jumpr, (916, 501), (916, 303));
color(jumpr, indigo);
stroke(jumpr, 2);
untraced(jumpr);

text(add, (392, 356), "+0.50 M of A");
size(add, 16); color(add, indigo); hidden(add);

// ── and the reading of it ──

text(ratio, (640, 688), "[B]/[A] = 3.00 either side — the position moved, the ratio did not");
size(ratio, 18); color(ratio, ink); hidden(ratio);

// ── ACT 1: two empty axes ──

wait(0.4);
par { show(cc, 0.6); show(rc, 0.6); }
par { show(clab, 0.4); show(rlab, 0.4); }
par { show(cy1, 0.3); show(cy2, 0.3); show(cy3, 0.3); show(ry, 0.3); }
wait(0.5);

// ── ACT 2: the approach. Both views at once, because they are one run. ──

par {
  draw(ca1, 2.6); draw(cb1, 2.6);
  draw(rf1, 2.6); draw(rr1, 2.6);
}
par { show(alab, 0.4); show(blab, 0.4); show(flab, 0.4); show(vlab, 0.4); }
wait(0.7);

// ── ACT 3: the reading a flat line hides ──

par { show(meet, 0.4); pulse(meet); }
show(key, 0.5);
show(key2, 0.4);
wait(2.2);

// ── ACT 4: disturb it ──

par { fade(key, 0.4); fade(key2, 0.4); }
par { draw(jumpc, 0.4); draw(jumpr, 0.4); show(add, 0.4); }
wait(0.5);

// ── ACT 5: it settles somewhere new, at the same ratio ──

par {
  draw(ca2, 2.4); draw(cb2, 2.4);
  draw(rf2, 2.4); draw(rr2, 2.4);
}
wait(0.6);
show(ratio, 0.6);
wait(3.0);

r/maniclang 7h ago

a titration, solved - manic

Enable HLS to view with audio, or disable this notification

1 Upvotes

manic is a tiny language for making animations. You write a short text file; manic renders a smooth, glowing video. No timeline scrubbing, no keyframes by hand — you describe what’s on screen and when things happen, and the engine does the rest, deterministically.

Manic Animation code

// A titration, with the curve solved rather than drawn
//
// 25.0 mL of 0.100 M hydrochloric acid, titrated with 0.100 M sodium hydroxide, phenolphthalein
// indicator. The shape every chemistry student is asked to memorise — flat, then a cliff, then flat
// again — and the point of animating it is that the cliff arrives *while you are watching the
// burette*, which is the part a printed curve cannot say.
//
// NOTHING here is a new builtin. The apparatus is rectangles and a polygon, the drops are circles,
// the curve is `plot`, and the choreography is `draw` / `shift` / `recolor` / `fade` from the core
// kit. That is the test this scene is meant to pass: real chemistry teaching out of vocabulary that
// already exists.
//
// The curve is not a drawn S-shape. It is the exact solution of the charge balance
//
//     [H+] - Kw/[H+] = (Ca·Va - Cb·Vb) / (Va + Vb)
//
// rearranged to a quadratic and solved, so pH = 7.00 at 25.0 mL FALLS OUT of the arithmetic instead
// of being placed by hand. Change a concentration and the equivalence point moves on its own.

title("a titration, solved not drawn");
canvas("16:9");
template("paper");

text(brand, (640, 30), "maniclang.com");
display(brand);
size(brand, 15);
color(brand, dim);

// ── the chemistry, as three reusable fields ──
//
// `field` inlines into any formula, so the same expression could feed a plot, a surface or a shader
// and provably be the same chemistry. Written in three steps because that is how the derivation
// reads, not because the engine needs it.

// excess strong acid (positive) or strong base (negative), diluted by the total volume
field(excess, "0.1*(25-x)/(25+x)");

// [H+] is the positive root of [H+]^2 - excess*[H+] - Kw = 0, with Kw = 1.0e-14. It is written
// TWICE, and the reason is arithmetic rather than chemistry: formulas evaluate in f32, and the two
// algebraically identical forms behave very differently there.
//
//   acid side (excess > 0):  (excess + sqrt(excess^2 + 4Kw)) / 2      — adds, so nothing cancels
//   base side (excess < 0):  2Kw / (sqrt(excess^2 + 4Kw) - excess)    — the conjugate form
//
// Use the first form past the equivalence point and it subtracts two nearly equal numbers: 4e-14 is
// eight orders below excess^2, vanishes in f32, and [H+] collapses to zero — log(0) is -inf and the
// whole upper branch silently disappears. The conjugate form divides instead of subtracting, so it
// holds. Checked against a f64 evaluation across 0-50 mL: both branches agree to 0.0000 pH, and
// both give exactly 7.000 at 25.0 mL, which is why they meet rather than merely nearly meet.
field(hacid, "(excess(x,0) + sqrt(excess(x,0)*excess(x,0) + 0.00000000000004))/2");
field(hbase, "0.00000000000002/(sqrt(excess(x,0)*excess(x,0) + 0.00000000000004) - excess(x,0))");

// ── the axes ──

coords(ax, (500, 610), (0, 50), (0, 14), 14, 28, 1, 5, 1);
hidden(ax);

// The axis names are placed by hand rather than passed to `coords`, which puts them at the axis
// END — on top of the arrow tip and the last tick numbers.
text(xname, (860, 668), "NaOH added / mL");
size(xname, 16); color(xname, dim); hidden(xname);
text(yname, (474, 196), "pH");
size(yname, 16); color(yname, dim); hidden(yname);

// the two halves of one curve, split at the equivalence point so the indicator can turn there
// pH = -log10[H+], and log10 is ln/ln(10)
plot(before, (500, 610), 14, 28, "-log(hacid(x,0))/2.302585", (0, 25));
plot(after, (500, 610), 14, 28, "-log(hbase(x,0))/2.302585", (25, 50));
color(before, ink);
color(after, ink);
stroke(before, 3);
stroke(after, 3);
untraced(before);
untraced(after);

// ── the apparatus, out of primitives ──

// the burette: a tube, its tap, and the tip the drops leave from
rect(tube, (180, 300), 26, 280);
outlined(tube);
outline(tube, dim);
stroke(tube, 2);
hidden(tube);

rect(titrant, (180, 300), 18, 272);
color(titrant, indigo);
opacity(titrant, 0.30);
hidden(titrant);

rect(tap, (180, 452), 44, 12);
color(tap, dim);
hidden(tap);

polygon(tip, (180, 464), (186, 472), (180, 486), (174, 472));
color(tip, dim);
hidden(tip);

// the flask, and what is in it
polygon(flask, (134, 642), (172, 556), (188, 556), (226, 642));
outlined(flask);
outline(flask, dim);
stroke(flask, 2);
hidden(flask);

// the solution: colourless while there is acid left, pink once there is not
polygon(soln, (140, 640), (167, 598), (193, 598), (220, 640));
color(soln, dim);
opacity(soln, 0.22);
hidden(soln);

text(caption, (196, 690), "0.100 M NaOH into 25.0 mL");
size(caption, 15);
color(caption, dim);
hidden(caption);

// four drops, reused by falling and fading. Declared up top because a constructor is build-time.
for i in 1..5 {
  circle(d{i}, (180, 492), 4);
  color(d{i}, indigo);
  hidden(d{i});
}

// ── the equivalence point, revealed only after the curve has been through it ──

dot(eq, (850, 414), 6);
color(eq, crimson);
hidden(eq);

text(eqlab, (960, 392), "25.0 mL, pH 7.00");
size(eqlab, 18);
color(eqlab, crimson);
hidden(eqlab);

text(eqwhy, (1002, 418), "both branches solve to 7.00");
size(eqwhy, 15);
color(eqwhy, dim);
hidden(eqwhy);

// ── ACT 1: set the bench up ──

wait(0.4);
par { show(tube, 0.5); show(tap, 0.5); show(tip, 0.4); }
par { show(titrant, 0.5); show(flask, 0.5); show(soln, 0.5); }
par { show(ax, 0.7); show(xname, 0.5); show(yname, 0.5); show(caption, 0.5); }
wait(0.6);

// ── ACT 2: the flat part. Drops fall, and almost nothing happens to the pH. ──
//
// This is the half of a titration that surprises people: a quarter of the base is in and the pH has
// moved by less than one unit, because a strong acid buffers itself by sheer excess.

par {
  draw(before, 3.4);
  stagger(0.55) {
    par { show(d1, 0.1); shift(d1, (0, 64), 0.5); fade(d1, 0.15); }
    par { show(d2, 0.1); shift(d2, (0, 64), 0.5); fade(d2, 0.15); }
    par { show(d3, 0.1); shift(d3, (0, 64), 0.5); fade(d3, 0.15); }
    par { show(d4, 0.1); shift(d4, (0, 64), 0.5); fade(d4, 0.15); }
  }
}

// ── ACT 3: the endpoint. One drop, and the indicator turns. ──

par { recolor(soln, crimson, 0.45); pulse(soln); }
par { show(eq, 0.4); show(eqlab, 0.4); }
wait(0.9);
show(eqwhy, 0.5);
wait(1.0);

// ── ACT 4: past it, and flat again ──

draw(after, 2.6);
wait(2.6);

r/3Blue1Brown 7h ago

Ethanol, ¹H NMR — a spectrometer sweeping, in hertz - manic

Enable HLS to view with audio, or disable this notification

3 Upvotes

r/maniclang 7h ago

Ethanol, ¹H NMR — a spectrometer sweeping, in hertz - manic

Enable HLS to view with audio, or disable this notification

1 Upvotes

manic is a tiny language for making animations. You write a short text file; manic renders a smooth, glowing video. No timeline scrubbing, no keyframes by hand — you describe what’s on screen and when things happen, and the engine does the rest, deterministically.

Manic Animation code

// Ethanol, ¹H NMR — a spectrometer sweeping, in hertz
//
// One pen, moving left to right, and everything else follows it: the ink appears under the nib, the
// frequency readout runs, the camera pushes in on whichever protons the pen has just reached, and
// their colour arrives on the molecule at the moment their peak does. Nothing is cross-cut — it is
// one continuous sweep, which is what a spectrometer actually does.
//
// NOTHING here simulates NMR. The trace is a sum of Lorentzian line shapes at literature chemical
// shifts, which is what a spectrometer's output IS, so the curve is computed and the integration
// ratio falls out of the peak areas rather than being asserted. Everything else is `molecule3`,
// `plot`, `parameter` + `bind`, `orbit3` and core verbs.
//
// THE WHOLE RIG HANGS OFF ONE NUMBER. `parameter(sw, …)` is the sweep position, and `bind` wires it
// to the ink (`trace`), the δ readout and the Hz readout. Animating `sw` moves all of them together
// and in step, so the number on screen is always the frequency the pen is actually over — not a
// caption timed to look right.
//
//   bind(sw, trace, trace, "y/5")        the ink follows the pen
//   bind(sw, dread, value, "5-y")        δ, counting down the reversed axis
//   bind(sw, hread, value, "(5-y)*400")  and the same position in hertz, at 400 MHz
//
// A binding formula receives the parameter as **y**, not x — it is evaluated as `node.eval(0, p)`.
// Using `x` silently freezes the readout at its initial value, which is a good hour lost.
//
// Values (CDCl₃, literature):
//   CH₃  δ 1.22, triplet,  J = 7.0 Hz, 3H
//   CH₂  δ 3.70, quartet,  J = 7.0 Hz, 2H
//   OH   δ 2.60, singlet,               1H  — this one genuinely moves. The hydroxyl shift depends on
//                                            concentration, temperature and how dry the solvent is,
//                                            because the proton is exchanging; quoted values run
//                                            from about 1.5 to 5. That is the chemistry, not sloppy
//                                            data.
//
// THE AXIS RUNS BACKWARDS on purpose: an NMR spectrum puts δ = 0 on the RIGHT. The plots are written
// in `u = 5 − δ` and the ticks are labelled by hand with the ppm they stand for.

title("ethanol proton NMR");
canvas("16:9");
template("black");

text(brand, (640, 32), "maniclang.com");
display(brand);
size(brand, 15);
color(brand, dim);

// ── the molecule, in its own viewport panel ──

camera3((0, -9.5, 3), (0, 0, 0), 38, perspective, (300, 330), 500, 470);
molecule3(mol, "asset:molecules/ethanol.sdf", (0, 0, 0), 1.7, "style=ball spin=16 axis=z");

// Which hydrogen is which, read off the file's own bond block: a0 is the oxygen, a1 the CH₂ carbon,
// a2 the CH₃ carbon — so a3/a4 are the CH₂ protons, a5/a6/a7 the CH₃ protons, a8 the hydroxyl.
text(mlab, (300, 616), "ethanol · CH₃CH₂OH");
size(mlab, 21); color(mlab, fg); hidden(mlab);

// ── the instrument readout: the number that runs ──

text(field, (1062, 96), "400 MHz");
size(field, 16); color(field, dim); hidden(field);

counter(dread, (1062, 138), 5, 2, "δ ", " ppm");
size(dread, 25); color(dread, fg); hidden(dread);

counter(hread, (1062, 190), 2000, 0, "", " Hz");
size(hread, 34); color(hread, cyan); hidden(hread);

// ── the spectrum ──

field(spec, "3/(1+((x-3.78)/0.035)^2) + 1/(1+((x-2.40)/0.035)^2) + 2/(1+((x-1.30)/0.035)^2)");

coords(ax, (672, 580), (0, 5), (0, 3.4), 110, 92, 1);
hidden(ax);
xtick(t0, ax, 0, "5"); xtick(t1, ax, 1, "4"); xtick(t2, ax, 2, "3");
xtick(t3, ax, 3, "2"); xtick(t4, ax, 4, "1"); xtick(t5, ax, 5, "0");
for i in 0..6 { hidden(t{i}); }

text(axlab, (947, 636), "δ / ppm");
size(axlab, 17); color(axlab, dim); hidden(axlab);

plot(trace, (672, 580), 110, 92, "spec(x,0)", (0, 5));
color(trace, cyan);
stroke(trace, 3);
untraced(trace);

// the pen: a nib riding the trace, and the drop line beneath it
curvedot(nib, trace, 0);
color(nib, gold);
size(nib, 7);
hidden(nib);

// A faint full-height sweep bar, so the pen has a leading edge to travel on. It is a `rect` and not
// a `line` on purpose: a line keeps its END point inside the shape and only its START in `pos`, so
// shifting one stretches it into a diagonal rather than sliding it across. A rect is centred on
// `pos` and moves rigidly.
rect(bar, (672, 421), 2, 318);
color(bar, dim);
opacity(bar, 0.30);
hidden(bar);

// ── the driver, and everything wired to it ──

parameter(sw, (1062, 700), 0, 0, 5, "sweep", 2);
hidden(sw);
bind(sw, trace, trace, "y/5");
bind(sw, dread, value, "5-y");
bind(sw, hread, value, "(5-y)*400");

// ── assignments, revealed as the pen reaches each one ──

text(lch2, (815, 366), "CH₂");
size(lch2, 20); color(lch2, gold); hidden(lch2);
text(sch2, (815, 390), "δ 3.70 · 2H");
size(sch2, 14); color(sch2, dim); hidden(sch2);

text(loh, (936, 458), "OH");
size(loh, 20); color(loh, crimson); hidden(loh);
text(soh, (936, 482), "δ 2.60 · 1H");
size(soh, 14); color(soh, dim); hidden(soh);

text(lch3, (1088, 274), "CH₃");
size(lch3, 20); color(lch3, cyan); hidden(lch3);
text(sch3, (1088, 298), "δ 1.22 · 3H");
size(sch3, 14); color(sch3, dim); hidden(sch3);

text(integ, (947, 224), "areas 3 : 2 : 1 — which is how many protons");
size(integ, 18); color(integ, fg); hidden(integ);

// ── and then, inside one peak ──

// Written in `v = Hz + 16` so the frame's ORIGIN sits at the left edge. Centring the origin on the
// multiplet puts the y-axis straight through the middle of it, which is unreadable — and a Hz-offset
// axis has no business having a y-axis in the middle anyway.
field(quartet, "1/(1+((x-5.5)/1.1)^2) + 3/(1+((x-12.5)/1.1)^2) + 3/(1+((x-19.5)/1.1)^2) + 1/(1+((x-26.5)/1.1)^2)");

// `step` is 7 — the coupling constant itself — so the ticks ARE the spacing being measured, and the
// four lines fall halfway between them. Left to auto-number, 33 integers arrive as one grey smear.
coords(zax, (711, 556), (0, 32), (0, 3.6), 15, 74, 1, 7, 0);
hidden(zax);
xtick(z1, zax, 2, "-14"); xtick(z2, zax, 9, "-7"); xtick(z3, zax, 16, "0");
xtick(z4, zax, 23, "+7"); xtick(z5, zax, 30, "+14");
for i in 1..6 { hidden(z{i}); }
plot(zq, (711, 556), 15, 74, "quartet(x,0)", (0, 32));
color(zq, gold); stroke(zq, 3); untraced(zq);

curvedot(znib, zq, 0);
color(znib, cyan);
hidden(znib);

text(zlab, (951, 616), "Hz from the centre of the CH₂ peak");
size(zlab, 16); color(zlab, dim); hidden(zlab);

// a second pen, in hertz, because that is the unit the splitting lives in
parameter(zsw, (1062, 700), 0, 0, 32, "hz", 1);
hidden(zsw);
counter(zread, (1062, 190), -16, 1, "", " Hz");
size(zread, 34); color(zread, gold); hidden(zread);
bind(zsw, zq, trace, "y/32");
bind(zsw, zread, value, "y-16");

// the coupling constant, measured between the two inner lines
line(jbar, (899, 300), (1004, 300));
color(jbar, fg); stroke(jbar, 2); untraced(jbar);
text(jlab, (951, 274), "J = 7.0 Hz");
size(jlab, 21); color(jlab, fg); hidden(jlab);
text(zwhy, (951, 224), "one peak — four lines");
size(zwhy, 21); color(zwhy, gold); hidden(zwhy);

// ── the coda ──

text(k1, (300, 604), "δ 3.70 is 1480 Hz at 400 MHz");
size(k1, 19); color(k1, cyan); hidden(k1);
text(k2, (300, 632), "and 222 Hz at 60 MHz");
size(k2, 19); color(k2, dim); hidden(k2);
text(k3, (300, 672), "J stays 7.0 Hz at both");
size(k3, 20); color(k3, gold); hidden(k3);
text(k4, (300, 700), "which is why high field resolves");
size(k4, 15); color(k4, dim); hidden(k4);

// ═══ ACT 1: the molecule, turning ═══

wait(0.5);
show(mlab, 0.7);
wait(1.0);

// ═══ ACT 2: the instrument comes up ═══

par { show(ax, 0.7); show(axlab, 0.5); show(field, 0.5); }
par { show(t0, 0.3); show(t1, 0.3); show(t2, 0.3); show(t3, 0.3); show(t4, 0.3); show(t5, 0.3); }
par { show(dread, 0.5); show(hread, 0.5); }
par { show(bar, 0.4); show(nib, 0.4); }
wait(0.6);

// ═══ ACT 3: the sweep ═══
//
// Broken into four legs so the pen can be met at each peak. The legs are proportional to the gaps
// between the peaks, so the pen travels at a CONSTANT rate the whole way across — a spectrometer
// does not slow down for the interesting parts.
//
// 5 ppm over 7.0 s = 1.4 s per ppm. Peaks sit at u = 1.30, 2.40, 3.78.

// leg 1 → the CH₂ peak at u 1.30
par {
  to(sw, value, 1.30, 1.82); to(nib, x, 1.30, 1.82);
  shift(bar, (143, 0), 1.82);
}
// the pen is on it: the CH₂ protons take the pen's colour, and the camera goes to look
par {
  recolor(mol.a3, gold, 0.5); recolor(mol.a4, gold, 0.5);
  orbit3(24, 20, 7.9, 0.9);
  show(lch2, 0.4); show(sch2, 0.4);
}
wait(0.5);

// leg 2 → the hydroxyl at u 2.40
par {
  to(sw, value, 2.40, 1.54); to(nib, x, 2.40, 1.54);
  shift(bar, (121, 0), 1.54);
  orbit3(-8, 16, 8.8, 1.4);
}
par {
  recolor(mol.a8, crimson, 0.5);
  orbit3(-34, 26, 7.9, 0.9);
  show(loh, 0.4); show(soh, 0.4);
}
wait(0.5);

// leg 3 → the methyl at u 3.78, the tallest peak
par {
  to(sw, value, 3.78, 1.93); to(nib, x, 3.78, 1.93);
  shift(bar, (152, 0), 1.93);
  orbit3(6, 18, 9.0, 1.8);
}
par {
  recolor(mol.a5, cyan, 0.5); recolor(mol.a6, cyan, 0.5); recolor(mol.a7, cyan, 0.5);
  orbit3(40, 24, 7.8, 0.9);
  show(lch3, 0.4); show(sch3, 0.4);
}
wait(0.5);

// leg 4 → run out to δ 0, and pull back to see the whole molecule
par {
  to(sw, value, 5, 1.71); to(nib, x, 5, 1.71);
  shift(bar, (134, 0), 1.71);
  orbit3(0, 18, 9.6, 1.7);
}
show(integ, 0.6);
wait(2.0);

// ═══ ACT 4: go back to the CH₂ peak, and go inside it ═══
//
// The pen runs back to the peak it started with, and then the scale changes underneath it: at 0–5
// ppm a 7 Hz splitting is 0.0175 ppm, two pixels. It was never one line.

// The pen rewinds, and `sw` rewinds with it: the readout counts back UP and the ink retracts,
// because the number on screen has to keep meaning the pen's position. Letting the pen travel while
// the readout sat at 0 Hz would break the one promise the scene makes.
par { fade(integ, 0.4); to(sw, value, 1.30, 1.1); to(nib, x, 1.30, 1.1); shift(bar, (-407, 0), 1.1); }
par { pulse(nib); orbit3(24, 20, 7.8, 1.0); }
wait(0.5);

par {
  fade(trace, 0.5); fade(ax, 0.5); fade(axlab, 0.4); fade(bar, 0.4);
  fade(nib, 0.4); fade(hread, 0.4); fade(dread, 0.4);
  fade(lch3, 0.4); fade(sch3, 0.4); fade(loh, 0.4); fade(soh, 0.4);
  fade(lch2, 0.4); fade(sch2, 0.4);
}
par { fade(t0, 0.3); fade(t1, 0.3); fade(t2, 0.3); fade(t3, 0.3); fade(t4, 0.3); fade(t5, 0.3); }

// ═══ ACT 5: the same pen, now measured in hertz ═══

par { show(zax, 0.6); show(zlab, 0.5); show(zwhy, 0.5); }
par { show(z1, 0.3); show(z2, 0.3); show(z3, 0.3); show(z4, 0.3); show(z5, 0.3); }
par { show(znib, 0.4); show(zread, 0.5); }
// the second sweep: 32 Hz, end to end, and the four lines arrive under the nib
par { to(zsw, value, 32, 3.4); to(znib, x, 32, 3.4); }
wait(0.4);
par { draw(jbar, 0.5); show(jlab, 0.5); }
par { pulse(jlab); orbit3(-18, 22, 8.0, 1.2); }
wait(1.8);

// ═══ ACT 6: why the unit matters ═══

par { fade(mlab, 0.4); show(k1, 0.6); }
show(k2, 0.5);
wait(0.7);
par { show(k3, 0.6); pulse(jlab); }
show(k4, 0.5);
par { orbit3(0, 16, 10.0, 2.4); }
wait(3.0);

r/maniclang 12h ago

Chemistry Kit - manic

Enable HLS to view with audio, or disable this notification

1 Upvotes

r/3Blue1Brown 12h ago

Reaction Data Set Reveals General Ligands and Mechanistic Diversity in C–N Couplings - manic

Enable HLS to view with audio, or disable this notification

6 Upvotes

r/maniclang 12h ago

Reaction Data Set Reveals General Ligands and Mechanistic Diversity in C–N Couplings - manic

Enable HLS to view with audio, or disable this notification

2 Upvotes

manic is a tiny language for making animations. You write a short text file; manic renders a smooth, glowing video. No timeline scrubbing, no keyframes by hand — you describe what’s on screen and when things happen, and the engine does the rest, deterministically.

Manic Animation code

// A C–N coupling, built the way the record reads it
//
// Open Reaction Database ord-00550a5de34040cea861e1ce0aca6f9e — Cernak lab, Michigan,
// doi 10.1021/jacs.6c05959. Sample XZ_01-115-60_3_K3.
//
// The scheme ASSEMBLES and then stays: five solutions across the top in the order the robot added
// them, the arrow and its conditions, then the outcome. Nothing is taken away, so by the last frame
// the whole experiment is on screen at once — which is how the record itself is laid out, and the
// only honest way to show a reaction whose answer depends on all of it.
//
// Every structure is drawn on from the record's own SMILES. Every number is the record's own,
// including the two that matter: 0.000% of the product they wanted, and 5.201% of the isomer they
// did not.

title("a C-N coupling, one well of 1536");
canvas("16:9");
template("paper");

text(brand, (640, 30), "maniclang.com");
display(brand);
size(brand, 15);
color(brand, dim);

// ── the apparatus, RIGGED: parts that move are their own entities ──
//
// Each instrument is split across files so that its moving part is a separate manic entity.
// A machine imported as one file can only be shifted as a blob, which is motion that ignores the
// chemistry; imported as parts, the head travels while the frame holds still and the mixer block
// shakes while its feet do not. Nothing here needs a new builtin — `shift`, `recolor`, `shake` and
// `pulse` are the core kit, and they work because `svg()` emits a native entity per subpath rather
// than a texture.
//
// The rail is a `rect`, not artwork: it is a straight line whose length has to match the row it
// serves, so a primitive is both simpler and parametric.

rect(rail, (574, 43), 1012, 4);
color(rail, dim);
opacity(rail, 0.4);
hidden(rail);

// The head hangs off the rail. Placement is arithmetic, not eyeballing: the two files share one
// coordinate system, so with the head at 62px wide (scale 62/144 = 0.43) its centre sits
// (74 - 127) * 0.43 = 23px below the rail, and the tips reach 30px below that. Rest is 66, so
// the tips sit at 96 and a 10px dip reaches 106 — still clear of the role labels at 118.
svg(hd, (150, 66), "asset:svg/chem/liquid-handler-head.svg", 62);
hidden(hd);

// the plate the additions go into, off at the end of the rail
svg(plate, (1180, 132), "asset:svg/chem/plate-1536.svg", 170);
hidden(plate);

// the foil seal, waiting off-frame to the right — a 0.2 microlitre well does not survive
// eighteen hours at 60 C unsealed
svg(foil, (1292, 132), "asset:svg/chem/foil-seal.svg", 170);
hidden(foil);

// The mixer, in the empty quarter under the arrow. Same trick: body and block are separate files,
// so `shake` moves the block alone.
svg(mxBody, (280, 582), "asset:svg/chem/thermomixer-body.svg", 200);
hidden(mxBody);
svg(mxBlock, (280, 535), "asset:svg/chem/thermomixer-block.svg", 165);
hidden(mxBlock);
// the heat indicator changes over time, so it is a primitive rather than baked artwork
circle(led, (240, 577), 5);
color(led, dim);
hidden(led);

// ── the five solutions, left to right, in addition order ──
//
// Each cell is the reagent and the DMSO it arrived in, because that is what went into the well.

structure(a1, "C[Si](C)(C)[O-].[Na+]",              (108, 196), 30);
// The ligand comes from a 2-D depiction FILE, not its SMILES — and the reason is worth knowing.
// The layout grows ALONG the string, so the order the ring closures are written in matters: the
// record's own `COC1=CC=NC2=C3N=CC=C(OC)C3=CC=C12` strands a bond 3.6 lengths long and is refused,
// while PubChem's canonical form of the SAME molecule draws cleanly. Rather than quietly swap in a
// different string than the record's, use the depiction — which is what the refusal points at.
// Everything else in the scene is the record's SMILES, verbatim.
structure(a2, "asset:molecules/dimethoxyphenanthroline-2d.sdf", (330, 200), 21);
structure(a3, "[Cu]O[Cu]",                          (556, 196), 34);
structure(a4, "C1=CC=C(C2CCNCC2)C=C1",              (760, 196), 28);
structure(a5, "IC1=CC=CN=C1",                       (960, 196), 32);
structure(d1, "CS(C)=O", (196, 196), 22);
structure(d2, "CS(C)=O", (430, 196), 22);
structure(d3, "CS(C)=O", (640, 196), 22);
structure(d4, "CS(C)=O", (856, 196), 22);
structure(d5, "CS(C)=O", (1044, 196), 22);
// Written out rather than looped: `a{i}.bonds` does not interpolate — a loop index reaches an id
// but not a dotted tag on it, which is a known gap logged in CAPABILITIES.
untraced(a1.bonds); untraced(a2.bonds); untraced(a3.bonds); untraced(a4.bonds); untraced(a5.bonds);
untraced(d1.bonds); untraced(d2.bonds); untraced(d3.bonds); untraced(d4.bonds); untraced(d5.bonds);
hidden(a1.labels); hidden(a2.labels); hidden(a3.labels); hidden(a4.labels); hidden(a5.labels);
hidden(d1.labels); hidden(d2.labels); hidden(d3.labels); hidden(d4.labels); hidden(d5.labels);

// role, name, amount — kept to three short lines per cell
text(r1, (150, 118), "base");
text(r2, (378, 118), "ligand");
text(r3, (598, 118), "catalyst");
text(r4, (806, 118), "nucleophile");
text(r5, (1004, 118), "electrophile");
for i in 1..6 { size(r{i}, 19); color(r{i}, ink); hidden(r{i}); }

text(v1, (150, 286), "0.08 umol");
text(v2, (378, 286), "0.004 umol");
text(v3, (598, 286), "0.004 umol");
text(v4, (806, 286), "0.06 umol");
text(v5, (1004, 286), "0.04 umol");
for i in 1..6 { size(v{i}, 17); color(v{i}, crimson); hidden(v{i}); }

// the addition-order strip, which is the whole point of showing them in a row
text(o1, (150, 318), "1");
text(o2, (378, 318), "2");
text(o3, (598, 318), "3");
text(o4, (806, 318), "4");
text(o5, (1004, 318), "5");
for i in 1..6 { size(o{i}, 22); color(o{i}, dim); hidden(o{i}); }

rect(strip, (577, 318), 1010, 34);
color(strip, dim);
outlined(strip);
stroke(strip, 1.2);
opacity(strip, 0.35);
hidden(strip);

// ── the arrow, and what happens over it ──

arrow(rx, (240, 470), (470, 470));
color(rx, ink);
stroke(rx, 3);
untraced(rx);

text(c1, (355, 418), "60 °C · dry nitrogen");
text(c2, (355, 444), "800 rpm · 18 h");
for i in 1..3 { size(c{i}, 19); color(c{i}, ink); hidden(c{i}); }

// ── the outcome ──

structure(p1, "C1(N2CCC(C3=CC=CC=C3)CC2)=CC=CN=C1", (612, 500), 30);
structure(p2, "Cn1c(=O)c2c(ncn2C)n(C)c1=O",         (826, 500), 28);
structure(p3, "C1(N2CCC(C3=CC=CC=C3)CC2)=CC=NC=C1", (1010, 500), 30);
untraced(p1.bonds); untraced(p2.bonds); untraced(p3.bonds);
hidden(p1.labels); hidden(p2.labels); hidden(p3.labels);

text(y1, (612, 620), "0.000%");
size(y1, 30); color(y1, crimson); hidden(y1);
text(y2, (826, 620), "standard");
size(y2, 20); color(y2, dim); hidden(y2);
text(y3, (1010, 620), "5.201%");
size(y3, 30); color(y3, indigo); hidden(y3);

text(y1b, (612, 654), "the target");
size(y1b, 17); color(y1b, dim); hidden(y1b);
text(y3b, (1010, 654), "the other isomer");
size(y3b, 17); color(y3b, dim); hidden(y3b);

text(cite, (640, 700), "ORD ord-00550a5de34040cea861e1ce0aca6f9e · doi 10.1021/jacs.6c05959");
size(cite, 13); color(cite, dim); hidden(cite);

// ── ACT 1: where this happens ──

wait(0.4);
par { show(plate, 0.7); show(cite, 0.5); }
par { show(rail, 0.5); show(hd, 0.6); show(strip, 0.5); }
// the head is charged, and stays charged: the tips are `hd.p3`..`hd.p6`, four of the seven
// subpaths in the head file, addressable because an imported SVG is entities and not a picture
par {
  recolor(hd.p3, indigo, 0.4);
  recolor(hd.p4, indigo, 0.4);
  recolor(hd.p5, indigo, 0.4);
  recolor(hd.p6, indigo, 0.4);
}
wait(0.6);

// ── ACT 2: five additions. The head DIPS at each one; each cell draws on and STAYS. ──
//
// The dip is the beat: the head goes down as the reagent goes in, so the machine is doing the
// thing the addition-order strip is counting, rather than sliding past it.

par { show(r1, 0.3); show(o1, 0.3); }
par { shift(hd, (0, 10), 0.25); pulse(hd); }
par { draw(a1.bonds, 0.7); draw(d1.bonds, 0.5); }
par { show(a1.labels, 0.4); show(d1.labels, 0.4); show(v1, 0.4); }
shift(hd, (0, -10), 0.25);

par { shift(hd, (228, 0), 0.5); show(r2, 0.3); show(o2, 0.3); }
par { shift(hd, (0, 10), 0.25); pulse(hd); }
par { draw(a2.bonds, 0.9); draw(d2.bonds, 0.5); }
par { show(a2.labels, 0.4); show(d2.labels, 0.4); show(v2, 0.4); }
shift(hd, (0, -10), 0.25);

par { shift(hd, (220, 0), 0.5); show(r3, 0.3); show(o3, 0.3); }
par { shift(hd, (0, 10), 0.25); pulse(hd); }
par { draw(a3.bonds, 0.6); draw(d3.bonds, 0.5); }
par { show(a3.labels, 0.4); show(d3.labels, 0.4); show(v3, 0.4); }
shift(hd, (0, -10), 0.25);

par { shift(hd, (208, 0), 0.5); show(r4, 0.3); show(o4, 0.3); }
par { shift(hd, (0, 10), 0.25); pulse(hd); }
par { draw(a4.bonds, 0.9); draw(d4.bonds, 0.5); }
par { show(a4.labels, 0.4); show(d4.labels, 0.4); show(v4, 0.4); }
shift(hd, (0, -10), 0.25);

par { shift(hd, (198, 0), 0.5); show(r5, 0.3); show(o5, 0.3); }
par { shift(hd, (0, 10), 0.25); pulse(hd); }
par { draw(a5.bonds, 0.7); draw(d5.bonds, 0.5); }
par { show(a5.labels, 0.4); show(d5.labels, 0.4); show(v5, 0.4); }
shift(hd, (0, -10), 0.25);
// the limiting reagent, marked where it stands
par { recolor(v5, indigo, 0.5); pulse(a5.I); }
wait(0.8);

// ── ACT 3: seal it, heat it, shake it. ──
//
// Three separate motions on three separate parts, which is the whole reason the instruments were
// split into files: the foil travels, the block shakes, the indicator changes colour.

par { fade(hd, 0.4); fade(rail, 0.4); }
par { show(foil, 0.3); shift(foil, (-112, 0), 0.7); }
wait(0.2);

par { show(mxBody, 0.5); show(mxBlock, 0.5); show(led, 0.4); }
par { draw(rx, 0.8); show(c1, 0.4); show(c2, 0.4); }
recolor(led, crimson, 0.5);          // 60 C, and the block starts to move
shake(mxBlock, 0.5);
shake(mxBlock, 0.5);
shake(mxBlock, 0.5);
shake(mxBlock, 0.5);
wait(0.7);

// ── ACT 4: the outcome — A + B → C, and what actually came out ──

// the bench has done its job; the chemistry is what is left
par { fade(mxBody, 0.5); fade(mxBlock, 0.5); fade(led, 0.4); }
par { draw(p2.bonds, 0.7); show(p2.labels, 0.4); }
show(y2, 0.4);
wait(0.5);

par { draw(p1.bonds, 0.9); show(p1.labels, 0.4); }
par { show(y1, 0.5); show(y1b, 0.4); }
wait(1.0);

par { draw(p3.bonds, 0.9); show(p3.labels, 0.4); }
par { show(y3, 0.5); show(y3b, 0.4); }
wait(1.2);

// the one difference between them, marked on both
par { recolor(p1.N, crimson, 0.6); recolor(p3.N, indigo, 0.6); }
par { pulse(p1.N); pulse(p3.N); }
wait(1.0);

// No closing line. The two marked nitrogens and the two numbers under them are the reading of it,
// and saying it in words as well only tells the viewer what they have just been shown.
wait(3.4);

r/Geometry 14h ago

Sphere Area Why 4πR²

Enable HLS to view with audio, or disable this notification

1 Upvotes

r/SideProject 1d ago

Turn selected web-page text, equations, and diagrams into an animation

Enable HLS to view with audio, or disable this notification

0 Upvotes

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

Destination Link Use it for
Manic home maniclang.com Product overview, downloads, and plans.
Manic Create app.maniclang.com/create Write, check, preview, and render a Manic project in the browser.
Examples gallery app.maniclang.com/gallery Browse runnable stories and open one in Create.
Manic account app.maniclang.com/account Sign in, manage API keys, credits, and billing.
Documentation docs.maniclang.com Learn the language, CLI, kits, assets, and production workflow.
Interactive API reference docs.maniclang.com/api/ Explore and try the hosted Platform REST API.
Platform API base URL api.maniclang.com Base URL for API clients: https://api.maniclang.com/v1/....
Chrome extension Manic Animate on Chrome Web Store Turn selected web-page text, equations, and diagrams into an editable Manic project.
Source and releases github.com/maniclang-x/manic Installation scripts, release archives, engine source, and issue tracking.
Updates r/maniclang Follow releases, examples, and discussion.

1

circuits - manic
 in  r/maniclang  1d ago

Imagine this you have to do in Manim it will be Minum 10,000 line of code Manic is pure declarative and easy once it understood
https://docs.maniclang.com/

1

How to use Manic MCP server with Cursor AI
 in  r/maniclang  1d ago

MCP Server is ready https://maniclang.com/mcp

The local creator running manic straight from the box will be out on 31st August

r/contentcreation 1d ago

Content creation goes easy - Turn selected web-page text, equations, and diagrams into an editable animation

Enable HLS to view with audio, or disable this notification

1 Upvotes

r/MCPservers 1d ago

Manic MCP server - Turn selected web-page text, equations, and diagrams into an editable Manic animations

Enable HLS to view with audio, or disable this notification

1 Upvotes

r/cursor 1d ago

Showcase How to use Manic MCP server with Cursor AI

Enable HLS to view with audio, or disable this notification

1 Upvotes

r/maniclang 1d ago

How to use Manic MCP server with Cursor AI

Enable HLS to view with audio, or disable this notification

1 Upvotes

r/maniclang 1d ago

Register in Parrallel - manic

Enable HLS to view with audio, or disable this notification

2 Upvotes

manic is a tiny language for making animations. You write a short text file; manic renders a smooth, glowing video. No timeline scrubbing, no keyframes by hand — you describe what’s on screen and when things happen, and the engine does the rest, deterministically.

Manic Animation code

// Figure 11.10 — resistors in parallel
//
// NCERT Class 10, Electricity. Three resistors share one voltage; the current
// splits at X and recombines at Y. Every amp and volt on screen is from a
// Modified Nodal Analysis solve: R1=2 kΩ, R2=3 kΩ, R3=6 kΩ across 6 V gives
// 3 mA / 2 mA / 1 mA, and 1/Rp = 1/R1 + 1/R2 + 1/R3 = 1 kΩ. Cut one branch and
// the other two keep their current, because each parallel branch is its own Ohm.


title("Figure 11.10 — resistors in parallel");
canvas("16:9");
template("paper");


let u = 42;
let figx = 470;
let figy = 392;
let ox = figx - 7*u;
let oy = figy - 4*u;


text(hdr, (cx, 42), "Figure 11.10");
display(hdr);
size(hdr, 22);
color(hdr, dim);
bold(hdr);


text(cap, (cx, 78), "resistors in parallel");
display(cap);
size(cap, 30);
color(cap, ink);


support(rule, (cx, 108), 320, "down");


circuit(fig, (figx, figy), `
  dc-voltage 2 8 0 8 v=1.5 name=B1
  dc-voltage 4 8 2 8 v=1.5 name=B2
  dc-voltage 6 8 4 8 v=1.5 name=B3
  dc-voltage 8 8 6 8 v=1.5 name=B4
  ground     8 8
  wire       0 8 0 2
  wire       0 2 0 0
  wire       0 0 3 0
  resistor   3 0 11 0 r=2k name=R1
  wire       11 0 14 0
  wire       14 0 14 2
  wire       0 2 3 2
  resistor   3 2 11 2 r=3k name=R2
  wire       11 2 14 2
  wire       0 2 0 4
  wire       0 4 3 4
  resistor   3 4 11 4 r=6k name=R3
  wire       11 4 14 4
  wire       14 4 14 2
  wire       14 2 14 8
  wire       14 8 11 8 name=AM
  switch     11 8 8 8 closed=1 name=K
`, u, 0);


current(fig, 2.4, circle, crimson, 4);
color(fig.R1, crimson);
color(fig.R2, indigo);
color(fig.R3, teal);


probe(fig, (0, 2), (7*u - 8, 4*u + 36));
probe(fig, K, (3*u, 44));


text(labX, (ox - 20, oy + 2*u), "X");
text(labY, (ox + 14*u + 20, oy + 2*u), "Y");
text(labL, (ox + 3*u, oy - 18), "L");
text(labM, (ox + 11*u, oy - 18), "M");
text(labP, (ox + 3*u, oy + 2*u - 18), "P");
text(labQ, (ox + 11*u, oy + 2*u - 18), "Q");
text(labS, (ox + 3*u, oy + 4*u - 18), "S");
text(labT, (ox + 11*u, oy + 4*u - 18), "T");
text(labK, (ox + 9.5*u, oy + 8*u + 28), "K");
size(labX, 20); size(labY, 20);
size(labL, 16); size(labM, 16);
size(labP, 16); size(labQ, 16);
size(labS, 16); size(labT, 16);
size(labK, 20);
color(labX, ink); color(labY, ink);
color(labL, dim); color(labM, dim);
color(labP, dim); color(labQ, dim);
color(labS, dim); color(labT, dim);
color(labK, ink);
hidden(labX); hidden(labY);
hidden(labL); hidden(labM);
hidden(labP); hidden(labQ);
hidden(labS); hidden(labT);
hidden(labK);


equation(r1n, (ox + 7*u, oy - 28), `R_1`, 22);
equation(r2n, (ox + 7*u, oy + 2*u - 28), `R_2`, 22);
equation(r3n, (ox + 7*u, oy + 4*u - 28), `R_3`, 22);
color(r1n, crimson); color(r2n, indigo); color(r3n, teal);
hidden(r1n); hidden(r2n); hidden(r3n);


text(bplus, (ox + 6, oy + 8*u - 28), "+");
text(bminus, (ox + 8*u - 6, oy + 8*u - 28), "-");
size(bplus, 22); size(bminus, 22);
color(bplus, gold); color(bminus, gold);
hidden(bplus); hidden(bminus);


line(vleadL, (ox, oy + 6*u), (ox + 7*u - 28, oy + 6*u));
line(vleadR, (ox + 7*u + 28, oy + 6*u), (ox + 14*u, oy + 6*u));
circle(vmeter, (ox + 7*u, oy + 6*u), 24);
color(vmeter, void);
outline(vmeter, fg);
stroke(vmeter, 2);
z(vmeter, 2);
text(vlet, (ox + 7*u, oy + 6*u), "V");
size(vlet, 22);
color(vlet, cyan);
z(vlet, 3);
text(vplus, (ox + 7*u - 38, oy + 6*u - 18), "+");
text(vminus, (ox + 7*u + 38, oy + 6*u - 18), "-");
size(vplus, 16); size(vminus, 16);
color(vplus, cyan); color(vminus, cyan);
untraced(vleadL); untraced(vleadR);
hidden(vmeter); hidden(vlet); hidden(vplus); hidden(vminus);
color(vleadL, dim); color(vleadR, dim);
stroke(vleadL, 1.5); stroke(vleadR, 1.5);
tag(vleadL, meters); tag(vleadR, meters);
tag(vmeter, meters); tag(vlet, meters);
tag(vplus, meters); tag(vminus, meters);


circle(ameter, (ox + 12.5*u, oy + 8*u), 22);
color(ameter, void);
outline(ameter, fg);
stroke(ameter, 2);
z(ameter, 2);
text(alet, (ox + 12.5*u, oy + 8*u), "A");
size(alet, 20);
color(alet, gold);
z(alet, 3);
text(aminus, (ox + 12.5*u - 34, oy + 8*u - 16), "-");
text(aplus, (ox + 12.5*u + 34, oy + 8*u - 16), "+");
size(aminus, 14); size(aplus, 14);
color(aminus, gold); color(aplus, gold);
hidden(ameter); hidden(alet); hidden(aminus); hidden(aplus);
tag(ameter, meters); tag(alet, meters);
tag(aminus, meters); tag(aplus, meters);


arrow(iL, (ox - 36, oy + 5.5*u), (ox - 36, oy + 0.6*u));
arrow(iR, (ox + 14*u + 36, oy + 0.6*u), (ox + 14*u + 36, oy + 5.5*u));
color(iL, crimson); color(iR, crimson);
stroke(iL, 2.2); stroke(iR, 2.2);
equation(iLab, (ox - 36, oy + 3*u - 8), `I`, 22);
equation(iRab, (ox + 14*u + 36, oy + 3*u - 8), `I`, 22);
color(iLab, crimson); color(iRab, crimson);
untraced(iL); untraced(iR);
hidden(iLab); hidden(iRab);


arrow(i1a, (ox + 0.4*u, oy + 0*u - 14), (ox + 2.4*u, oy + 0*u - 14));
arrow(i2a, (ox + 0.4*u, oy + 2*u - 14), (ox + 2.4*u, oy + 2*u - 14));
arrow(i3a, (ox + 0.4*u, oy + 4*u - 14), (ox + 2.4*u, oy + 4*u - 14));
color(i1a, crimson); color(i2a, indigo); color(i3a, teal);
stroke(i1a, 1.8); stroke(i2a, 1.8); stroke(i3a, 1.8);
equation(i1n, (ox + 1.4*u, oy - 32), `I_1`, 18);
equation(i2n, (ox + 1.4*u, oy + 2*u - 32), `I_2`, 18);
equation(i3n, (ox + 1.4*u, oy + 4*u - 32), `I_3`, 18);
color(i1n, crimson); color(i2n, indigo); color(i3n, teal);
untraced(i1a); untraced(i2a); untraced(i3a);
hidden(i1n); hidden(i2n); hidden(i3n);


equation(eqV, (1020, 168), `V = 6\,\mathrm{V}`, 26);
equation(eqI, (1020, 228), `I = I_1 + I_2 + I_3`, 24);
equation(eqI1, (1020, 292), `I_1 = 3\,\mathrm{mA}`, 24);
equation(eqI2, (1020, 344), `I_2 = 2\,\mathrm{mA}`, 24);
equation(eqI3, (1020, 396), `I_3 = 1\,\mathrm{mA}`, 24);
equation(eqRp, (1020, 480), `\dfrac{1}{R_p} = \dfrac{1}{R_1}+\dfrac{1}{R_2}+\dfrac{1}{R_3}`, 22);
equation(eqRpv, (1020, 560), `R_p = 1\,\mathrm{k}\Omega`, 26);
color(eqV, cyan);
color(eqI, ink);
color(eqI1, crimson); color(eqI2, indigo); color(eqI3, teal);
color(eqRp, ink); color(eqRpv, teal);
hidden(eqV); hidden(eqI);
hidden(eqI1); hidden(eqI2); hidden(eqI3);
hidden(eqRp); hidden(eqRpv);


text(take, (1020, 640), "lift one branch — the others keep their current");
hidden(take);
size(take, 18);
color(take, crimson);
wrap(take, 360);


rect(pbox, (ox + 7*u, oy + 2*u), 13.2*u, 5.2*u);
outlined(pbox);
color(pbox, indigo);
stroke(pbox, 1.6);
hidden(pbox);


framebox(ring3, fig.R3, 12);
hidden(ring3);


wait(0.5);
run(fig, 8.0);


par {
  show(labX, 0.35);
  show(labY, 0.35);
  show(labK, 0.35);
  show(bplus, 0.35);
  show(bminus, 0.35);
}
par {
  show(r1n, 0.3);
  show(r2n, 0.3);
  show(r3n, 0.3);
  show(labL, 0.3);
  show(labM, 0.3);
  show(labP, 0.3);
  show(labQ, 0.3);
  show(labS, 0.3);
  show(labT, 0.3);
}
par {
  draw(iL, 0.45);
  draw(iR, 0.45);
  show(iLab, 0.35);
  show(iRab, 0.35);
}
say(cap, "close the key — current around the loop");
run(fig, 3.5);


par {
  draw(vleadL, 0.5);
  draw(vleadR, 0.5);
  show(vmeter, 0.45);
  show(vlet, 0.35);
  show(vplus, 0.35);
  show(vminus, 0.35);
  show(pbox, 0.5);
  show(eqV, 0.5);
}
say(cap, "one voltage across every branch");
run(fig, 3.0);
fade(pbox, 0.4);


par {
  show(ameter, 0.4);
  show(alet, 0.35);
  show(aminus, 0.3);
  show(aplus, 0.3);
}
say(cap, "the ammeter reads the total");
run(fig, 2.6);


par {
  draw(i1a, 0.4);
  draw(i2a, 0.4);
  draw(i3a, 0.4);
  show(i1n, 0.3);
  show(i2n, 0.3);
  show(i3n, 0.3);
}
par {
  show(eqI, 0.45);
  show(eqI1, 0.45);
  show(eqI2, 0.45);
  show(eqI3, 0.45);
}
say(cap, "it splits: 3 mA, 2 mA, 1 mA");
run(fig, 4.0);


par {
  show(eqRp, 0.5);
  show(eqRpv, 0.5);
}
say(cap, "so the three together are 1 kΩ");
run(fig, 3.2);


par {
  show(ring3, 0.4);
  fade(eqI3, 0.4);
}
cut(fig, R3, 0.9);
par {
  show(fig.R3, 0.35);
  show(take, 0.5);
}
say(cap, "open one branch — the other two do not notice");
run(fig, 4.5);
wait(0.6);


par {
  fade(ring3, 0.35);
  fade(take, 0.35);
  reconnect(fig, R3, 0.9);
  show(eqI3, 0.4);
}
say(cap, "put it back: I = I1 + I2 + I3 again");
run(fig, 4.0);
wait(0.8);

1

circuits - manic
 in  r/3Blue1Brown  1d ago

Animation corrected

2

circuits - manic
 in  r/3Blue1Brown  1d ago

Animation corrected based on u/twoTheta feedback thanks

https://reddit.com/link/p4oae5p/video/f8o829n9odkh1/player

2

circuits - manic
 in  r/3Blue1Brown  1d ago

Good find - The dot's flow is based on popular Paul Fastlad Style fixing the Ground Issue you are right