Skip to content
animorio
IT
All components

The Galton board forecast

Loading, Physics, TransitionsCanvas, GSAPAdvancedCompressed weight: Demo 18 KBGSAP 28 KBFreeDownload the zip

A quarter’s revenue forecast run as a Monte Carlo simulation and shown as a Galton board behind glass: a hopper full of steel beads, a small gate in its neck, eight rows of brass pins and nine bins. Every bead is a batch of simulated quarters: it goes through the gate, bounces down the pins, which light up when it strikes them, and lands in the bin of its result. The piles grow into a bell while the count climbs; at the end the bell is drawn over the piles, the beads inside the 90% range take its colour and the median line rises, read off the beads that really landed. If the data does not arrive the gate jams red; Retry knocks the board and the beads flow again.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
  <title>The Galton board forecast</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the Galton board forecast: a revenue forecast run as a Monte Carlo simulation, shown as a Galton board behind
         glass. Every bead is a batch of simulated quarters: it leaves the hopper through the gate, bounces down the
         brass pins (each one lights up when it is struck) and lands in the bin of its result. The piles grow into a
         bell; at the end the curve is drawn over them, the 90% range lights up and the median is read off the beads
         themselves. The board is painted by script.js -->
    <section class="fc" aria-labelledby="fc-title" data-forecast>
      <div class="fc__board" role="progressbar" aria-labelledby="fc-title" aria-valuemin="0" aria-valuemax="100"
           aria-valuenow="0" aria-valuetext="Not started" data-bar>
        <canvas class="fc__canvas" aria-hidden="true" data-board></canvas>
        <p class="fc__axis" aria-hidden="true">
          <span class="fc__tick-label" data-axis="0"></span>
          <span class="fc__tick-label" data-axis="1"></span>
          <span class="fc__tick-label" data-axis="2"></span>
        </p>
      </div>
      <div class="fc__head">
        <div class="fc__words">
          <h2 class="fc__title" id="fc-title"><span class="fc__swap" data-title>Q4 revenue forecast</span></h2>
          <p class="fc__meta"><span class="fc__swap" data-meta>10,000 simulated quarters</span></p>
        </div>
        <p class="fc__figure" aria-hidden="true">
          <span class="fc__pct" data-pct></span>
          <svg class="fc__done" viewBox="0 0 28 28" data-tick><path d="M6.5 14.8l5 5 10-11.4" pathLength="1"/></svg>
        </p>
      </div>
      <button class="pill" type="button" data-go data-autoclic><span class="pill__label" data-label>Run forecast</span></button>
      <p class="sr" role="status" aria-live="polite" data-live></p>
    </section>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

43 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The Galton board forecast” from Animorio and fit it into my site

What it does

On a black background, alone in the middle, a Galton board behind glass: a midnight blue panel with a long slanting reflection, at the top a funnel hopper with brass edges full of a hundred polished steel beads, under it a narrow neck closed by a small gate, then a triangle of eight rows of brass pins and at the bottom nine bins split by thin walls, with the scale $1.0M, $1.4M and $1.8M under the floor. The first time the beads rain down into the hopper and settle. Under the board are the title Q4 revenue forecast, the line 10,000 simulated quarters and the light Run forecast button. Pointing at the button (or reaching it with the keyboard, or touching it with a finger) makes the beads at the top of the hopper jump and the gate give a small twitch. Pressed, the button becomes Simulating, light with a ring, the percentage appears on the right and the gate starts letting the beads through one at a time, sliding aside: each bead falls on the first pin, goes right or left, hits the pin of the row below, and so on to the bottom; the pins stay still and polished, with no lights (GLOW, at 0, makes them light up when struck). The beads fall into the bins and heap up in little mounds; the piles grow into a bell while the line counts 4,200 of 10,000 quarters. When the last bead is still, a mint green curve is drawn from left to right over the piles, the 90% range is tinted lightly, the beads inside it turn green and the others go dark, and a dashed line rises from the floor to the curve: the median. A green tick is drawn where the percentage was, the title becomes Forecast ready and the line says for example Median $1.40M · 90% $1.18–1.62M, worked out from the beads that really landed, so a little different every time. Run again opens the floor of the bins like nine trapdoors: the beads drop out, rain back into the hopper from above, the trapdoors close with a bounce and the forecast runs again. Once in two, in the demo, half way the data does not arrive: the gate jams and glows red, the beads already on their way finish their run, the red title Data source timed out shakes and the button says Retry, which knocks the board (everything jumps) and lets the flow go on from where it stopped. A tap on the board, while it is not showing a result, gives it a little knock.

How it works

Beads with position based dynamics

Every bead has a position and a speed; a frame’s time (never more than 1/30 of a second) is cut into four steps. At every step the awake beads take gravity and move, then for four passes the overlaps are pushed apart: bead against bead (half each, all of it to the awake one if the other is asleep), against the walls as segments with their thickness, against the gate, the floor and the pins. At the end the speed is what the position really did, (x − old x) ÷ step, but never more than 30 units a second above the old one: so pushing an overlap apart does not throw the beads and the hopper does not boil.

The coin toss on every pin

A pin gives back part of the speed it took (BOUNCE, along the line between the centres) and, the first time a bead touches it, picks a side at random. The sideways push is worked out to carry the bead half a pitch, 12 units, by the row below: the fall time is t = (−v + √(v² + 2·g·DY)) ÷ g, and the sideways speed is 12 ÷ t times SCATTER, give or take 8%. So every bead makes eight coin tosses and the bell comes by itself. Two beads in flight among the pins pass each other without colliding: with collisions in flight the bell came out too wide.

The hopper and the gate

The neck is wider than two beads (12.4 units): with a neck of a bead and a half two beads wedged in an arch over the hole and the flow stopped. The gate holds every bead that has no leave; a GSAP timeline says how many should have gone, and every frame, if one is missing, the lowest bead on the gate gets leave and goes through, while the gate slides aside and back. If no bead reaches the gate for 0.35 seconds, the ones in the neck get a small shake.

Piles that sleep, and the bell

A bead nearly still in a bin for 0.3 seconds falls asleep: it does not move any more and is a fixed support for the others. The piles stay still and the loop is cheap; when nothing moves the canvas is not painted. A bead that lands right on top of a still one is moved once a little towards the middle of its bin, so the piles grow as mounds and not as towers. At the end the beads in every bin are counted: mean and spread give the bell’s shape, its height comes from the real tops of the piles (least squares), and the median and the 90% range are read from the counts, interpolating inside the bin.

Painting, the end and those who cannot see

The panel, the pins and the glass in front are painted once into two pictures of their own; every frame the pins’ glow (a soft sprite in lighter, fading as e^(−3.4·t)), the shadows and the beads (steel sprites made at the screen’s density), the floor’s trapdoors, the gate and the bell with its glow are painted. The panel is a role=progressbar with aria-valuenow and aria-valuetext («42 percent, 4,200 of 10,000 quarters») at most every 0.6 seconds, aria-busy while it works and an aria-live line at the start, at the end with the median and when it jams. Clicks from a script (the library preview’s) wait 2.4 seconds after the end before running again.

Values to change

NameFileDefaultWhat it changes
BEADSscript.js100How many beads are in the hopper: each one is a batch of simulations.
SIMULATIONSscript.js10000How many quarters the forecast simulates: it is the number counted under the title.
SECONDSscript.js7How long the demo’s forecast takes, in seconds (a real one follows the computation).
RANGEscript.js[1.0, 1.8]The value of the left and right edge of the bins, in millions: the scale, the median and the 90% range are read from it.
MONEYscript.js{ before: '$', after: 'M', digits: 2 }How the figures are written: what goes before, what after and how many decimals.
GRAVITYscript.js760How hard the beads fall, in drawing units per second squared.
BOUNCEscript.js0.3How much the beads bounce on the pins: 0 not at all, 0.7 a lot.
SCATTERscript.js1How hard every pin pushes the bead sideways: the higher, the wider the bell.
GLOWscript.js0How much the struck pins light up: 0 not at all, 2 a lot.
FAIL_EVERYscript.js2In the demo, one forecast in how many jams because the data does not arrive; 0 never.
FAIL_ATscript.js0.45How far through the forecast the jam comes, from 0 to 1.
LABELSscript.js{ busy: 'Simulating', done: 'Run again', retry: 'Retry' }The button’s words while it works, at the end and after a jam; the one at rest, Run forecast, is changed in index.html.
WORDSscript.js{ busy: 'Running simulations', done: 'Forecast ready', failed: 'Data source timed out' }The title while it works, at the end and after a jam; the one at rest, Q4 revenue forecast, is changed in index.html.
NOTESscript.js{ of: 'of', unit: 'quarters', median: 'Median', range: '90%', failed: 'Sales data did not respond' }The words of the line under the title: the count, the result and the jam; the one at rest is changed in index.html.

Try them in the Playground

When to use it

For a computation of a few seconds whose result is a distribution: a revenue forecast, a risk simulation, an A/B test closing, an estimate of delivery times, a budget with several scenarios. While you wait the board shows how the result comes about, and at the end the result is the very shape you watched grow. It suits a finance, data analysis or planning app. For any loader where there is nothing to distribute it tells the wrong story: a bar is better.

On phones

The component is at most 320 pixels wide, so at 390 it all fits with its margins; the canvas paints at the screen’s real density, up to twice, and the physics is in drawing units, the same at any size. The beads and pins are small but sharp. The button is 48 tall and under a finger it squashes and springs back; the jump of the beads that the mouse brings on the way past comes with a finger when you touch it. A tap on the board gives it a little knock; the page’s vertical scroll passes over the canvas.

With reduced motion

No falling beads, glowing pins, knocks or trapdoors: the hopper is already full and still. During the forecast the top bead of the hopper fades out and another fades in at its place in a bin, picked with eight coin tosses, so the piles grow along with the percentage. At the end the bell, the 90% range and the median fade in, without drawing themselves; Run again fades the board and brings it back full.

Pitfalls

  • With a neck a bead and a half wide two beads leaned each on a wall and wedged in an arch over the hole: the flow stopped after two beads and shaking did not help, because the arch formed again. The neck is now wider than two beads and the gate still lets through only the one with leave.
  • To keep the beads in the hopper there was a limit holding them inside at every step, a little narrower than the real wall: wall and limit fought over the beads at the edge, and the whole hopper boiled without ever stopping (average speed 40 units a second, at rest). The limit now only brings back a bead that has really gone past the wall.
  • With collisions between beads in flight and a fixed push on the pins, the beads skipped rows and the bell came out wide, with five beads in the edge bins instead of half a one. The push now carries every bead exactly half a pitch by the row below and beads in flight do not collide: eight pins a bead on average and a real bell.
  • Beads that fell asleep at once in the bins stopped balanced on each other and the piles grew as towers against the walls. A bead now falls asleep only under 4 units a second for 0.3 seconds, and one landing right on top of a still one is moved once towards the middle: the piles are mounds.
  • The count changed many times a second with the words’ fade and stayed half faded: the fade now comes only when the line changes meaning, and the number is written straight in.

Libraries

GSAP 3.15.0, Archivo (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 18 KB; the libraries: GSAP 28 KB.

Testing

Tested on 13 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All components