
The time wheel
Pickers, 3D, Physics
NewFree
A shop's Checkout · €48.00 as an enamelled cash register of the old days: in the oxblood pill, lined with gold, a keyboard of nine keys in three columns built in CSS 3D, over it a gilt sign with the window of the total, under it the drawer. At a click the keys of the total go down one after the other and come back up on their springs, the flags 4, 8 and 00 jump up in the window swinging on their rods, the drawer's spring kicks it out, it knocks on its stop and bounces, and the words become Paid · Order 4821. Another click pushes the drawer shut with a thud and the flags drop 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 cash register button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/big-shoulders-display@5.3.0/wght.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the cash register button: the Checkout of a shop as an enamelled cash register of the old days. In the pill a
bank of keys in columns, over it the window where the amount flags come up, under it the cash drawer. A click
rings the total up: the keys go down one after the other on their springs, the flags with the total jump up
in the window, the drawer is kicked out and bounces on its stop, and the pill says Paid with the order number.
Another click pushes the drawer shut and the register is back at rest -->
<div class="till" data-till>
<div class="till__unit">
<!-- the drawer under the pill: the tray with the notes and the coins, and its front -->
<div class="till__drawer" aria-hidden="true" data-drawer>
<div class="till__slide" data-slide>
<svg class="till__tray" viewBox="0 0 170 40" preserveAspectRatio="none" data-tray></svg>
<div class="till__front">
<span class="till__pull"></span>
</div>
</div>
</div>
<div class="till__body" data-body>
<!-- the sign over the pill: a gilt frame, and behind its glass the flags of the amount -->
<div class="till__sign" aria-hidden="true">
<span class="till__crest">Amount</span>
<span class="till__window" data-window>
<span class="till__mark">€</span>
<span class="till__slot" data-slot="tens"><span class="till__flag" data-flag="tens">4</span></span>
<span class="till__slot" data-slot="units"><span class="till__flag" data-flag="units">8</span></span>
<span class="till__mark till__mark--dot">.</span>
<span class="till__slot till__slot--wide" data-slot="cents"><span class="till__flag" data-flag="cents">00</span></span>
</span>
</div>
<button class="till__btn" type="button" data-btn data-autoclic>
<span class="till__fill" aria-hidden="true"></span>
<!-- the keys: three columns of three, built in CSS 3D by script.js -->
<span class="till__bed" aria-hidden="true">
<span class="till__bank" data-bank></span>
</span>
<span class="till__faces">
<span class="till__face" data-face="rest">
<span class="till__word" data-wide="Checkout · €48.00" data-rest-text>Checkout · €48.00</span>
</span>
<span class="till__face" data-face="busy" aria-hidden="true">
<span class="till__quiet" data-busy-text>Ringing up…</span>
</span>
<span class="till__face" data-face="done" aria-hidden="true">
<span class="till__paid" data-paid-text>Paid</span>
<span class="till__sep">·</span>
<span class="till__order" data-order-text>Order 4821</span>
</span>
</span>
</button>
</div>
</div>
<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.
30 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 cash register button” from Animorio and fit it into my siteA plane 76 pixels wide is turned by rotateX(52deg) inside a recess with perspective: 190px, so the keys are seen a little from above and the back ones look further away. Each key is a pile of eight discs with transform-style: preserve-3d: a blurred shadow on the plane, four narrow discs for the stem, two nickel rims (a conic gradient) and the celluloid face with its figure. Each disc sits at translateZ(var(--h) · var(--f)), where --f is its share of the height: the script writes only --h per key, and pressing squeezes the whole pile together, the stem disappearing under the face.
Chrome paints a layer in 3D at the size it has before its transform: keys built at 16 pixels came out grainy and the figures read badly. Now the keys are built four times larger (64 pixels, figures at 29) inside a box with scale3d(0.25, 0.25, 0.25), and the heights are multiplied by four too: scale3d shrinks the Z axis as well, so the perspective stays right and the faces come out sharp at any screen density.
Each key's height is a spring: h'' = ω² · (H0 − TRAVEL · press − h) − 2 · ζ · ω · h', with ω = 2π · 9 and ζ = KEY_BOUNCE. GSAP only moves press, the finger: it goes down in 75 milliseconds with power2.in, stays down a moment and lifts at once; it is the spring that brings the key back up, past its place and back. The plane cannot be gone through: under 0.6 pixels the key stops. When the pointer passes, each key gets a small speed, column by column 45 milliseconds apart, and trembles on its spring.
Each flag sits in a slot with overflow: hidden and perspective: 60px, hidden under the lip with translateY(105%). When its key bottoms out, the target of its spring (ω = 2π · 5.5, damping 0.26) goes to 1 and it gets a push: it rises, goes a little past and stops. As it rises it tilts on its rod with a rotateX proportional to its speed, so it looks mounted on a rod and not on a rail. The figures come from TOTAL: tens, units and cents; the keys of each column are the figure needed and two neighbours, so changing the total in the Playground rebuilds the keyboard and the flags by themselves.
The drawer is a tray in SVG (notes in their bays, coins in their cups, the shadow under the register) and an enamelled front, inside a box with overflow: hidden that starts under the pill. The spring pushes it with a force that weakens as it opens, 2600 · KICK · (1 − x / (1.6 · OPEN)), and the runners hold it back a little; at OPEN pixels it knocks on its stop and comes back with 38% of its speed, two or three times. Each knock pushes the register's vertical spring, and it jolts. At the first knock the pill says Paid and the order number. To shut it a power2.in tween takes it back to zero, then the thud: the register jumps, the pill's fill squashes and the flags drop, from the cents to the tens.
Three faces on top of each other, as wide as their words (Checkout · €48.00, Ringing up…, Paid · Order 4821), the pill measured once on the widest. The button is a real button with aria-disabled while the keys strike and the drawer opens or shuts, an aria-live region that says Ringing up…, then Paid. Order 4821, €48.00. and at last Drawer closed, and once open an aria-label that says Close the drawer. Ringing the total up and opening the drawer take about 1.5 seconds, less than the 2.8 between two clicks of the preview: one click opens, the next shuts, and each sale takes the next order number. The physics loop sleeps when everything is still and no tween is working.
| Name | File | Default | What it changes |
|---|---|---|---|
LABEL | script.js | Checkout | The words at rest, before the total. |
TOTAL | script.js | 48 | The total in euros, from 0.01 to 99.99: the keyboard puts the figure needed and two neighbours in each column, and the flags show the total. |
BUSY | script.js | Ringing up… | The words while the keys strike; the aria-live region says them too. |
PAID | script.js | Paid | The gold word once the drawer is out, before the order number. |
ORDER | script.js | 4821 | The number of the first order: each sale takes the next. In a real site it is the number your server gives the order. |
KEY_GAP | script.js | 0.3 | The seconds from one key to the next. |
TRAVEL | script.js | 6 | How far a pressed key goes down, in pixels (a key stands 9 high). |
KEY_BOUNCE | script.js | 0.18 | How the keys come back up: 0.05 they bounce a long time, 0.18 they pass their place and come back, 1 they rise without passing it. |
KICK | script.js | 1 | How hard the spring kicks the drawer out: 0.5 it slides out, 2 it slams the stop and bounces more. |
OPEN | script.js | 34 | How far the drawer comes out, in pixels: the further, the more of the tray shows. |
For the button that closes a purchase: the Checkout or Pay of an online shop, the till of a point-of-sale app, a café or a market with a period identity, a pastry shop, a barber, a record shop. It has to be wired to a real payment: here the payment is simulated, and in a real site the drawer opens when the server confirms the order.
The pill is about 280 pixels wide, with the sign over it and the drawer under it: at 390 pixels it fits with room to spare. A tap makes the keys tremble, squashes the fill and rings the total up; another tap shuts the drawer. The keys keep their size and stay sharp at density 3 too.
The keys do not move: the face of the one pressed warms with gold and goes back to ivory. The flags fade in already in place, the drawer fades out shut and back in open, with no bounce and no jolt, and the words fade in. The pill does not swell and the words do not widen (under the mouse the fill only lightens a little). The next click shuts it the same way.
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0, Big Shoulders Display (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 10 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.