
The time wheel
Pickers, 3D, Physics
NewFree
A gelateria's game: a die of clear red resin in a walnut tray lined with pistachio felt. It is picked up with a finger and thrown, or Roll throws it, and from then on it is a real rigid body: it bounces off the felt and the sides, tumbles, stops on a face, and the face up picks the flavour. The light from behind makes the resin glow, the pips of the far faces show through, the shadow carries a patch of red light, and a six rains sprinkles into the tray.
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 gelato die</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the gelato die: a translucent resin die in a felt tray, thrown by hand or by Roll. It is a rigid body that
tumbles, knocks the walls and settles on a face; the face up picks the scoop. The tray comes first: above it
there is room for the die to fly -->
<section class="game" aria-labelledby="game-title" data-game data-autoclic>
<div class="tray" role="img" aria-label="A cherry-red die in a felt tray" data-tray>
<div class="tray__world" data-world>
<div class="tray__felt"><canvas class="tray__shadow" data-shadow></canvas></div>
<div class="tray__side tray__side--back"></div>
<div class="tray__side tray__side--left"></div>
<div class="tray__side tray__side--right"></div>
<div class="tray__rim"></div>
<div class="tray__side tray__side--front"></div>
<div class="die" data-die></div>
</div>
<canvas class="tray__air" aria-hidden="true" data-air></canvas>
</div>
<header class="game__head">
<h2 class="game__title" id="game-title">Roll for your scoop</h2>
<p class="game__rule">Every face is half price today. Six is on us.</p>
</header>
<ul class="menu" aria-label="What each face gives" data-menu></ul>
<button class="roll" type="button" data-roll><span class="roll__label" data-roll-label>Roll the die</span></button>
<div class="result is-hint" aria-live="polite" data-result>
<p class="result__big" data-big>Pick up the die and throw it</p>
<p class="result__small" data-small></p>
</div>
</section>
</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.
46 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 gelato die” from Animorio and fit it into my siteThe floor of the tray is a div tipped back 46 degrees with rotateX, in a box with perspective; the sides are divs turned 90 degrees on its edges and the rim is a frame raised with translateZ. The die stands on the floor: six faces around a middle (translateZ by half a side, turned with rotateX and rotateY) and on it a single matrix3d, written every frame from the position and the quaternion of the physics. So the tray, the felt and the die share one space, and the browser sorts them by depth.
The die has a position, a speed, an orientation (a quaternion) and a spin; its inertia is the same about every axis, a cube's (mass times side squared over six). At each step of 1/240 of a second gravity pulls it down, then the eight corners touching the felt or a side are found: for each one an impulse along the normal, with the bounce of BOUNCE only if the hit is hard, and a friction impulse against the sliding, never more than GRIP times the push. The impulses are solved eight times over and add up, so four corners resting share the weight and the die lies still without trembling.
When the die touches something and stays nearly still for a fifth of a second, it has stopped: the face up is the one whose normal is most upright. If it leans on a side it gets a nudge and goes again; if it lies flat it is squared up by the degree it lacks, the loop stops and the flavour is read. After eight seconds it stops anyway.
With the tray pressed, the point under the finger is found by casting a ray from the eye through the pointer to the level HOLD above the felt (the same perspective as the CSS, solved backwards). The die follows it on a short spring and turns in the fingers. Let go, it takes the speed of the hand over the last tenth of a second (none, if the hand had stopped), a little lift and a spin the way of the throw; under FLICK pixels a second it is a die set down and does not count. Roll is a pretend hand: a tween carries the die to the front and throws it in an arc at the far half with THROW and SPIN, a little different every time.
The light comes from behind and above. Every frame, for every face, from its normal: the opacity grows edge-on (--a, the resin looks thicker and redder), the glow of the light that goes through the die to the eye (--glow), the shine of the light sent to the eye (--shine, with the halfway vector between light and eye) and the pips of the far faces dimmed (--far). The shadow is a canvas laid on the felt: the convex hull of the eight corners projected along the light, blurred with the shadowBlur trick, plus a dark print under the die when it touches down and, inside the shadow, a patch in the colour of the resin.
On the JACKPOT, SPRINKLES sprinkles fall: little rods tumbling in the air, held back by it, drawn on a canvas over the tray with the same perspective; once on the felt they bounce once and move to the felt's canvas, under the die's shadow, until the die is picked up again. A click from a script (the library's preview) presses Roll, once the die has lain still for a while.
| Name | File | Default | What it changes |
|---|---|---|---|
FLAVOURS | script.js | [['Lemon sorbet', '#f3d75c', '#3a2c07', 'Half price today'], …] | What each face gives, from one to six: its name, the colour of its scoop, the colour of the name on the scoop and the line under it. |
GRAVITY | script.js | 2100 | How fast the die falls, in pixels a second squared. |
BOUNCE | script.js | [0.45, 0.45] | How much the die bounces off the felt and off the wooden sides. |
GRIP | script.js | 0.45 | How much the felt holds the die as it slides and turns. |
THROW | script.js | [520, 320] | How Roll throws: the speed forwards and upwards, in pixels a second (a little more or less each time). |
SPIN | script.js | 22 | How fast the die tumbles as it leaves Roll's hand, in radians a second. |
JACKPOT | script.js | 6 | The face that also rains sprinkles into the tray. |
SPRINKLES | script.js | 60 | How many sprinkles fall. |
FLICK | script.js | 320 | How fast a throw by hand must leave the hand to count, in pixels a second. |
For the site or app of a gelateria, a café or a shop that gives something away at random: the flavour of the day, the week's discount, a loyalty card's prize, a newsletter's game, anywhere a draw should feel like a real die thrown on the table.
Everything sits in a column at most 340 pixels wide, and the tray is a little narrower because in perspective its front edge widens. The finger picks the die up by pressing anywhere on the tray and throws it without scrolling the page (touch-action none on the tray only); a short tap throws like Roll.
No throws and no flights: Roll, or a tap on the tray, fades the die's faces out and back in with the die already lying on a random face, then the flavour lights up without a bounce. No drop on opening and no sprinkles.
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 21 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.