motion-library 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +37 -2
  3. package/anims/basics/anticipation.js +56 -0
  4. package/anims/basics/card-flip.js +39 -0
  5. package/anims/basics/easing-race.js +57 -0
  6. package/anims/basics/fade-scale.js +44 -0
  7. package/anims/basics/stagger-grid.js +43 -0
  8. package/anims/chrome/black-hole.js +81 -0
  9. package/anims/chrome/cloth-reveal.js +156 -0
  10. package/anims/chrome/dna-helix.js +52 -0
  11. package/anims/chrome/dust-bust.js +109 -0
  12. package/anims/chrome/extruded-type.js +64 -0
  13. package/anims/chrome/fractal-tree.js +75 -0
  14. package/anims/chrome/gem.js +111 -0
  15. package/anims/chrome/geode.js +102 -0
  16. package/anims/chrome/glass-lens.js +73 -0
  17. package/anims/chrome/glass-prism.js +95 -0
  18. package/anims/chrome/gyroid.js +69 -0
  19. package/anims/chrome/holo-card.js +52 -0
  20. package/anims/chrome/infinite-tunnel.js +81 -0
  21. package/anims/chrome/inflated-type.js +141 -0
  22. package/anims/chrome/isometric-city.js +97 -0
  23. package/anims/chrome/jelly-cube.js +329 -0
  24. package/anims/chrome/klein-bottle.js +80 -0
  25. package/anims/chrome/liquid-metal.js +94 -0
  26. package/anims/chrome/low-poly-planet.js +163 -0
  27. package/anims/chrome/mandelbulb.js +79 -0
  28. package/anims/chrome/melting-logo.js +126 -0
  29. package/anims/chrome/menger-sponge.js +79 -0
  30. package/anims/chrome/neon-geodesic.js +149 -0
  31. package/anims/chrome/paper-fold.js +135 -0
  32. package/anims/chrome/path-spheres.js +112 -0
  33. package/anims/chrome/point-globe.js +81 -0
  34. package/anims/chrome/portal.js +105 -0
  35. package/anims/chrome/rubiks-cube.js +74 -0
  36. package/anims/chrome/sdf-morph.js +129 -0
  37. package/anims/chrome/shatter.js +120 -0
  38. package/anims/chrome/soap-bubble.js +86 -0
  39. package/anims/chrome/terrain-flyover.js +78 -0
  40. package/anims/chrome/tesseract.js +148 -0
  41. package/anims/chrome/torus-knot.js +89 -0
  42. package/anims/chrome/volumetric-clouds.js +70 -0
  43. package/anims/flow/bio-shore.js +140 -0
  44. package/anims/flow/calligraphy.js +116 -0
  45. package/anims/flow/chladni.js +152 -0
  46. package/anims/flow/cloud-chamber.js +101 -0
  47. package/anims/flow/confetti.js +46 -0
  48. package/anims/flow/curl-flow.js +52 -0
  49. package/anims/flow/dandelion.js +80 -0
  50. package/anims/flow/dla-crystal.js +57 -0
  51. package/anims/flow/drone-show.js +357 -0
  52. package/anims/flow/embers.js +63 -0
  53. package/anims/flow/firefly-sync.js +107 -0
  54. package/anims/flow/galaxy.js +69 -0
  55. package/anims/flow/galton-board.js +155 -0
  56. package/anims/flow/gpu-particles.js +73 -0
  57. package/anims/flow/harmonograph.js +61 -0
  58. package/anims/flow/holi.js +153 -0
  59. package/anims/flow/iron-filings.js +112 -0
  60. package/anims/flow/lightning.js +72 -0
  61. package/anims/flow/murmuration.js +105 -0
  62. package/anims/flow/orbitals.js +195 -0
  63. package/anims/flow/particle-life.js +87 -0
  64. package/anims/flow/physarum.js +171 -0
  65. package/anims/flow/pin-art.js +181 -0
  66. package/anims/flow/rainbow.js +127 -0
  67. package/anims/flow/roche-limit.js +161 -0
  68. package/anims/flow/sand-mandala.js +121 -0
  69. package/anims/flow/scramble-crossing.js +152 -0
  70. package/anims/flow/snowfall.js +84 -0
  71. package/anims/flow/sparks.js +71 -0
  72. package/anims/flow/spider-web.js +123 -0
  73. package/anims/flow/starry-night.js +121 -0
  74. package/anims/flow/states-of-matter.js +113 -0
  75. package/anims/flow/strange-attractor.js +54 -0
  76. package/anims/flow/wheat-field.js +186 -0
  77. package/anims/flow/wind-tunnel.js +227 -0
  78. package/anims/fluid/bubbles.js +152 -0
  79. package/anims/fluid/caustics.js +45 -0
  80. package/anims/fluid/dripping-text.js +53 -0
  81. package/anims/fluid/ferrofluid.js +56 -0
  82. package/anims/fluid/fire-smoke.js +83 -0
  83. package/anims/fluid/honey-drip.js +167 -0
  84. package/anims/fluid/ink-drop.js +50 -0
  85. package/anims/fluid/ink-fluid.js +125 -0
  86. package/anims/fluid/lava-lamp.js +56 -0
  87. package/anims/fluid/liquid-distort.js +64 -0
  88. package/anims/fluid/liquid-glass-fill.js +53 -0
  89. package/anims/fluid/marbling.js +53 -0
  90. package/anims/fluid/mercury.js +177 -0
  91. package/anims/fluid/ocean-waves.js +50 -0
  92. package/anims/fluid/paint-swirl.js +153 -0
  93. package/anims/fluid/raindrops-glass.js +60 -0
  94. package/anims/fluid/ripples.js +57 -0
  95. package/anims/fluid/sph-water.js +75 -0
  96. package/anims/fluid/underwater.js +55 -0
  97. package/anims/fluid/water-surface-3d.js +98 -0
  98. package/anims/hover/cursor-trail.js +37 -0
  99. package/anims/hover/magnetic.js +41 -0
  100. package/anims/hover/ripple.js +31 -0
  101. package/anims/hover/spotlight.js +31 -0
  102. package/anims/hover/tilt.js +36 -0
  103. package/anims/micro/arc-spinner.js +22 -0
  104. package/anims/micro/like-burst.js +42 -0
  105. package/anims/micro/skeleton.js +36 -0
  106. package/anims/micro/submit-success.js +45 -0
  107. package/anims/micro/toggle.js +35 -0
  108. package/anims/morph/gooey.js +36 -0
  109. package/anims/morph/icon-morph.js +45 -0
  110. package/anims/morph/line-draw.js +34 -0
  111. package/anims/morph/liquid-button.js +53 -0
  112. package/anims/morph/shape-morph.js +61 -0
  113. package/anims/registry-more.js +327 -0
  114. package/anims/registry.js +294 -0
  115. package/anims/scroll/parallax.js +50 -0
  116. package/anims/scroll/pinned-track.js +40 -0
  117. package/anims/scroll/scrub-reveal.js +34 -0
  118. package/anims/scroll/velocity-skew.js +38 -0
  119. package/anims/scroll/zoom-through.js +41 -0
  120. package/anims/shaders/aurora.js +47 -0
  121. package/anims/shaders/dither.js +69 -0
  122. package/anims/shaders/domain-warp.js +52 -0
  123. package/anims/shaders/guilloche.js +42 -0
  124. package/anims/shaders/kaleidoscope.js +64 -0
  125. package/anims/shaders/reaction-diffusion.js +85 -0
  126. package/anims/shaders/rgb-glitch.js +56 -0
  127. package/anims/shaders/truchet.js +57 -0
  128. package/anims/shaders/voronoi.js +50 -0
  129. package/anims/springs/ball-collisions.js +47 -0
  130. package/anims/springs/pendulum-wave.js +35 -0
  131. package/anims/springs/rubber-band.js +50 -0
  132. package/anims/springs/squash-bounce.js +50 -0
  133. package/anims/springs/verlet-rope.js +46 -0
  134. package/anims/text/letter-rise.js +40 -0
  135. package/anims/text/line-mask.js +26 -0
  136. package/anims/text/scramble.js +32 -0
  137. package/anims/text/typewriter.js +35 -0
  138. package/anims/text/vf-wave.js +26 -0
  139. package/anims/transitions/circle-reveal.js +42 -0
  140. package/anims/transitions/push-slide.js +40 -0
  141. package/anims/transitions/shared-expand.js +43 -0
  142. package/anims/transitions/staircase-wipe.js +41 -0
  143. package/anims/transitions/view-transition.js +43 -0
  144. package/bin.mjs +155 -0
  145. package/js/code.js +122 -0
  146. package/package.json +10 -4
@@ -0,0 +1,327 @@
1
+ // Round 2: the deep end of Flow & particles, Fluid, and Chrome & 3D.
2
+ const P = (min, max, step, label) => ({ min, max, step, label });
3
+
4
+ export const MORE = [
5
+ // ── Flow & particles
6
+ { id: 'galaxy', cat: 'flow', title: 'Spiral galaxy', kind: 'webgl', posterAt: 1800,
7
+ trick: '40,000 stars on log-spiral arms, rotating differentially',
8
+ notes: ['Each star is placed on one of the arms by a logarithmic spiral, then scattered so the arms read as density, not lines.', 'Inner stars orbit faster than outer ones (a flat rotation curve), which is what winds the arms over time.', 'Points add their light together, so the dense core blooms on its own.'],
9
+ params: { stars: P(5000, 80000, 1000, 'Stars'), arms: P(2, 6, 1, 'Arms'), twist: P(0.5, 4, 0.1, 'Arm twist') } },
10
+ { id: 'strange-attractor', cat: 'flow', title: 'Strange attractor', kind: 'canvas', posterAt: 2600,
11
+ trick: 'Thousands of particles riding the Aizawa equations in 3D',
12
+ notes: ['A strange attractor is a set of three equations whose trajectories never repeat but never escape: the Aizawa system draws a glowing apple-like shell with a spine.', 'Thousands of particles are integrated each frame and projected through a slowly turning 3D camera.', 'Trails come from fading the canvas a little each frame instead of clearing it.'],
13
+ params: { particles: P(500, 6000, 100, 'Particles'), speed: P(0.2, 3, 0.1, 'Speed') } },
14
+ { id: 'physarum', cat: 'flow', title: 'Slime mould maps Tokyo', kind: 'webgl', posterAt: 14000,
15
+ trick: 'A quarter of a million agents: sense, turn, step, deposit',
16
+ notes: ['Each agent senses the trail ahead-left, ahead and ahead-right, turns toward the strongest, steps and lays down more trail. The trail spreads and fades every step, so only well-used routes survive. That is how the real Physarum polycephalum forages.', 'In 2010 researchers put oat flakes where the cities around Tokyo are, and the mould grew a network much like the rail system. Here the flakes leak food into the trail map, the lit bay is avoided, and a fine mesh tightens into trunk lines between the cities.', 'The trail is drawn as a wet height field: its slope shades the veins and gives them highlights, and a slow grain streams through the tubes.'],
17
+ params: { agents: P(128, 768, 64, 'Grid (agents = grid²)'), sensor: P(0.15, 1, 0.01, 'Sensor angle'), decay: P(0.8, 0.98, 0.005, 'Trail decay') } },
18
+ { id: 'lightning', cat: 'flow', title: 'Lightning arcs', kind: 'canvas', posterAt: 1300, pointer: true,
19
+ trick: 'Midpoint displacement, recursive branches, glow and afterimage',
20
+ notes: ['Each bolt starts as one straight line; its midpoint is pushed sideways at random, then each half is split again, and so on: a fractal path.', 'Some segments fork off into fading branches.', 'Two passes give the glow: a wide blurred stroke under a thin white core. Bolts re-strike rapidly and leave an afterimage.'],
21
+ params: { branches: P(0, 1, 0.05, 'Branching'), rate: P(2, 30, 1, 'Strikes / second') } },
22
+ { id: 'starry-night', cat: 'flow', title: 'Starry night in brushstrokes', kind: 'canvas', posterAt: 9000, pointer: true,
23
+ trick: 'One stream function sets both the stroke direction and the colour bands',
24
+ notes: ['The sky is a single stream function: a long wave across it, two swirls locked together in the middle, and a bump round every star and the moon. Strokes follow its contour lines, so each one curls with the swirl it sits in.', 'Colour bands are read from the same function, so stripes of colour run along the strokes, as in Van Gogh’s paintings. The cypress, the hills and the village each have their own flow and palette.', 'Every stroke is four bristles side by side with a lighter edge for the ridge of paint. A broad underpainting goes down first, then finer strokes keep painting over it as the swirls slowly turn. The pointer stirs the sky.'],
25
+ params: { strokes: P(60, 900, 20, 'Strokes per frame'), size: P(0.5, 2, 0.05, 'Brush size'), swirl: P(0, 2, 0.05, 'Swirl') } },
26
+ { id: 'iron-filings', cat: 'flow', title: 'Iron filings', kind: 'canvas', posterAt: 14000, pointer: true,
27
+ trick: 'Filings turn to the field and hop onto contours of the flux function',
28
+ notes: ['Thousands of steel slivers lie on paper over two hidden bar magnets. Each one turns to lie along the local field, like a tiny compass with no head or tail.', 'When the paper is tapped the filings hop and settle onto field lines: they are pulled across the field toward the nearest contour of the flux function (in 2D, the sum of each pole’s angle times its charge), which is where real filings chain up.', 'Near the poles the strong field grips them; far away the shake wins. One magnet drifts on its own; drag either and watch the pattern rebuild over the next taps.'],
29
+ params: { filings: P(2000, 16000, 500, 'Filings'), lines: P(12, 60, 2, 'Field lines'), taps: P(0.3, 3, 0.1, 'Tap rate') } },
30
+ { id: 'wheat-field', cat: 'flow', title: 'Wheat field in wind', kind: 'webgl', posterAt: 6000, pointer: true,
31
+ trick: 'A hundred thousand bent strips in one instanced draw, backlit by a low sun',
32
+ notes: ['Every stalk is a thin strip that swells into an ear and a tuft of awns. One instanced draw places them all, spread so their density on screen stays even.', 'The vertex shader bends each stalk by the wind at its root: a slow sway plus gusts, which are blobs of noise sliding downwind, so you can follow a gust across the field. A bent ear shows the sun more of itself, so gusts roll across as waves of gold.', 'The sun is low and ahead, so you see the shaded side of each stalk while light comes through the ears and catches their edges. The pointer parts the stalks.'],
33
+ params: { stalks: P(20000, 200000, 5000, 'Stalks'), wind: P(0.2, 2.5, 0.05, 'Wind'), gusts: P(0, 2, 0.05, 'Gusts') } },
34
+ { id: 'galton-board', cat: 'flow', title: 'Galton board', kind: 'canvas', posterAt: 19000,
35
+ trick: 'A dozen coin tosses per bead add up to a bell curve',
36
+ notes: ['Beads pour through a lattice of brass pegs. At each row a bead goes left or right, more or less a coin toss, so where it lands is the sum of a dozen tosses, and such sums pile up as a binomial distribution, which tends to the normal curve.', 'The beads are real little bodies: they bounce off pegs that soak up energy, push against each other through a spatial hash and stack in the bins. The dashed line is what theory expects for the beads poured so far.', 'Bead colour shifts as the run goes on, so each bin shows its history in layers. Then the floor opens and the run starts again.'],
37
+ params: { beads: P(100, 700, 20, 'Beads'), rows: P(6, 16, 1, 'Rows'), rate: P(5, 80, 1, 'Beads per second') } },
38
+ { id: 'pin-art', cat: 'flow', title: 'Pin art', kind: 'webgl', posterAt: 4500, pointer: true,
39
+ trick: 'Thousands of instanced steel pins that only ever slide one way',
40
+ notes: ['The pin-screen toy in 3D: thousands of steel pins slide through a board. A hand, then a face, then a ripple pushes up from behind, and every pin it touches slides forward, so the shape stands out in relief.', 'Pins only move one way, so when the shape pulls back its imprint stays; then a sweep flattens the board for the next one. Press with the pointer to push pins back in and draw into the relief.', 'Each pin is an instanced cylinder with a domed head, shaded like polished steel in a studio; a raking light from the upper left lets taller pins shade the ones below them.'],
41
+ params: { pins: P(32, 110, 2, 'Pins across'), depth: P(0.4, 2, 0.05, 'Relief depth'), speed: P(0.4, 2, 0.1, 'Speed') } },
42
+ { id: 'confetti', cat: 'flow', title: '3D paper confetti', kind: 'canvas', posterAt: 900,
43
+ trick: 'Squash by cos(spin) to fake a 3D tumble, front and back colours',
44
+ notes: ['Each piece spins about its own axis. Scaling its height by the cosine of the spin angle fakes the 3D tumble, and the sign picks the front or back colour.', 'Paper is mostly drag: strong air resistance stops the burst quickly, then a sideways flutter carries the pieces down.', 'Two cannons fire every few seconds.'],
45
+ params: { pieces: P(40, 400, 10, 'Pieces'), gravity: P(0.2, 2, 0.05, 'Gravity') } },
46
+ { id: 'firefly-sync', cat: 'flow', title: 'Firefly sync', kind: 'canvas', posterAt: 22360, pointer: true,
47
+ trick: 'Pulse-coupled clocks: every flash nudges the neighbours forward',
48
+ notes: ['Each firefly is a clock that flashes when it runs out, then resets. When it sees a neighbour flash it nudges its own clock forward a little, and if that tips it over it flashes at once: the Mirollo–Strogatz model.', 'Nobody leads. Small groups lock together, waves of flashes roll through the trees, and in the end the whole forest blinks as one. After a while a gust scrambles part of it and it starts again; the pointer startles the ones nearby.', 'Fireflies sit between three layers of trunks; the nearest are drawn as out-of-focus discs, and when most of the forest flashes at once the air itself lights up a little.'],
49
+ params: { fireflies: P(100, 1500, 50, 'Fireflies'), coupling: P(0, 0.2, 0.005, 'Coupling'), spread: P(0, 0.2, 0.005, 'Clock spread') } },
50
+ { id: 'spider-web', cat: 'flow', title: 'Spider web at dawn', kind: 'canvas', posterAt: 28000,
51
+ trick: 'Built in a real orb weaver’s order, then weighed down by dew',
52
+ notes: ['An orb weaver builds in the order real ones do: a frame from the twigs, spokes out from the hub (alternating sides to keep the tension even), then the sticky capture spiral, laid from the outside in as straight steps between spokes.', 'Near the rim the spiral follows the frame; toward the hub it rounds into circles. Then dew condenses: each strand sags under its beads, and every bead is a lens showing the bright sky upside down.', 'Silk glints where it lies across the light, with a faint rainbow sheen. A breeze rocks the web; then a fresh morning and a fresh web.'],
53
+ params: { spokes: P(12, 48, 1, 'Spokes'), turns: P(10, 60, 1, 'Spiral turns'), breeze: P(0, 3, 0.1, 'Breeze') } },
54
+ { id: 'gpu-particles', cat: 'flow', title: 'GPU particles (262k)', kind: 'webgl', posterAt: 2500,
55
+ trick: 'Positions live in a float texture; a shader moves them all',
56
+ notes: ['A quarter of a million particles is too many for JavaScript, so their positions live in a float texture and a fragment shader moves every one through 3D curl noise each frame.', 'Two textures swap roles each frame (ping-pong): read from one, write to the other.', 'Drawing reads each particle back with texelFetch and adds its light, so dense filaments glow.'],
57
+ params: { size: P(128, 1024, 64, 'Grid (particles = size²)'), speed: P(0.2, 3, 0.1, 'Speed') } },
58
+ { id: 'orbitals', cat: 'flow', title: 'Hydrogen orbitals', kind: 'webgl', posterAt: 4500,
59
+ trick: 'Metropolis sampling of the exact wavefunction, coloured by its phase',
60
+ notes: ['A quarter of a million particles each stand for a place the electron might be. Every frame each proposes a small random step and keeps it with probability |ψ(new)|² ÷ |ψ(old)|², so the cloud’s density always follows the true wavefunction.', 'ψ is the exact hydrogen solution, a Laguerre polynomial times a spherical harmonic, evaluated in the shader. Colour is its complex phase, which turns at a rate set by the energy.', 'Between orbitals the atom is in a superposition of both. The two parts turn at different rates, so the cloud sloshes as it flows into the next shape; that sloshing is how an atom emits light.'],
61
+ params: { grid: P(128, 768, 64, 'Grid (particles = grid²)'), speed: P(0.2, 3, 0.1, 'Speed'), glow: P(0, 2, 0.1, 'Glow') } },
62
+ { id: 'roche-limit', cat: 'flow', title: 'Roche limit', kind: 'webgl', posterAt: 34000,
63
+ trick: 'A moon spirals inside the Roche limit, sheds two streams and becomes a ring',
64
+ notes: ['A rubble-pile moon spirals in toward an ice giant. Its own gravity holds it together, but the planet pulls harder on the near side than the far side, and that difference (the tide) grows as 1/d³.', 'Inside about 2.4 planet radii the tide wins: the moon stretches and sheds two streams, inward and outward. Inner rubble orbits faster than outer rubble, so the streams shear and wind round the planet until they close into a ring.', 'Twenty thousand particles, each lit by the sun and dark in the planet’s shadow; hidden behind the planet by a ray test; the planet itself is ray traced, and the ring shadows its cloud tops.'],
65
+ params: { particles: P(4000, 40000, 1000, 'Particles'), speed: P(0.3, 2.5, 0.1, 'Speed'), glow: P(0.2, 2, 0.1, 'Brightness') } },
66
+ // ── Fluid
67
+ { id: 'fire-smoke', cat: 'fluid', title: 'Fire & smoke', kind: 'webgl', posterAt: 2800,
68
+ trick: 'A fluid solver where heat rises, cools and turns to smoke',
69
+ notes: ['The same Navier–Stokes solver as the ink, plus buoyancy: hot cells push the velocity field upward and smoke weighs it down.', 'Heat cools a little every step as it travels, so flames shrink into smoke trails, and vorticity keeps the licks curling.', 'Colour comes from temperature through a blackbody ramp: white-yellow at the core, red at the tips, grey smoke above.'],
70
+ params: { heat: P(0.2, 2, 0.05, 'Heat'), cooling: P(0.85, 0.99, 0.005, 'Heat persistence'), curl: P(0, 50, 1, 'Curl') } },
71
+ { id: 'caustics', cat: 'fluid', title: 'Pool caustics', kind: 'webgl', posterAt: 1200,
72
+ trick: 'Iterated wave distortion; bright where light converges',
73
+ notes: ['Caustics are the bright threads on a pool floor where the rippling surface focuses sunlight.', 'The shader bends each sample point through several layers of moving waves and measures how tightly points bunch together; tight bunches are bright.', 'Red, green and blue are sampled at slightly different offsets, so the threads carry a faint rainbow fringe.'],
74
+ params: { speed: P(0, 2, 0.05, 'Speed'), intensity: P(0.2, 2, 0.05, 'Intensity') } },
75
+ { id: 'ferrofluid', cat: 'fluid', title: 'Ferrofluid', kind: 'webgl', posterAt: 1500, pointer: true,
76
+ trick: 'Distance-field blob with cosine spikes that reach for the magnet',
77
+ notes: ['The outline is a circle plus sharpened cosine bumps: raising the cosine to a high power turns soft waves into spikes.', 'Spikes grow strongest on the side facing the magnet (your pointer), like real magnetic fluid.', 'A normal from the distance field gives the oily black surface its highlights and blue-ish rim.'],
78
+ params: { spikes: P(8, 40, 1, 'Spikes'), strength: P(0, 2, 0.05, 'Magnet strength') } },
79
+ { id: 'marbling', cat: 'fluid', title: 'Paper marbling', kind: 'webgl', posterAt: 1800, pointer: true,
80
+ trick: 'Trace each pixel back through a comb and a swirl to the paint bands',
81
+ notes: ['Marbled paper is paint floated on water, combed, then printed. Here each pixel traces backward through the comb waves and a swirl around the stylus to find which band of paint it came from.', 'Because it is a backward lookup, the paint never smears or loses detail.', 'Move the pointer to drag the stylus through the paint.'],
82
+ params: { comb: P(0, 2, 0.05, 'Comb'), swirl: P(0, 2, 0.05, 'Swirl') } },
83
+ { id: 'raindrops-glass', cat: 'fluid', title: 'Rain on glass', kind: 'canvas', posterAt: 2600,
84
+ trick: 'Each drop is a lens: the blurred city, sharp and upside down',
85
+ notes: ['The city behind the window is drawn blurred, but every drop shows a sharp, flipped and shrunken copy of it, because a water drop is a tiny lens.', 'Heavy drops overcome surface tension, slide in fits and starts, swallow the small beads in their path and leave a trail of droplets.', 'Small beads keep landing, so the glass never empties.'],
86
+ params: { rain: P(0.2, 3, 0.1, 'Rain'), size: P(0.5, 2, 0.05, 'Drop size') } },
87
+ { id: 'liquid-glass-fill', cat: 'fluid', title: 'Liquid in a glass', kind: 'canvas', posterAt: 1700,
88
+ trick: 'A row of springs makes the surface; tilting shoves it sideways',
89
+ notes: ['The surface is a row of columns tied to each other and to rest by springs: a one-dimensional wave equation.', 'When the glass tilts, the change in tilt pushes every column sideways, so the liquid sloshes up one wall and back, then settles.', 'The surface is drawn level with the world, not the glass, which is what sells the tilt.'],
90
+ params: { slosh: P(0, 2, 0.05, 'Slosh'), fill: P(0.2, 0.9, 0.01, 'Fill level') } },
91
+ { id: 'ink-drop', cat: 'fluid', title: 'Ink drop in water', kind: 'canvas', posterAt: 2800,
92
+ trick: 'Particles circling a sinking, widening vortex ring',
93
+ notes: ['A drop of ink rolls up into a vortex ring as it falls into water. Here thousands of particles orbit the ring’s core while the ring sinks, widens and slows.', 'After a moment the ring breaks up: particles drift off it in curling plumes.', 'A faint fill each frame lets old ink linger and clear slowly, like real water.'],
94
+ params: { particles: P(800, 6000, 100, 'Particles'), interval: P(1.5, 6, 0.1, 'Drop every (s)') } },
95
+ { id: 'underwater', cat: 'fluid', title: 'Underwater', kind: 'webgl', posterAt: 1500,
96
+ trick: 'God rays, caustics on sand, swaying kelp, rising bubbles',
97
+ notes: ['Everything is one shader: slanting god rays from noise bands, caustic light dancing on the sand, kelp ribbons swaying in the current and bubbles rising in columns.', 'Depth fog darkens toward the bottom and a colour gradient carries you from surface to deep water.', 'Every element moves at its own speed, so it never feels looped.'],
98
+ params: { rays: P(0, 2, 0.05, 'Rays'), bubbles: P(0, 1, 0.05, 'Bubbles') } },
99
+ { id: 'dripping-text', cat: 'fluid', title: 'Dripping text', kind: 'canvas', posterAt: 2400,
100
+ trick: 'Gooey filter merges drips with letters; necks pinch and snap',
101
+ notes: ['Drops start under the letters (found by scanning the text’s bottom edges), swell, stretch downward and accelerate as they get heavier.', 'A blur followed by a hard alpha threshold melts drop and letter into one liquid outline, so the neck thins and snaps like real paint.', 'Fallen drops collect in a puddle at the bottom.'],
102
+ params: { drips: P(4, 30, 1, 'Drips'), viscosity: P(0.4, 3, 0.05, 'Viscosity') } },
103
+ { id: 'mercury', cat: 'fluid', title: 'Liquid mercury', kind: 'webgl', posterAt: 1500, pointer: true,
104
+ trick: 'Metaball beads that merge, split under a fast stroke and mirror a studio',
105
+ notes: ['JavaScript moves the beads: ones that touch snap together into one bead with the same area and momentum, and a fast stroke through a bead tears it in two.', 'The shader adds every bead into one metaball field, turns it into a height (round for small beads, flatter for big puddles) and takes a normal from it.', 'That normal reflects a studio: pale floor at the rim, a dark room band, bright softboxes on top. Move fast through the metal to split it.'],
106
+ params: { beads: P(4, 22, 1, 'Starting beads'), cohesion: P(0, 2, 0.05, 'Cohesion'), tilt: P(0, 2, 0.05, 'Tray tilt') } },
107
+ { id: 'honey-drip', cat: 'fluid', title: 'Honey drip', kind: 'canvas', posterAt: 2400,
108
+ trick: 'Viscous creep that runs away: the neck thins, snaps and springs back',
109
+ notes: ['Honey flows down into a bulb, and the heavier and longer the strand gets, the faster it stretches. That runaway is why a drip hangs for ages, then suddenly goes.', 'The strand keeps its volume, so as it lengthens the neck thins; when it is thin enough it snaps, the tail breaks into tiny beads and the strand springs back up.', 'The strand is a Verlet chain hanging from a swaying dipper; the pool is a damped row of springs plus a mound that spreads slowly, like thick honey.'],
110
+ params: { viscosity: P(0.4, 3, 0.05, 'Viscosity'), flow: P(0.3, 2.5, 0.05, 'Flow') } },
111
+ { id: 'water-surface-3d', cat: 'fluid', title: 'Ocean at sunset', kind: 'webgl', posterAt: 1500,
112
+ trick: 'Raymarched heightfield: Fresnel sky reflection, glowing crests, sun glint',
113
+ notes: ['The sea is a heightfield made of sharp-crested waves; each wave drags the sample point along its slope, which bunches crests the way real swell does.', 'Each pixel marches its ray through the wave slab until it hits the surface, then reflects the sunset sky. Fresnel makes the water mirror-like toward the horizon and see-through at your feet.', 'The low sun glows through thin crests, its glint sparkles on facets that face it, and the far field calms into haze so it never shimmers.'],
114
+ params: { waves: P(0.3, 2, 0.05, 'Wave height'), sun: P(0, 1, 0.02, 'Sun height'), speed: P(0.2, 3, 0.1, 'Speed') } },
115
+ { id: 'bubbles', cat: 'fluid', title: 'Soap bubbles', kind: 'canvas', posterAt: 2600, pointer: true,
116
+ trick: 'Thin-film colours from thickness; a pop tears the film into droplets',
117
+ notes: ['A soap film shows the colour of light that its thickness reinforces. The film drains, so it is thinnest at the top, banded lower down, and turns gold, then black, just before it bursts.', 'The rim is drawn with a conic gradient whose colours come from that thickness, so the bands swirl around the bubble as it turns.', 'A wand blows new ones; they wobble when they meet, rise, then slowly sink. A pop opens from one point and throws the film off as droplets. Touch one to pop it.'],
118
+ params: { count: P(3, 30, 1, 'Bubbles'), wind: P(-1, 1, 0.05, 'Wind') } },
119
+ { id: 'paint-swirl', cat: 'fluid', title: 'Paint swirl', kind: 'webgl', posterAt: 3000, pointer: true,
120
+ trick: 'Colour advected around vortices; spouts pour fresh rings; paints never mix',
121
+ notes: ['Each frame every pixel looks back along the velocity field (several Lamb–Oseen vortices) and takes the colour from there. That is advection, the step at the heart of every fluid solver.', 'Two spouts push paint outward, so each colour change becomes a ring, and the vortices wind the rings into spirals. The canvas wraps around like a torus, so there are no walls.', 'Sampling is clamped Catmull-Rom, and each pixel is nudged toward the nearest paint colour, so the bands stay crisp like immiscible acrylics. Move the pointer in circles to stir.'],
122
+ params: { vortices: P(1, 6, 1, 'Vortices'), strength: P(0, 2.5, 0.05, 'Swirl strength'), pour: P(0, 2, 0.05, 'Pour') } },
123
+ { id: 'sph-water', cat: 'fluid', title: 'SPH water tank', kind: 'canvas', posterAt: 3000,
124
+ trick: 'Particles that measure their own crowding and push back',
125
+ notes: ['Smoothed-particle hydrodynamics: each particle sums its neighbours through a smooth kernel to get a density, and pressure grows with how far that density is above rest.', 'Pressure pushes crowded particles apart and viscosity evens out their velocities; a spatial grid keeps the neighbour search cheap. If the pressure is too soft against gravity, the water crushes into a corner.', 'Gravity swings to slosh the tank. The particles are splatted as soft blobs and thresholded into one liquid surface.'],
126
+ params: { particles: P(200, 1000, 50, 'Particles'), slosh: P(0, 1.5, 0.05, 'Slosh') } },
127
+ // ── Chrome & 3D
128
+ { id: 'liquid-metal', cat: 'chrome', title: 'Liquid metal', kind: 'webgl', posterAt: 1500,
129
+ trick: 'A blob pulls out of a chrome pool, hangs, drops back and rings the surface',
130
+ notes: ['The pool is a rippling height field and the blob is a wobbling sphere; a smooth minimum joins them, so a neck forms as the blob rises and thins until it lets go.', 'When the blob falls back, ring waves spread from the impact and fade with distance and time.', 'Each ray reflects twice, so the blob mirrors the pool and the pool mirrors the blob; a ring light around the studio becomes the rippling zebra bands.'],
131
+ params: { cycle: P(3, 10, 0.1, 'Cycle (s)'), ripple: P(0, 2, 0.05, 'Ripples'), smooth: P(0.1, 0.8, 0.01, 'Neck smoothness') } },
132
+ { id: 'mandelbulb', cat: 'chrome', title: 'Mandelbulb fractal', kind: 'webgl', posterAt: 1500,
133
+ trick: 'Power-8 orbits in spherical coordinates, raymarched by distance estimate',
134
+ notes: ['Each point is raised to a power in spherical coordinates (radius to the n, both angles times n) and added back, over and over. Points whose orbit stays small belong to the bulb.', 'How fast the orbit escapes gives a safe distance to the surface, so the ray can step there in big jumps. Steps taken stand in for ambient occlusion; the orbit trap picks the colour.', 'The power drifts slowly, so the whole bulb breathes and rearranges its crevices.'],
135
+ params: { power: P(3, 12, 0.1, 'Power'), drift: P(0, 2, 0.05, 'Power drift'), detail: P(0.5, 3, 0.1, 'Detail') } },
136
+ { id: 'infinite-tunnel', cat: 'chrome', title: 'Infinite tunnel', kind: 'webgl', posterAt: 1300,
137
+ trick: 'A distance-field tube on a winding path, panels cut from angle and depth',
138
+ notes: ['The tunnel is everything further than 1.5 units from a curved path, so the camera can fly down it forever while it bends.', 'Panels come from the angle around the tube and the depth along it, with every other row staggered; each panel stands proud of its seams, so lights pick out the bevels.', 'Three coloured lights travel just ahead of you, and pulses race along the seams.'],
139
+ params: { speed: P(0, 3, 0.05, 'Speed'), twist: P(-3, 3, 0.1, 'Twist'), glow: P(0, 2, 0.05, 'Seam glow') } },
140
+ { id: 'glass-prism', cat: 'chrome', title: 'Glass prism', kind: 'webgl', posterAt: 1400,
141
+ trick: 'Refract in, bounce inside, refract out, separately for red, green and blue',
142
+ notes: ['Each ray bends as it enters the glass, travels to the far face, and either bends out again or, if it hits too steeply, reflects inside (total internal reflection).', 'Red, green and blue each use a slightly different index of refraction, so the striped studio behind the prism splits into rainbow fringes: dispersion.', 'The fan of colour on the floor turns with the prism. Fresnel mixes in a reflection that grows at grazing angles.'],
143
+ params: { dispersion: P(0, 3, 0.05, 'Dispersion'), ior: P(1.1, 2, 0.01, 'Index of refraction'), spin: P(0, 3, 0.05, 'Spin') } },
144
+ { id: 'low-poly-planet', cat: 'chrome', title: 'Low-poly planet', kind: 'webgl', posterAt: 1200, pointer: true,
145
+ trick: '1,280 flat faces pushed out by noise, coloured by height, with trees and clouds',
146
+ notes: ['An icosahedron is split three times into 1,280 triangles and each vertex is pushed outward by 3D noise; below sea level everything stays flat.', 'Every face is flat-shaded and coloured by its height: sea, sand, grass, forest, rock, snow. Pine trees are little five-sided cones on the green faces.', 'Clouds are clusters of tiny icosahedrons that orbit faster than the ground. Drag to spin; it keeps its momentum.'],
147
+ params: { terrain: P(0, 2.5, 0.05, 'Terrain height'), sea: P(0, 1, 0.02, 'Sea level'), spin: P(0, 3, 0.05, 'Spin') } },
148
+ { id: 'point-globe', cat: 'chrome', title: 'Dot globe with arcs', kind: 'canvas', posterAt: 1800, pointer: true,
149
+ trick: 'A Fibonacci sphere of dots, noise continents, great-circle flight arcs',
150
+ notes: ['Dots are spread evenly over the sphere with a golden-angle spiral; a 3D noise map decides which are land.', 'Each frame every dot is rotated and given a perspective divide by hand on a 2D canvas, and only the near side is drawn.', 'Arcs follow great circles between two land dots, lift higher for longer trips, and ping when they land. Drag to spin.'],
151
+ params: { dots: P(3000, 30000, 1000, 'Dots'), arcs: P(0, 20, 1, 'Arcs'), spin: P(0, 3, 0.05, 'Spin') } },
152
+ { id: 'extruded-type', cat: 'chrome', title: 'Extruded 3D type', kind: 'canvas', posterAt: 1500, pointer: true,
153
+ trick: 'A text mask stacked back to front through one 3D rotation',
154
+ notes: ['The word is drawn once into a mask, then stamped dozens of times from back to front, each layer shifted by the same 3D rotation, so the stack reads as solid letters.', 'An orthographic projection of a flat plane is exactly a 2D affine transform, so the canvas transform does the 3D maths.', 'The face gets a moving chrome sweep and the sides brighten as they turn toward the light. Move the pointer to steer it.'],
155
+ params: { depth: P(0.2, 2.5, 0.05, 'Depth'), layers: P(6, 80, 1, 'Layers'), tilt: P(0, 1.6, 0.05, 'Tilt') } },
156
+ { id: 'isometric-city', cat: 'chrome', title: 'Isometric city', kind: 'canvas', posterAt: 1800,
157
+ trick: "Painter's order on an iso grid, buildings on springs, a day–night cycle",
158
+ notes: ['Tiles are drawn back to front by row plus column, so nearer buildings and cars overlap farther ones without a depth buffer.', 'Each building rises to its height on a spring after a delay that grows with distance, so the city builds itself as a wave; now and then one is knocked down and rebuilt.', 'Cars drive the road grid; at night the sky darkens, headlights come on and windows light up from a fixed hash, so they never flicker.'],
159
+ params: { size: P(6, 18, 1, 'Grid size'), cycle: P(6, 40, 1, 'Day length (s)'), traffic: P(0, 40, 1, 'Cars') } },
160
+ { id: 'holo-card', cat: 'chrome', title: 'Holographic card', kind: 'dom', posterAt: 1400, pointer: true,
161
+ trick: 'CSS 3D tilt on a spring, color-dodge foil and a glare that follows the light',
162
+ notes: ['The card tilts toward the pointer through a spring, in real CSS 3D (rotateX and rotateY under perspective).', 'A rainbow foil layer slides with the tilt and is blended with color-dodge through a fine diagonal mask, which is what makes it read as holographic foil.', 'A radial glare follows the light. The tilt variables live on the card itself, so only the card restyles each frame.'],
163
+ params: { tilt: P(0, 35, 1, 'Max tilt (deg)'), foil: P(0, 2, 0.05, 'Foil'), stiffness: P(40, 400, 5, 'Spring stiffness') } },
164
+ { id: 'torus-knot', cat: 'chrome', title: 'Iridescent torus knot', kind: 'webgl', posterAt: 1500,
165
+ trick: 'A tube swept along a (p, q) knot with parallel-transport frames',
166
+ notes: ['A (p, q) torus knot winds p times around the axis and q times through the hole. A circle is swept along it to make the tube: 520 rings of 28 vertices.', 'The ring orientation is carried along by parallel transport, so it never flips the way Frenet frames do; the leftover twist is spread evenly so the seam closes.', 'Shading reflects a studio and shifts hue with the viewing angle like a thin film; stripes flow along the knot.'],
167
+ params: { p: P(1, 7, 1, 'p (turns around)'), q: P(1, 9, 1, 'q (turns through)'), flow: P(0, 3, 0.05, 'Stripe flow') } },
168
+ { id: 'jelly-cube', cat: 'chrome', title: 'Jelly cube', kind: 'webgl', posterAt: 2400, pointer: true,
169
+ trick: 'A 4×4×4 mass-spring lattice bends a rounded cube; light is absorbed by thickness',
170
+ notes: ['Sixty-four masses in a 4×4×4 grid are joined by springs along the edges, across the face and body diagonals, and one node over to resist bending; a gentle pull toward the best-fit rigid shape keeps it from collapsing. It lands, squashes and wobbles on its own physics.', 'The smooth rounded cube you see is fixed inside that lattice with tricubic Bézier weights (free-form deformation), so 64 points bend about 1,500 vertices.', 'For the jelly look, a first pass records how far away the back of the cube is; the front then absorbs light by the thickness in between (thicker means deeper colour), bends the floor seen through it, and the shadow glows with the light that got through. Grab and throw it, or tap to make it hop.'],
171
+ params: { softness: P(0.4, 2.5, 0.05, 'Softness'), hop: P(0, 1.6, 0.05, 'Hop height'), hue: P(0, 360, 1, 'Jelly hue') } },
172
+ { id: 'neon-geodesic', cat: 'chrome', title: 'Neon geodesic sphere', kind: 'canvas', posterAt: 1800,
173
+ trick: 'A frequency-3 icosphere stroked as neon, with pulses walking the struts',
174
+ notes: ['Each of an icosahedron’s 20 faces is split into nine triangles and pushed out onto a sphere: 92 nodes and 270 struts. The twelve original corners are the only nodes where five struts meet, so they are drawn bigger.', 'Struts are sorted into four depth bands, so the far side is thinner and dimmer, and stroked with a cyan-to-magenta gradient. The glow is the same drawing shrunk to a quarter and a twelfth of its size and stretched back up, which blurs it almost for free.', 'Pulses walk the struts, preferring to carry straight on at each node, and light the panels they pass. A bright ring sweeps from pole to pole, and the glossy floor mirrors it all, fading with distance.'],
175
+ params: { pulses: P(0, 40, 1, 'Pulses'), spin: P(0, 3, 0.05, 'Spin'), glow: P(0, 2, 0.05, 'Glow') } },
176
+ { id: 'path-spheres', cat: 'chrome', title: 'Spheres on a knot', kind: 'webgl', posterAt: 1500,
177
+ trick: 'Balls riding a trefoil knot, ray traced with soft shadows and interreflections',
178
+ notes: ['Each ball sits on a trefoil knot laid flat, where the over- and under-crossings become height, and slides along it while its radius breathes.', 'Spheres and a floor can be hit exactly with a little algebra, so nothing is marched: every pixel solves its ray against each ball and bounces up to three times, so the chrome balls mirror the coloured ones, each other and the floor.', 'Soft shadows come from how closely the shadow ray passes each ball (the closer, the darker), and the occlusion from how much of the sky each nearby ball blocks.'],
179
+ params: { spheres: P(3, 24, 1, 'Balls'), speed: P(0, 3, 0.05, 'Speed'), breathe: P(0, 2, 0.05, 'Breathing') } },
180
+ { id: 'sdf-morph', cat: 'chrome', title: 'SDF shape morph', kind: 'webgl', posterAt: 2000,
181
+ trick: 'Five distance functions blended in turn, with a twist at each midpoint',
182
+ notes: ['Each shape is a signed distance function: how far any point is from its surface (a sphere is just length minus radius). The object runs sphere, rounded cube, torus, octahedron, then a sphere carved by a gyroid lattice.', 'To morph, the two distances are mixed by an ease-in-out amount, and space is twisted around the vertical axis by an angle that peaks halfway, so each in-between shape wrings itself into the next.', 'Rays reflect once, so the carved lattice mirrors itself and the floor. The floor’s soft shadow comes from marching toward the light and keeping the closest miss.'],
183
+ params: { hold: P(0.3, 4, 0.1, 'Hold (s)'), morph: P(0.3, 3, 0.1, 'Morph (s)'), twist: P(0, 3, 0.05, 'Twist') } },
184
+ { id: 'paper-fold', cat: 'chrome', title: 'Paper fold', kind: 'dom', posterAt: 950,
185
+ trick: 'Six panels on nested CSS 3D hinges, shaded from their true normals',
186
+ notes: ['The sheet is two halves, each holding three rows. The bottom and top rows turn 180° about their creases; then the whole right half, folded rows and all, turns over onto the left. Nested preserve-3d makes the inner folds ride along with the outer one.', 'Each panel has a printed front and a tinted back with backface-visibility hidden, so whichever side faces you is the one that shows. Folded layers sit a pixel apart so they never fight over depth.', 'Shading is worked out in JavaScript: each face’s normal is turned through its hinges and the table tilt and compared with the lamp. Creases darken inside and catch light outside, and stay faintly marked after the first fold. The folds use a strong ease-in-out.'],
187
+ params: { cycle: P(4, 20, 0.5, 'Cycle (s)'), tilt: P(0, 70, 1, 'Table tilt (deg)'), crease: P(0, 2, 0.05, 'Crease shading') } },
188
+ // ── Flow & particles, round 3
189
+ { id: 'snowfall', cat: 'flow', title: 'Snowfall under a lamp', kind: 'canvas', posterAt: 2600,
190
+ trick: 'Three depths of flakes, gusting wind, and a drift that builds where they land',
191
+ notes: ['Each flake has a depth: far ones are tiny, faint and slow, near ones large, soft and fast. That parallax alone sells the depth.', 'The wind is three slow sine swells added together, so the gusts never repeat in a way you can spot; near flakes feel them most.', 'Near flakes that reach the ground raise a column of the drift, and every frame the drift is smoothed so it slumps into soft mounds. Flakes inside the lamp’s cone are drawn brighter.'],
192
+ params: { flakes: P(150, 1500, 50, 'Flakes'), wind: P(0, 2, 0.05, 'Wind') } },
193
+ { id: 'sparks', cat: 'flow', title: 'Grinder sparks', kind: 'canvas', posterAt: 1500,
194
+ trick: 'Streaks coloured by temperature, a lossy floor bounce, and sparks that burst',
195
+ notes: ['Every spark is drawn as a short line from where it was a moment ago, a cheap motion blur that makes them read as fast.', 'Colour follows temperature as it cools: white, yellow, orange, then dull red. Additive blending makes overlapping streaks glow.', 'The floor keeps only a third of the bounce, and a few sparks split into smaller ones mid-flight, which is what real steel sparks do.'],
196
+ params: { rate: P(60, 800, 20, 'Sparks per second'), gravity: P(0.3, 2, 0.05, 'Gravity') } },
197
+ { id: 'murmuration', cat: 'flow', title: 'Starling murmuration', kind: 'canvas', posterAt: 4200,
198
+ trick: 'A 3D flock where each bird only watches its seven nearest neighbours',
199
+ notes: ['Real starlings track a fixed number of neighbours (about seven), not everyone within a distance. That keeps the flock cohesive while it stretches and folds.', 'Each bird matches its neighbours’ heading, drifts toward them and keeps a minimum gap; a hash grid means it only checks birds in nearby cells.', 'A falcon dives through every few seconds. Birds near it flee, and the fear passes from neighbour to neighbour, which is what sends the dark waves rippling across the cloud.'],
200
+ params: { birds: P(400, 3000, 100, 'Birds'), falcon: P(0, 2, 0.1, 'Falcon attacks') } },
201
+ { id: 'dandelion', cat: 'flow', title: 'Dandelion clock', kind: 'canvas', posterAt: 3800,
202
+ trick: 'Golden-angle seed sphere in 3D; gusts tear seeds loose and they float away',
203
+ notes: ['The seeds sit on a sphere spaced by the golden angle, which spreads any number of points evenly, and the head turns slowly in 3D.', 'Gusts take seeds from the windward side only. Free seeds are pulled toward the wind’s speed by drag, sink slowly and tumble under their parachutes.', 'Each parachute is a fan of fine filaments whose spread changes as it turns, so it reads as a 3D umbrella. A bare head regrows.'],
204
+ params: { seeds: P(40, 300, 10, 'Seeds'), wind: P(0.3, 2.5, 0.05, 'Wind') } },
205
+ { id: 'states-of-matter', cat: 'flow', title: 'States of matter', kind: 'canvas', posterAt: 27000,
206
+ trick: 'One force law and a thermostat: gas, droplets, then a hexagonal crystal',
207
+ notes: ['Every pair of atoms feels the Lennard-Jones force: hard repulsion up close, gentle attraction a little further out. A thermostat nudges every speed toward a target temperature that slowly falls.', 'Hot, the atoms are a gas; cooler, they stick into droplets that rain into a pool; colder still, the pool freezes into a hexagonal crystal, with grain boundaries where crystals that started in different places meet. Then it heats up and melts.', 'Atoms are coloured by speed, which is temperature; bonds are drawn between close neighbours, so you watch the lattice form, and atoms with six neighbours (sitting in crystal) get a cool highlight.'],
208
+ params: { atoms: P(300, 1600, 50, 'Atoms'), gravity: P(0, 3, 0.1, 'Gravity'), cycle: P(0.4, 2.5, 0.1, 'Cycle speed') } },
209
+ { id: 'bio-shore', cat: 'flow', title: 'Bioluminescent shore', kind: 'webgl', posterAt: 7000, pointer: true,
210
+ trick: 'A glow map laid over the beach: lit by breaking waves, carried by the wash',
211
+ notes: ['On some nights plankton flash blue whenever the water is disturbed. Here the glow lives in a texture laid over the beach in world space: every frame it fades, rides up and back down the sand with the wash, and is lit again wherever a wave breaks.', 'Breaking is patchy, so the light flickers along each crest; the foam edge of the wash sparkles. Drag the pointer through the water to light a trail.', 'Each pixel casts a ray onto the beach to find where it looks, then reads the waves, the wet sand’s sheen, the stars reflected in both, and the glow.'],
212
+ params: { waves: P(0.3, 2, 0.05, 'Wave rate'), glow: P(0.2, 2.5, 0.05, 'Glow'), sparkle: P(0, 3, 0.1, 'Sparkle') } },
213
+ { id: 'scramble-crossing', cat: 'flow', title: 'Scramble crossing in the rain', kind: 'canvas', posterAt: 17000,
214
+ trick: 'The social force model: lanes form in the crowd with nobody planning them',
215
+ notes: ['Umbrellas wait on four corners while the cars go; then every light turns to walk at once and the crowds pour across in all directions, diagonals included.', 'Each walker follows the social force model: a pull toward where they are going and a soft push away from everyone near them, stronger from people ahead. Nobody plans lanes, yet lanes form: people fall in behind others heading their way.', 'The asphalt is wet, so neon signs and headlights smear across it; rain streaks down and rings the ground. When the signal flashes, stragglers hurry.'],
216
+ params: { people: P(60, 500, 10, 'People'), rain: P(0, 1, 0.05, 'Rain'), pace: P(0.4, 2, 0.05, 'Pace') } },
217
+ { id: 'embers', cat: 'flow', title: 'Bonfire embers', kind: 'canvas', posterAt: 3000,
218
+ trick: 'Buoyancy that fades as embers cool, swirled by a curl-noise field',
219
+ notes: ['Lift depends on temperature squared, so the hottest embers shoot up and cooled ones drift and sink: the column thins out naturally.', 'The swirl is the curl of a field of layered sines, which is divergence-free: embers curl into eddies and never bunch up in one spot.', 'Embers twinkle and change colour as they cool; the fire behind them is a row of flickering soft tongues added together as light.'],
220
+ params: { embers: P(60, 600, 20, 'Embers'), turbulence: P(0, 2.5, 0.05, 'Turbulence') } },
221
+ { id: 'calligraphy', cat: 'flow', title: 'Calligraphy brush', kind: 'canvas', posterAt: 10000,
222
+ trick: 'Forty bristles, each with its own ink, write 永 with flying-white streaks',
223
+ notes: ['A brush writes 永 (eternity), the character students practise because it holds the eight basic strokes. The brush is forty bristles in a teardrop footprint that turns with the stroke and grows with pressure.', 'Each bristle carries its own ink and lays it down as it moves. On a long stroke the outer bristles run dry first and leave the streaked “flying white” (飛白); where the brush slows, at a turn or an end, ink pools darker; a quick flick lifts it to a point.', 'Strokes follow smooth paths with a little hand wobble, so no two sheets match. Then the red seal goes on and a fresh sheet is laid.'],
224
+ params: { pressure: P(0.5, 1.6, 0.05, 'Pressure'), ink: P(0.4, 2, 0.05, 'Ink load'), speed: P(0.4, 2.5, 0.1, 'Speed') } },
225
+ { id: 'harmonograph', cat: 'flow', title: 'Harmonograph', kind: 'canvas', posterAt: 9000,
226
+ trick: 'Four decaying pendulums near a simple ratio, slightly detuned',
227
+ notes: ['Two pendulums move the pen and two move the paper. Each is a sine that slowly dies away, and the pen’s position is their sum.', 'Frequencies sit close to a simple ratio such as 2:3 or 3:4; the tiny detuning makes the loops creep round into a rosette instead of retracing one path.', 'Many short sub-steps each frame keep the line smooth, and the stroke thins as the swing dies. When it stops, the paper is changed and the next ratio is drawn.'],
228
+ params: { speed: P(0.3, 3, 0.1, 'Speed'), decay: P(0.1, 2, 0.05, 'Decay') } },
229
+ { id: 'chladni', cat: 'flow', title: 'Chladni plate', kind: 'webgl', posterAt: 5000,
230
+ trick: 'Sand bounces off the moving plate and settles on its nodal lines',
231
+ notes: ['A square brass plate is bolted at its centre to a speaker, with sixty-five thousand grains of sand on it. Near each resonant frequency one standing wave dominates: the plate buzzes everywhere except along its nodal lines.', 'Each grain hops by however hard its spot is shaking and slides a little downhill toward stillness, so the sand collects on the still lines and draws the figure Ernst Chladni showed in 1787. The grains live in a float texture and move on the GPU.', 'The tone sweeps upward and lingers near each resonance; in between, two modes mix and the sand drifts from one figure toward the next.'],
232
+ params: { grains: P(128, 512, 32, 'Grid (grains = grid²)'), sweep: P(0.3, 3, 0.1, 'Sweep speed'), shake: P(0.3, 2, 0.05, 'Shake') } },
233
+ { id: 'dla-crystal', cat: 'flow', title: 'Crystal growth (DLA)', kind: 'canvas', posterAt: 5000,
234
+ trick: 'Random walkers stick on contact; tips catch more, so branches grow',
235
+ notes: ['Diffusion-limited aggregation: a particle wanders at random until it touches the crystal, then sticks right there.', 'Tips reach further into the wandering particles than the hollows between them, so they catch more and grow faster. That feedback alone makes the branching, coral-like fractal.', 'Walkers are released on a circle just outside the crystal and dropped if they stray, which keeps every random step useful. Colour follows the distance from the seed; when it fills the view it starts again.'],
236
+ params: { speed: P(0.2, 3, 0.1, 'Growth speed'), stickiness: P(0.1, 1, 0.05, 'Stickiness') } },
237
+ { id: 'sand-mandala', cat: 'flow', title: 'Sand mandala', kind: 'canvas', posterAt: 22000,
238
+ trick: 'Grain by grain from symmetric funnels, then swept into a spiral',
239
+ notes: ['Chalk guide lines go down first, then coloured sand from funnels moving in step round the circle, from the centre out, ring by ring: a lotus, a palace with four gates, flames, petals, a ring of fire.', 'Each grain takes the colour of the design under it with a slight variation, so the colours read as sand; grains are batched by colour so tens of thousands a second stay cheap.', 'When it is finished it is swept away, as the real ones are: the picture is sampled into grains, and a brush coming in from the rim spirals every grain it passes into a pile at the centre.'],
240
+ params: { grains: P(400, 5000, 100, 'Grains per frame'), symmetry: P(4, 16, 1, 'Symmetry'), speed: P(0.4, 2.5, 0.1, 'Speed') } },
241
+ { id: 'wind-tunnel', cat: 'flow', title: 'Wind tunnel', kind: 'webgl', posterAt: 9000, pointer: true,
242
+ trick: 'A lattice Boltzmann solver on the GPU sheds a Kármán vortex street',
243
+ notes: ['Each lattice cell holds nine populations of particles, one per direction. Every step they stream to the neighbouring cell and relax toward equilibrium, and viscous flow comes out of that, with a little extra viscosity where it shears hard, to stay stable.', 'Behind the body the boundary layer peels away and rolls up into eddies shed from alternate sides: a Kármán vortex street. The body changes every few seconds between a cylinder, a wing at 12° and a flat plate. Drag it to steer the wake.', 'The smoke lines come from a rake of nozzles at the inlet and ride the flow; MacCormack advection keeps the curls sharp instead of blurring them away.'],
244
+ params: { reynolds: P(60, 600, 10, 'Reynolds number'), speed: P(0.3, 2, 0.1, 'Speed'), smoke: P(6, 30, 1, 'Smoke lines') } },
245
+ { id: 'rainbow', cat: 'flow', title: 'Rainbow from raindrops', kind: 'webgl', posterAt: 3000,
246
+ trick: 'Each drop sends one colour at its angle from the anti-solar point',
247
+ notes: ['Light that enters a raindrop, bounces once inside and comes out leaves at about 42° from the point opposite the sun, red a little wider than violet. So each drop sends you one colour only while it crosses that angle, and the bow is where thousands of drops agree.', 'A second bounce makes the fainter outer bow at about 51°, with its colours reversed. Between the two, Alexander’s band is darker, because no drop sends light from there, and inside the main bow the sky is brighter.', 'Every drop is an instanced streak coloured by its own angle; the sky adds the faint glow of countless drops too far away to see.'],
248
+ params: { drops: P(4000, 40000, 1000, 'Drops'), rain: P(0.2, 2, 0.05, 'Rain'), bow: P(0, 2, 0.05, 'Bow strength') } },
249
+ // ── Chrome & 3D, round 3
250
+ { id: 'menger-sponge', cat: 'chrome', title: 'Menger sponge', kind: 'webgl', posterAt: 2500,
251
+ trick: 'One box minus a cross, repeated by folding space at each level',
252
+ notes: ['The sponge is a cube with its centre cross removed, then the same removed from each of the 20 small cubes left, and so on. As a distance function that is one box and a short loop: fold space into repeating cells with mod, and subtract a cross three times thinner at each level.', 'The loop also records how deep a point sits in the holes, which darkens the inner tunnels for free ambient occlusion.', 'Brass is lit by a studio key with a soft shadow marched toward it, a cool sky fill and a sheen at grazing angles.'],
253
+ params: { levels: P(1, 5, 1, 'Levels'), speed: P(0, 2.5, 0.05, 'Speed') } },
254
+ { id: 'gyroid', cat: 'chrome', title: 'Gyroid fly-through', kind: 'webgl', posterAt: 2500,
255
+ trick: 'A thickened gyroid sheet with a tube carved along the camera path',
256
+ notes: ['The gyroid, sin x cos y + sin y cos z + sin z cos x = 0, splits space into two interwoven labyrinths. Taking its absolute value minus a thickness turns it into endless organic walls.', 'A tube along the camera’s winding path is subtracted from the walls, so the flight never crashes. The gyroid isn’t an exact distance, so the marcher steps at 70%.', 'A headlight glint, warm and cool fills, ambient occlusion from one extra distance sample, a hue that drifts with depth and fog that swallows the distance.'],
257
+ params: { speed: P(0, 3, 0.05, 'Flight speed'), scale: P(0.6, 1.8, 0.05, 'Wall scale') } },
258
+ { id: 'black-hole', cat: 'chrome', title: 'Black hole', kind: 'webgl', posterAt: 2000,
259
+ trick: 'Rays bent toward the hole at every step, crossing a Doppler-beamed disk',
260
+ notes: ['Each ray is stepped through space and nudged toward the hole by an amount that grows as 1/r². Rays that pass too close fall in and stay black; the rest curve round and pick up the stars behind, forming an Einstein ring.', 'Because rays bend, the accretion disk’s far side shows arching over the top and under the bottom of the shadow.', 'The side of the disk turning toward you is brighter and bluer (relativistic beaming), the receding side dimmer and redder, and inner rings orbit faster.'],
261
+ params: { spin: P(0, 3, 0.05, 'Disk speed'), tilt: P(0.05, 0.6, 0.01, 'Camera height') } },
262
+ { id: 'tesseract', cat: 'chrome', title: 'Tesseract', kind: 'webgl', posterAt: 4000,
263
+ trick: 'A rigid 4D rotation, projected to 3D, turns the cube inside out',
264
+ notes: ['A tesseract is a cube in four dimensions: 16 corners (every combination of ±1 in x, y, z and w) and 32 edges joining corners that differ in one coordinate.', 'It rotates in planes that involve w, then is projected into 3D the way 3D is projected onto a screen: further along w comes out smaller. So the inner cube grows, swallows the outer one and turns inside out, though nothing bends.', 'Edges are instanced chrome tubes in a studio, gold on the near side of w and blue on the far side, reflected in a glossy floor.'],
265
+ params: { spin: P(0.2, 2.5, 0.1, 'Spin'), tube: P(0.4, 2.5, 0.1, 'Tube thickness'), perspective: P(0.4, 2, 0.05, '4D perspective') } },
266
+ { id: 'soap-bubble', cat: 'chrome', title: 'Soap bubble', kind: 'webgl', posterAt: 3000,
267
+ trick: 'Thin-film interference from a draining, swirling film thickness',
268
+ notes: ['Light reflects off both the outside and the inside of a film a few hundred nanometres thick. Depending on thickness, angle and wavelength the two reflections add or cancel, so each spot shows its own colour.', 'The film drains under gravity (thicker at the bottom, thin at the top) and is stirred by drifting noise, so the colours flow and swirl.', 'Front and back walls both reflect a soft studio, mostly at the edges (Fresnel), and the surface wobbles as the bubble floats.'],
269
+ params: { thickness: P(0.3, 2, 0.05, 'Film thickness'), swirl: P(0, 3, 0.05, 'Swirl') } },
270
+ { id: 'terrain-flyover', cat: 'chrome', title: 'Terrain flyover', kind: 'webgl', posterAt: 2500,
271
+ trick: 'Noise layers damped by their own slope carve ridges like erosion',
272
+ notes: ['The mountains are just a function height(x, z) made of layers of noise. Each layer is damped where the slope so far is steep (the derivative trick), which gives sharp ridges and smooth valleys like real erosion.', 'A ray steps forward by a fraction of its height above the ground until it lands. Shadows step from the ground toward the low sun.', 'Snow settles on high, flat ground, rock shows on steep faces, a lake fills the lowest valleys, and haze thickens with distance.'],
273
+ params: { speed: P(0, 3, 0.05, 'Flight speed'), sun: P(0.08, 0.9, 0.01, 'Sun height') } },
274
+ { id: 'volumetric-clouds', cat: 'chrome', title: 'Volumetric clouds', kind: 'webgl', posterAt: 2500,
275
+ trick: 'March a noise density, and march again toward the sun at every step',
276
+ notes: ['The cloud layer is a 3D density made of noise, shaped into a slab. A ray walks through it, gathering light and losing transparency as it goes (Beer’s law).', 'At each step a short second march toward the sun measures how much cloud is in the way: that gives the dark bellies and bright tops.', 'Looking toward the sun, a forward-scattering term makes thin edges glow. The wind slides the whole field along.'],
277
+ params: { cover: P(0, 1, 0.02, 'Cloud cover'), wind: P(0, 3, 0.05, 'Wind') } },
278
+ { id: 'gem', cat: 'chrome', title: 'Brilliant-cut diamond', kind: 'webgl', posterAt: 2000,
279
+ trick: '49 facet planes, internal reflection, and red, green and blue traced apart',
280
+ notes: ['The stone is the intersection of 49 half-spaces: table, crown, girdle and pavilion facets of a round brilliant. A ray finds where it enters and, inside, which facet it reaches first.', 'Past the critical angle light can’t escape and is totally reflected back in. A diamond is cut so most light bounces round and leaves through the top, which is why it looks so bright.', 'Red, green and blue are traced with slightly different refractive indices, so escaping light fans into colours: the fire. Strip lights in a dark studio give it something to sparkle with.'],
281
+ params: { ior: P(1.5, 2.6, 0.01, 'Index of refraction'), fire: P(0, 3, 0.05, 'Fire (dispersion)') } },
282
+ { id: 'klein-bottle', cat: 'chrome', title: 'Klein bottle', kind: 'webgl', posterAt: 2500,
283
+ trick: 'A parametric mesh drawn as an additive hologram, so you see through the self-intersection',
284
+ notes: ['A Klein bottle has no inside or outside: the neck passes through the wall and joins the base from within. It is built from its parametric equations as a triangle mesh.', 'It is drawn as a see-through hologram: faint faces, bright rims and grid lines, blended additively, so no sorting is needed and you can see the neck pass through the wall. Normals come from the two derivative directions and are flipped toward the viewer.', 'Glowing lines follow the parameter grid (anti-aliased with fwidth), which makes the twist easy to follow as it turns.'],
285
+ params: { spin: P(0, 3, 0.05, 'Spin'), grid: P(0, 1.5, 0.05, 'Grid lines') } },
286
+ { id: 'inflated-type', cat: 'chrome', title: 'Inflated type', kind: 'webgl', posterAt: 4000,
287
+ trick: 'A pressure membrane solved inside the letters gives them a balloon’s round profile',
288
+ notes: ['The word is drawn once to a canvas. An exact distance transform gives the distance to every letter edge, and a membrane under pressure is solved inside the letters (h = 0 at the seams): its square root is round across every stroke, like a sealed foil balloon.', 'Raymarched in 3D, a point is inside the balloon if it is inside a letter and within that height of the mid-plane. It blows up with a springy overshoot, then lets down; while it is soft, noise crinkles the seams.', 'The foil is holographic: a thin-film colour that shifts with the viewing angle, over a pastel studio with two softboxes. Edit WORDS in the code to change the words.'],
289
+ params: { inflate: P(0.3, 1.6, 0.05, 'Inflation'), wobble: P(0, 2, 0.1, 'Wobble'), sheen: P(0, 1.5, 0.05, 'Holographic sheen') } },
290
+ { id: 'rubiks-cube', cat: 'chrome', title: 'Rubik’s cube', kind: 'dom', posterAt: 2600,
291
+ trick: '27 CSS 3D cubies; each turn is baked into whole-number positions',
292
+ notes: ['Each of the 27 cubies is a div with six faces and stores its position and orientation. A move turns one layer of nine a quarter turn; only outward faces carry stickers.', 'When a turn lands it is multiplied into each cubie’s position and orientation and rounded back to whole numbers, so floating-point error can never build up over hundreds of moves.', 'It scrambles with random moves, pauses, then plays them backwards to solve itself. Each turn uses a strong ease-in-out, a decisive flick, while the view slowly orbits.'],
293
+ params: { moves: P(4, 30, 1, 'Scramble moves'), turn: P(0.15, 1, 0.01, 'Seconds per turn') } },
294
+ { id: 'melting-logo', cat: 'chrome', title: 'Melting chrome logo', kind: 'webgl', posterAt: 4300,
295
+ trick: 'A sinking melt front with a rounded rim, drips, and a puddle joined by smooth minimums',
296
+ notes: ['Solid chrome letters slump into a puddle, then pull themselves back up like liquid metal with a memory. The word is drawn once to a canvas and turned into a 2D distance field by an exact distance transform, then extruded with rounded edges.', 'A wavy melt front sinks through the letters; a smooth maximum gives it a molten, rounded rim. Drips hang from the front wherever there is metal and stretch as it goes, and a puddle grows on the floor. Smooth minimums join them all into one liquid surface.', 'Chrome needs something to reflect: a studio of softboxes, coloured strips, a ring light and a bright wall behind the camera. Edit WORD in the code for your own.'],
297
+ params: { drips: P(0, 2, 0.05, 'Drips'), speed: P(0.4, 2, 0.1, 'Speed'), gloss: P(0.5, 1.5, 0.05, 'Gloss') } },
298
+ { id: 'dna-helix', cat: 'chrome', title: 'DNA double helix', kind: 'canvas', posterAt: 3500,
299
+ trick: 'Shaded sphere sprites sorted back to front, with a travelling unzip',
300
+ notes: ['Two backbones wind round one axis, the second about 140° behind the first (that offset makes the major and minor grooves), joined by base pairs: A with T, C with G.', 'Everything is a point in 3D, turned, projected and drawn back to front as a pre-shaded sphere sprite, so it reads as solid without a 3D engine.', 'A replication fork travels along the helix: the strands peel apart there and the rungs split into their two halves, then zip back together behind it.'],
301
+ params: { spin: P(0, 3, 0.05, 'Spin'), unzip: P(0, 1.5, 0.05, 'Unzipping') } },
302
+ { id: 'fractal-tree', cat: 'chrome', title: '3D fractal tree', kind: 'canvas', posterAt: 9000,
303
+ trick: 'Branches split in three around the golden angle, growing level by level',
304
+ notes: ['Every branch splits into three shorter, thinner ones, turned round the parent by the golden angle and tilted outward, so the crown fills evenly in 3D.', 'The tree grows in: a level only starts once the one before it is full length, each branch easing out so it shoots and settles.', 'Wind sway grows toward the tips, blossoms open on the last level, petals drift down, and branches are drawn far to near as the camera circles.'],
305
+ params: { depth: P(4, 8, 1, 'Levels'), wind: P(0, 3, 0.05, 'Wind') } },
306
+ { id: 'shatter', cat: 'chrome', title: 'Shatter & rewind', kind: 'webgl', posterAt: 2300,
307
+ trick: 'Voronoi cracks, ballistic shards, and time run backwards',
308
+ notes: ['A pane of glass takes a hit, cracks into shards and flies apart, then time runs backwards and it puts itself back together.', 'The cracks are a Voronoi diagram: seed points dense near the impact and sparse far from it, each owning the part of the pane closest to it. Each cell is cut out by clipping the pane against the halfway line to every other seed.', 'Every shard follows a ballistic path with its own spin, which is easy to run in reverse. The glass bends the scene behind it a little differently for red, green and blue, and catches a reflection as it tilts.'],
309
+ params: { shards: P(20, 200, 5, 'Shards'), force: P(0.3, 2.5, 0.1, 'Force'), speed: P(0.4, 2, 0.1, 'Speed') } },
310
+ // ── Shaders & glitch, round 3
311
+ { id: 'domain-warp', cat: 'shaders', title: 'Domain-warped marble', kind: 'webgl', posterAt: 2000,
312
+ trick: 'Noise read at a point pushed by noise read at a pushed point: f(p + g(p + h(p)))',
313
+ notes: ['Instead of reading noise at p, read it at p pushed by another noise, itself read at a pushed point. Each layer of pushing folds the pattern into marbled, smoky filaments (Inigo Quilez’s domain warping).', 'The intermediate push vectors are reused to pick colours, so the dye follows the folds instead of sitting on top of them.', 'Every layer drifts slowly in time, so the marble keeps flowing; the warp setting scales how far each layer pushes.'],
314
+ params: { warp: P(0, 2, 0.05, 'Warp'), speed: P(0, 3, 0.05, 'Speed') } },
315
+ { id: 'truchet', cat: 'shaders', title: 'Truchet flow', kind: 'webgl', posterAt: 2600,
316
+ trick: 'Random quarter-arc tiles that always connect, with a checkerboard flow',
317
+ notes: ['Each square holds two quarter-circle arcs joining the midpoints of its sides, in one of two orientations picked at random. Either way the arcs meet their neighbours’, so the grid forms unbroken winding paths.', 'Dashes flow along the arcs. Flipping the flow on alternate squares (a checkerboard) keeps the direction continuous from tile to tile.', 'Now and then a tile turns a quarter with an ease, rerouting the paths around it.'],
318
+ params: { tiles: P(3, 20, 1, 'Tiles across'), flow: P(0, 3, 0.05, 'Flow speed') } },
319
+ { id: 'kaleidoscope', cat: 'shaders', title: 'Kaleidoscope', kind: 'webgl', posterAt: 2000,
320
+ trick: 'Polar angle modulo one wedge, mirrored, over drifting glass chips',
321
+ notes: ['Each pixel is turned into an angle and a radius; the angle is wrapped into one wedge and mirrored at its edges, so whatever is in that wedge repeats round the circle with perfect symmetry.', 'The wedge holds moving Voronoi cells drawn as glass chips with dark edges, plus a warm light leak.', 'Red, green and blue are sampled at slightly different scales, a colour fringe like cheap mirrors; the image turns slowly and breathes in and out.'],
322
+ params: { mirrors: P(3, 16, 1, 'Mirrors'), zoom: P(0.5, 2.5, 0.05, 'Zoom') } },
323
+ { id: 'dither', cat: 'shaders', title: 'Ordered dither', kind: 'webgl', posterAt: 2000,
324
+ trick: 'A raymarched scene quantised against an 8×8 Bayer threshold matrix',
325
+ notes: ['A small 3D scene (a twisted ring, a bouncing ball, a floor) is raymarched and lit, then each pixel’s brightness is reduced to a couple of ink tones.', 'Each pixel compares its brightness with a threshold from an 8×8 Bayer matrix, built from interleaved bits. The thresholds are spread so evenly that the on/off pattern reads as smooth grey from a distance.', 'The canvas renders at a fraction of the resolution and is scaled up without smoothing, so the dither grid stays crisp and chunky.'],
326
+ params: { pixel: P(1, 6, 1, 'Pixel size'), tones: P(2, 6, 1, 'Tones') } },
327
+ ];