
The needle gauge
Counters, Physics, SVG
NewFree
A kudos counter: a glass jar of cat’s eye marbles with a paper tag tied round its neck with string, carrying the number. Give kudos drops a marble in from above the rim: it falls in, bounces off the glass and the others, rolls with its vanes turning and settles, and as it lands the number on the tag rolls up. Held, it pours one marble after another until the jar is full; minus plucks the top marble and lifts it out. The jar can be dragged sideways, and the marbles knock against the glass.
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">
<title>The marble jar</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the marble jar: a kudos counter, a glass jar of cat's eye marbles with a paper tag tied round its neck. Give
kudos drops a marble in from above: it falls into the jar, bounces off the glass and the other marbles, rolls
and settles, and the number on the tag rolls on. Held, the button pours a stream until the jar is full; the
minus button lifts the top marble out. The jar can be dragged sideways to rattle them. The glass is drawn by
the script in two layers, behind and in front of the marbles, which are a canvas -->
<div class="kj" data-kj>
<div class="kj__scene" role="button" tabindex="0"
aria-label="The kudos jar. Drag it sideways, or press Enter, to rattle the marbles" data-scene>
<div class="kj__move" data-move>
<svg class="kj__layer" viewBox="-170 -380 340 396" aria-hidden="true" data-art="back"></svg>
</div>
<canvas class="kj__layer kj__marbles" aria-hidden="true" data-canvas></canvas>
<div class="kj__move" data-move>
<span class="kj__edge" data-edge></span>
<span class="kj__edge" data-edge></span>
<svg class="kj__layer" viewBox="-170 -380 340 396" aria-hidden="true" data-art="front"></svg>
<div class="kj__tag" aria-hidden="true" data-tag>
<svg class="kj__string" viewBox="0 0 64 26" aria-hidden="true">
<path class="kj__string-back" d="M32 0C30.5 8 29.4 16 30 25.5"/>
<path class="kj__string-front" d="M32 0C33.6 8 34.8 16 34 25.5"/>
</svg>
<div class="kj__card">
<span class="kj__hole"></span>
<span class="kj__kicker">Kudos</span>
<span class="kj__count" data-count></span>
<span class="kj__for" data-for>for Marta</span>
</div>
</div>
</div>
</div>
<div class="kj__actions">
<button class="kj__btn kj__btn--less" type="button" aria-label="Take a kudos back" data-less>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.5 12h11"/></svg>
</button>
<button class="kj__btn kj__btn--more" type="button" aria-describedby="kj-hint" data-more data-autoclic>
<span class="kj__word" data-word>Give kudos</span>
<svg class="kj__plus" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 6.5v11M6.5 12h11"/></svg>
</button>
</div>
<p class="sr" id="kj-hint">Hold to pour.</p>
<p class="sr" aria-live="polite" data-live></p>
</div>
</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.
47 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 marble jar” from Animorio and fit it into my siteEach marble is a circle with a position, a speed, an angle and a spin, in the drawing’s units (a little more than a pixel). The world moves on GSAP’s ticker in fixed steps of 1/480 of a second (a slow frame counts as 1/30 at most): gravity (GRAVITY) adds speed, then the contacts are found between the marbles and with the inside of the jar, a box with rounded bottom corners, open at the top.
Each contact is solved with an impulse along the line between the centres: it can never pull, it takes away the speed with which two marbles close in and gives back BOUNCE of it (BOUNCE_GLASS against the glass), but only in a knock, when the contact is new and the marbles close in faster than 45 units a second: a contact that was already there a step ago is a resting one and never bounces, so the marbles come to rest instead of jittering. Across the contact there is friction, capped at FRICTION times the push: it acts on the contact point and turns the marbles, so they roll instead of sliding. The walls and the bottom move with the jar, and their speed enters the sums: that is how the marbles feel the jar being shaken.
The pushes of every contact are remembered from one step to the next and used again as a first guess (warm starting): a pile of forty marbles stands without trembling. After the pushes, whatever still overlaps is pulled apart by moving the marbles, without touching their speeds. Once every marble has been nearly still for a third of a second the world goes to sleep: nothing is drawn again until a marble arrives, one is taken out or the jar is shaken.
Each kind of marble is three pictures painted once for the screen’s density: the tinted glass, the vanes (pointed, twisted blades, deep in the middle and bright at the tips, with a few bubbles) and on top the dark edge, the light gathered low on the right and the highlight at the top left. Every frame they are laid down with drawImage, and only the vanes turn with the marble. Before them, on the bottom, each one’s shadow and a light of its colour.
The jar is SVG in two layers: behind the canvas the shadow on the page, the back of the glass, the far half of the rim and the floor; in front the thick bottom, the sides seen edge-on, the reflections, the near half of the rim and the string, its twist drawn as short slanted strokes. A falling marble goes through the mouth in front of the far rim and behind the near one. Dragged, the jar follows the hand on a spring, and the tag is a pendulum that feels its acceleration.
The real count changes at once on the click (the button, the limit, the words read out), but the tag counts the marbles that have arrived: at a new marble’s first touch the figure that changes rolls, the old one leaving at the top and the new one coming in with back.out. How many marbles the jar holds is worked out from their size, so not even in the Playground does the pile ever go over the rim.
| Name | File | Default | What it changes |
|---|---|---|---|
NAME | script.js | 'Marta' | Who the kudos are for: it is on the tag and in the words read out. |
START | script.js | 12 | The marbles already in the jar when the page opens, settled in their places. |
MAX | script.js | 40 | How many marbles the jar holds; with bigger marbles it holds fewer, and it never overflows. |
HOLD | script.js | [0.32, 0.12] | Holding the button: how long before it starts to pour, and how often a marble comes, in seconds. |
GRAVITY | script.js | 2600 | Gravity, in the drawing’s units per second squared: higher, the marbles fall and settle faster. |
BOUNCE | script.js | 0.42 | How much a marble bounces off another (0 = not at all, 1 = fully). |
BOUNCE_GLASS | script.js | 0.33 | How much a marble bounces off the glass of the jar. |
FRICTION | script.js | 0.3 | The friction of glass on glass: it is what makes the marbles roll instead of sliding. |
ROLLING | script.js | 1.4 | How quickly a rolling marble loses its spin, per second. |
SIZE | script.js | [16, 18.4] | The marbles’ radius, the smallest and the largest, in the drawing’s units (10 to 21). |
SHAKE | script.js | 32 | How far the jar can be dragged to either side. |
For counting something that piles up and is nice to watch grow: kudos or thanks in a team, stars given to a project, points in a collection, tips, the days in a row of a habit, the votes of a small ranking. It suits a person’s profile card, an internal board, an app for teams or for school, anywhere a rising number should feel like a reward rather than a statistic.
At 390 pixels the jar takes the whole width and the buttons stay 58 tall; nothing overflows sideways and the canvas uses the screen’s density, so the marbles stay sharp. A finger works like the mouse: a tap gives a marble, held down it pours, the jar can be dragged sideways with a finger (the page still scrolls up and down) and a tap on the glass is a knock. A long press opens no menu and two quick taps do not zoom the page.
No falls, bounces, rolling or shaking: the new marble appears already settled where it would have landed (the world runs all its steps at once, without drawing them) and fades in; the one taken out fades where it was and the others are already in place. The number changes with a fade instead of rolling, the tag does not swing and the jar cannot be dragged.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 19 KB; the libraries: GSAP 28 KB.
Tested on 7 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.