
The cube stories
Stories, 3D, Transitions
NewFree
The plan picker of a backup service, with every price on the wheels of an odometer: switching from monthly to yearly, or choosing a plan, the wheels really count to the new price, carrying from one wheel to the next, blurred while they spin, and land on it with a little spring; the total due today runs like a fuel pump.
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 odometer pricing</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/sofia-sans@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/sofia-sans-condensed@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the plans of a backup service. Every price stands on the wheels of a mechanical counter, drawn by the script:
changing the billing or the plan, the wheels run to the new price like an odometer, carrying from one wheel
to the next, blurred while they spin, and land on it with a little spring -->
<section class="picker" aria-labelledby="picker-title" data-picker data-autoclic>
<header class="picker__head">
<h2 class="picker__title" id="picker-title">Choose a plan</h2>
<p class="picker__sub">Backed up every night, kept far from home.</p>
</header>
<div class="billing" role="radiogroup" aria-label="Billing" data-billing>
<span class="billing__thumb" aria-hidden="true" data-thumb></span>
<span class="billing__row">
<button class="billing__opt" type="button" role="radio" aria-checked="true" data-period="month">Monthly</button>
<button class="billing__opt" type="button" role="radio" aria-checked="false" tabindex="-1" data-period="year">Yearly <span class="billing__save" data-save>Save 20%</span></button>
</span>
<!-- the same words in ink, cut to the size of the thumb: they change colour exactly at its edge -->
<span class="billing__row billing__row--ink" aria-hidden="true" data-ink>
<span class="billing__opt">Monthly</span>
<span class="billing__opt">Yearly <span class="billing__save" data-save>Save 20%</span></span>
</span>
</div>
<div class="plans" role="radiogroup" aria-labelledby="picker-title" data-plans></div>
<div class="due">
<div class="due__words">
<span class="due__title" id="due-title">Due today</span>
<span class="due__then" data-then><span class="due__then-in" data-then-in></span></span>
</div>
<div class="due__amount">
<span class="due__cur" aria-hidden="true">€</span>
<span class="wheels wheels--big" aria-hidden="true" data-meter></span>
<span class="sr" data-due-sr></span>
</div>
</div>
<button class="go" type="button" data-go>
<span class="go__face" data-go-face>
<svg class="go__spin" viewBox="0 0 20 20" aria-hidden="true" data-go-spin><path d="M10 2.5a7.5 7.5 0 1 1-7.5 7.5"/></svg>
<span class="go__label" data-go-label>Continue with Household</span>
</span>
</button>
<p class="sr" 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="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/CustomEase.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.
53 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 odometer pricing” from Animorio and fit it into my siteThe value is a number of cents that moves smoothly, and every wheel is worked out from it: the cents wheel is the value itself, every other wheel is the whole part of the value divided by its power of ten, plus how far the rest has gone past the last unit before the turn (from 0.99 to 1.00 the units turn together with the last cent). So the carries ripple up the row as in a machine, and a price going from €11.99 to €115.08 really counts every cent in between.
Each wheel is a cylinder with ten digits a tenth of a turn apart, drawn on a canvas: a digit turned by an angle φ sits R·sin φ from the middle and is squashed by cos φ in height, and a gradient darkens the wheel towards the top and the bottom, where it turns away. The cents have light wheels, like the tenths wheel of an odometer; a leading zero is a blank face, which fills in together with the carry of the wheel on its left.
While the wheels move, each frame is drawn several times along the time the shutter stays open (BLUR frames), one copy for every pixel and a half of travel, and the copies are added up on a clear sheet, which is then dyed the colour of the digits. A wheel covering more than a few digits a frame becomes the streaks its ten digits would leave: for each column of pixels the share of a turn that is ink, worked out once from the real digits.
The run follows the RUN curve in SPIN seconds to LAND[0] cents past the price, then a damped spring brings it back, starting from rest. The value can be worked out for any moment, not step by step: that is what lets the blur look a little into the past. Choosing a plan its wheels give a shake that goes backwards first, so a price ending in 9 does not set off the carry at once.
The plans are a radiogroup with the arrows, Home and End; the plan chosen opens on what it includes while the one before closes with the same curve and the same duration, and they all open to the height of the tallest list, so the card never changes size. The Monthly / Yearly control has on top a copy of its words in black, clipped with clip-path to the size of the thumb: the colour changes exactly at its edge.
A single loop on GSAP’s ticker redraws only the counters that are moving, once more when they stop, and then goes to sleep. A click from the library’s preview (detail 0) on the card switches between monthly and yearly and to the next plan in turn; an aria-live region reads out the new prices and the total.
| Name | File | Default | What it changes |
|---|---|---|---|
PLANS | script.js | [['Solo', '1 computer, 1 TB', 4.99, […]], …] | The plans: name, what it covers, price a month in euros, what comes with it. |
PICK | script.js | 1 | The plan recommended, chosen when the page opens (0 is the first). |
SAVE | script.js | 20 | How much less a month costs when paying yearly, in per cent. |
SPIN | script.js | 1.1 | The seconds the wheels take to run to the new price. |
RUN | script.js | 0.66, 0, 0.2, 1 | How the wheels speed up and slow down. |
LAND | script.js | [0.22, 15] | The landing: how many cents the wheels go past the price and how stiff the spring bringing them back is. |
BLUR | script.js | 1 | The blur of the spinning wheels: for how many frames the shutter stays open (0 = none). |
STAGGER | script.js | 0.07 | The seconds between one price setting off and the next. |
OPEN | script.js | 0.5 | The seconds a plan takes to open on what it includes. |
SIZE | script.js | [20, 32] | The height of the digits on the wheels, in the plans and in the total, in pixels. |
For choosing the plan of a subscription: the pricing page of an app or a service, the window for upgrading to a higher plan, the summary before paying. It suits places where the price should feel like a real reckoning, and where the yearly saving has to show.
At 390 pixels the card fits in 358, with the plans one under the other and the counters the same size; Most chosen wraps under the name. A tap chooses a plan like a click, the plans sink a touch under the finger and two quick taps do not zoom the page.
The wheels do not spin: the new price is in place at once and the counter fades in; the thumb goes to its place without sliding, the plans open and close at once and what they include fades in, and the line under Due today changes with a fade.
GSAP 3.15.0, Sofia Sans (font variabile) 5.3.0, Sofia Sans Condensed (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 13 KB; the libraries: GSAP + CustomEase 31 KB.
Tested on 6 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.