
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
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.
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>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
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 siteEvery 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.
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 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.
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.
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.
| Name | File | Default | What it changes |
|---|---|---|---|
BEADS | script.js | 100 | How many beads are in the hopper: each one is a batch of simulations. |
SIMULATIONS | script.js | 10000 | How many quarters the forecast simulates: it is the number counted under the title. |
SECONDS | script.js | 7 | How long the demo’s forecast takes, in seconds (a real one follows the computation). |
RANGE | script.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. |
MONEY | script.js | { before: '$', after: 'M', digits: 2 } | How the figures are written: what goes before, what after and how many decimals. |
GRAVITY | script.js | 760 | How hard the beads fall, in drawing units per second squared. |
BOUNCE | script.js | 0.3 | How much the beads bounce on the pins: 0 not at all, 0.7 a lot. |
SCATTER | script.js | 1 | How hard every pin pushes the bead sideways: the higher, the wider the bell. |
GLOW | script.js | 0 | How much the struck pins light up: 0 not at all, 2 a lot. |
FAIL_EVERY | script.js | 2 | In the demo, one forecast in how many jams because the data does not arrive; 0 never. |
FAIL_AT | script.js | 0.45 | How far through the forecast the jam comes, from 0 to 1. |
LABELS | script.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. |
WORDS | script.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. |
NOTES | script.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. |
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.
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.
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.
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.
Tested on 13 October 2026
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.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.