
The needle gauge
Counters, Physics, SVG
NewFree
In the minimal style: the people inside a small room, counted twice, with one big thin number and with old-fashioned tally marks on a hairline. With plus the pen draws a mark from the top down, which presses a pixel too far and comes back up; every five the stroke across, the only colour, runs over the four, which rock one after the other and draw together into a bundle. The line ends at a small wall: when the room is full, one plus too many shoves the whole row against the wall, which bends and comes back.
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 tally counter</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the door count of a small room: how many people are in, a minus and a plus, and the same count kept the old
way, as tally marks on a hairline. Every fifth mark is the stroke across the four; the hairline ends at a
thin wall, the size of the room. The marks, the line and the wall are drawn by the script -->
<div class="tally" data-tally>
<div class="tally__head">
<p class="tally__label" id="tally-label">Checked in</p>
<p class="tally__note" id="tally-note" data-note></p>
</div>
<div class="tally__row">
<div class="tally__value" role="spinbutton" tabindex="0" aria-labelledby="tally-label" aria-describedby="tally-note" aria-valuemin="0" aria-valuemax="25" aria-valuenow="7" aria-valuetext="7 people" data-value>
<span class="tally__num" aria-hidden="true" data-num></span>
</div>
<div class="tally__keys">
<button class="tally__btn" type="button" aria-label="One person out" data-less>
<span class="tally__btn-in" aria-hidden="true">
<svg viewBox="0 0 48 48">
<circle class="tally__ring" cx="24" cy="24" r="23.25"/>
<path class="tally__icon" d="M17.5 24h13"/>
</svg>
</span>
</button>
<button class="tally__btn" type="button" aria-label="One person in" data-more data-autoclic>
<span class="tally__btn-in" aria-hidden="true">
<svg viewBox="0 0 48 48">
<circle class="tally__ring" cx="24" cy="24" r="23.25"/>
<path class="tally__icon" d="M17.5 24h13M24 17.5v13"/>
</svg>
</span>
</button>
</div>
</div>
<svg class="tally__field" aria-hidden="true" data-field>
<path class="tally__base" d="" data-base/>
<path class="tally__wall" d="" data-wall/>
<g data-marks></g>
</svg>
<p class="sr" aria-live="polite" data-said></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.
26 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 tally counter” from Animorio and fit it into my siteEach mark is an SVG line redrawn every frame, with a foot standing still on the line and a top: the top sits at the mark’s length (a little different from one to the next, with a sine, so they look handwritten but tidy) and at its rocking angle. The stroke is drawn for the fraction p: a new mark goes from p 0 to 1 in DRAW seconds with power2.in, from the top down, like a pen speeding up; minus takes it back to 0 and the mark goes back up. The round cap of a stroke just begun would be a speck, so the mark fades in over the first 14% of the stroke.
The fifth mark has no place of its own: its two ends are worked out from the feet of the first and the fourth of the bundle, six pixels further out, from 20% to 80% of the height. While it is drawn (power3.inOut, SLASH seconds) every frame looks at where its tip is: when it passes a mark’s foot, that mark gets a push on its angle, so the four rock one after the other to the beat of the stroke. Going back with minus the tip passes the marks the other way and gives a smaller push the other way.
Each mark has three damped springs: the angle on its foot (SPRING, 320 and 0.3), the press from the top (900 and 0.34: landing, the mark gets a speed that shortens it by about one pixel and comes back) and the sideways shift that draws a closed bundle together by 16% around its middle. The loop takes steps of 1/240 of a second at most, a slow frame counts as 1/30, and it takes itself off GSAP’s ticker when everything is still.
The number is set in Roboto Flex, whose width runs from 25 to 151. Each figure sits in a cell as wide as a zero, measured at the start and at every change of size; changing, the old figure goes to wdth 25 and fades, the new one starts at 25 and reaches 100 with back.out, which takes it a hair past and settles it. The units change first, the tens 50 thousandths later. With two figures the tens cell is born zero wide and opens, pushing the units along; when the count changes faster than every 0.24 seconds (holding a button) the swap takes less than half the time, so there is always a figure to read. The beat of weight when a bundle closes is a CSS variable, --wg, which GSAP takes from WEIGHT[0] to WEIGHT[1] and back with an elastic.
At the most, plus changes nothing: a shove spring moves the row towards the wall, more on the left and not at all next to the wall, so the bundles close up; each mark gets its push after a delay of its distance divided by 900 pixels a second, and when the wave arrives the wall, a quadratic curve, bends and takes the colour for half a second. A button counts on the press (pointerdown) and repeats while held; Enter and Space count in the click. The scripted click of the library’s preview plays a whole tour in a GSAP timeline: up to a full room, one plus too many, and back down to where it was.
| Name | File | Default | What it changes |
|---|---|---|---|
MAX | script.js | 25 | How many people the room holds: the highest number and the marks that fit up to the wall (5 to 40). |
START | script.js | 7 | How many people are inside when the page opens. |
DRAW | script.js | 0.16 | How long the pen takes to draw one mark, in seconds. |
SLASH | script.js | 0.26 | How long the stroke across a five takes, in seconds. |
ROCK | script.js | 1 | How much the marks rock when the stroke crosses them or the row hits the wall (0 = not at all). |
SPRING | script.js | [320, 0.3] | The marks’ spring: how stiff it is and how much it is held back (1 = no wobble). |
WEIGHT | script.js | [280, 560] | The number’s weight at rest and on the beat when a bundle of five closes (100 to 1000). |
HOLD | script.js | [0.42, 0.18, 0.07] | Holding a button: how long before it starts repeating, the first gap and the shortest, in seconds. |
WORD | script.js | ['person', 'people'] | The word for one and for more than one, read out to people using a screen reader. |
NOTE | script.js | ['Room holds {max}', 'Full house', 'No one is in yet'] | The note at the top right: usually, when the room is full and pressing minus at zero; {max} becomes the highest number. |
For counting by hand something that grows one at a time and has a ceiling: the people coming through the door of an event or a workshop, the covers of an evening, the places on a guided tour, the reps of an exercise in an app. The marks tell at a glance how far it is to the wall, even without reading the number.
The number shrinks with the screen (a quarter of its width at most) and the bundles spread over the width there is, up to the wall: at 390 pixels five bundles sit in a row without overflowing, and with many bundles they get a little smaller. A finger works like the mouse: a tap on a button counts one and makes it give, held down it repeats faster and faster. A long press opens no menu and two quick taps do not zoom the page.
No pen, rocking, shoves or bending walls: a new mark fades in already whole, one taken away fades out, the bundle is already drawn together, the figures change with a fade without squeezing and the note changes with a fade. Buttons and keys work as always.
GSAP 3.15.0, Roboto Flex (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 18 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.