
The needle gauge
Counters, Physics, SVG
NewFree
How many scoops on a gelato cone: with plus a scoop drops onto the stack, squashes as it lands and rolls to the middle of the one below, and the tower sways on its springs, slower the taller it is; with minus the top one lifts off and goes. Past the most the tower teeters and stands back up, below the fewest the last scoop will not come off the cone.
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 cone stepper</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/opsz.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- how many scoops on a gelato cone: a card with the cone and, under it, a stepper. Every scoop added drops
onto the stack and squashes, and the tower sways on its springs; pushed past the most scoops it teeters,
and the last scoop will not come off. The scoops are drawn by the script -->
<div class="cone-card" data-card>
<svg class="scene" viewBox="0 0 340 300" aria-hidden="true" data-scene>
<defs>
<!-- one scoop, 60 wide, centred on its middle: a dome with a ruffled skirt where the scoop was turned -->
<path id="gelato-scoop" d="M-30 7C-31.5-13-17.5-29 0-29C17.5-29 31.5-13 30 7C30.8 9.5 32 12.5 30.6 15.6Q26.2 21.5 21.9 17.2Q17.5 23 13.1 16.4Q8.7 22 4.4 17.6Q0 23.4-4.4 16.8Q-8.7 22.2-13.1 17.4Q-17.5 23-21.9 16.6Q-26.2 21.6-30.6 15.6C-32 12.5-30.8 9.5-30 7Z"/>
<clipPath id="gelato-in-scoop"><use href="#gelato-scoop"/></clipPath>
<radialGradient id="gelato-round" cx="0.38" cy="0.3" r="0.78">
<stop offset="0" stop-color="#ffffff" stop-opacity="0.34"/>
<stop offset="0.34" stop-color="#ffffff" stop-opacity="0"/>
<stop offset="0.68" stop-color="#3c140a" stop-opacity="0"/>
<stop offset="1" stop-color="#3c140a" stop-opacity="0.26"/>
</radialGradient>
<linearGradient id="gelato-ruffle" x1="0" y1="0" x2="0" y2="1">
<stop offset="0.7" stop-color="#3c140a" stop-opacity="0"/>
<stop offset="0.75" stop-color="#3c140a" stop-opacity="0.09"/>
<stop offset="1" stop-color="#3c140a" stop-opacity="0.2"/>
</linearGradient>
<radialGradient id="gelato-contact">
<stop offset="0" stop-color="#3c140a" stop-opacity="0.34"/>
<stop offset="1" stop-color="#3c140a" stop-opacity="0"/>
</radialGradient>
<linearGradient id="gelato-wafer" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" class="wafer-light"/>
<stop offset="0.45" class="wafer-mid"/>
<stop offset="1" class="wafer-dark"/>
</linearGradient>
<radialGradient id="gelato-floor">
<stop offset="0" class="floor-shade" stop-opacity="0.3"/>
<stop offset="1" class="floor-shade" stop-opacity="0"/>
</radialGradient>
<clipPath id="gelato-in-cone"><path d="M-27.5 3H27.5L3.4 84.5Q0 90-3.4 84.5Z"/></clipPath>
<!-- the very top of the scene fades out, so a scoop dropped onto a tall stack comes in softly -->
<linearGradient id="gelato-fade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ffffff" stop-opacity="0"/>
<stop offset="0.1" stop-color="#ffffff" stop-opacity="1"/>
</linearGradient>
<mask id="gelato-top" maskUnits="userSpaceOnUse" x="0" y="0" width="340" height="300">
<rect width="340" height="300" fill="url(#gelato-fade)"/>
</mask>
</defs>
<g mask="url(#gelato-top)">
<!-- the world: the cone's rim at 0 0, the floor 100 below; the script scales it to fit the most scoops -->
<g data-world>
<ellipse class="floor" cx="0" cy="100" rx="30" ry="5" fill="url(#gelato-floor)" data-floor/>
<g data-cone>
<ellipse class="cone__mouth" cx="0" cy="-2.5" rx="28" ry="3.6"/>
<path class="cone__body" d="M-27.5 3H27.5L3.4 84.5Q0 90-3.4 84.5Z" fill="url(#gelato-wafer)"/>
<g class="cone__grid" clip-path="url(#gelato-in-cone)" data-grid></g>
<path class="cone__rim" d="M-30-2.5C-20-4.8 20-4.8 30-2.5L30.4 4.5C20 8.2-20 8.2-30.4 4.5Z"/>
<path class="cone__rim-edge" d="M-30.4 4.5C-20 8.2 20 8.2 30.4 4.5"/>
<path class="cone__rim-light" d="M-28-2C-18-4.2 18-4.2 28-2"/>
<ellipse class="cone__contact" cx="0" cy="3" rx="27" ry="6" fill="url(#gelato-contact)" data-cone-contact/>
</g>
<g data-scoops></g>
<g data-flying></g>
</g>
</g>
</svg>
<div class="order">
<div class="order__label">
<span class="order__name" id="scoops-name">Scoops</span>
<span class="order__note" id="scoops-note"><span class="order__note-in" data-note>€1.90 each</span></span>
</div>
<div class="stepper">
<button class="stepper__btn" type="button" aria-label="Remove one scoop" data-less>
<span class="stepper__disc" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M6.5 12h11"/></svg>
</span>
</button>
<input class="stepper__value" type="text" inputmode="numeric" autocomplete="off" spellcheck="false" maxlength="2" role="spinbutton" aria-labelledby="scoops-name" aria-describedby="scoops-note" aria-valuemin="1" aria-valuemax="4" aria-valuenow="3" aria-valuetext="3 scoops" value="3" data-value>
<button class="stepper__btn" type="button" aria-label="Add one scoop" data-more data-autoclic>
<span class="stepper__disc" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M6.5 12h11M12 6.5v11"/></svg>
</span>
</button>
</div>
</div>
<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.
41 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 cone stepper” from Animorio and fit it into my siteThe scoop is a single path in a box 60 wide: a dome and under it a skirt with seven lobes, the ruffled part where the scoop turned it. Every scoop reuses it with <use> and lays its flavour on it (a CSS variable), the bits scattered the same way every time by a seeded generator, two shadings (light at the top left, shade on the edge and on the skirt), the mark of the scoop and the shadow of the one above, cut by the shape itself with a clipPath in local coordinates. The cone has its wafer grid in two families of slanted lines, each with its line of light.
Every scoop rests on the one below at a joint: an angle on a spring, an offset from the middle that rolls back to zero and a height that squashes (1 = round). Every frame the tower is rebuilt from the cone up: the point it rests on, its middle (height times squash), the point it offers to the scoop above. The squash is anchored at the resting point, so a squashed scoop lowers all the ones above it and the blow runs along the stack.
Each joint is pulled sideways by the sine of the lean of every scoop above it: a tall tower is softer and slower than a short one. The load couples all the joints, and the softest way they can bend together is 1 / (4 sin²(π / (4n + 2))) times softer than a single joint (8.3 times with four scoops): the pull of the weight is kept at two thirds of that threshold, so even the tallest tower always stands back up.
A joint that speeds up pushes its neighbours the other way, like the inertia of a chain: the accelerations come from solving a symmetric tridiagonal mass matrix (1 on the diagonal, WHIP beside it) with the Thomas algorithm. Symmetric means the exchange between joints never adds energy: the top lags behind the base and the whip dies out. Everything is integrated with semi-implicit Euler in steps of 1/240 of a second, with the frame step capped at 1/30.
A new scoop starts just above the card at the speed THROW and falls with GRAVITY, guided towards the top of the stack but never exactly to the middle. When its middle reaches the right height above the resting point it becomes part of the tower keeping exactly the place it had: the offset from the middle comes from the sideways distance, the angle from the lean of the stack, the squash from the vertical distance. Then it takes the blow: the squash, a share for the scoops below, the cone giving way, and a shove to the joints on the side it landed. With quick clicks every scoop starts above the one before, still in the air, and a minus sends the highest falling one back up first.
Past the most every joint gets a speed to the left, a little more going up, so the top leans about TEETER degrees, and the scoops slip on each other; repeated shoves add less and less and two soft stops (on every joint and on the lean of every scoop) never let it fall. Below the fewest a GSAP timeline tugs the last scoop up 9 pixels and lets it drop. A button held down repeats faster and faster (HOLD); the number is a field with role="spinbutton" and aria-valuenow, it answers the arrows, Home and End, and takes the digit typed wherever the caret was; an aria-live region says the number once the clicking is over, and always the limits.
| Name | File | Default | What it changes |
|---|---|---|---|
MIN | script.js | 1 | The fewest scoops (0 lets the cone go empty). |
MAX | script.js | 4 | The most scoops, up to 7: above 4 the cone gets smaller to fit them. |
START | script.js | 3 | The scoops when the page opens. |
THROW | script.js | 600 | How fast a scoop comes in from the top of the card, in pixels per second. |
GRAVITY | script.js | 2400 | How fast a falling scoop speeds up, in pixels per second per second. |
SQUASH | script.js | 0.2 | How much a scoop squashes as it lands (0 = not at all). |
SWAY | script.js | [100, 0.3] | The tower’s springs: how stiff they are and how much they are held back (1 = no sway). |
TEETER | script.js | 17 | How many degrees the tower leans when pushed past the most. |
HOLD | script.js | [0.42, 0.16, 0.06] | Holding a button down: the wait before it repeats, the first gap and the shortest, in seconds. |
WORD | script.js | ['scoop', 'scoops'] | The word read after the number, for one and for more. |
For choosing how many where the amount can be shown: scoops, portions, slices, tiers of a cake, books on a pile, boxes in a shipment. It suits a takeaway order or a product configurator, where the limit is real (a cone only holds so much) and is worth feeling: the teetering tower says it before the words do.
On a phone the card fits in 390 pixels with everything it has, and the buttons are 48 pixel discs. A finger works like the mouse: a tap counts at once, held down it repeats faster and faster and past the most it keeps the tower bent; a long press opens no menu and two quick taps do not zoom the page. Tapping the number opens the numeric keyboard.
The scoops do not fall or squash and the tower does not sway: a scoop added appears in its place with a fade and one taken off fades where it is. Past the limits the tower stays still and only the note under the label changes, fading instead of sliding; the buttons do not shrink when pressed.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 15 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.