Skip to content
animorio
IT
All components

The gelato die

Games, Physics, 3DGSAP, CSS only, CanvasAdvancedCompressed weight: Demo 15 KBGSAP 28 KBFreeDownload the zip

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.

The code

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>

Playground

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

Or ask Claude Code for it

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 site

What it does

On a white background, at the top, a walnut tray seen a little from above, lined with pistachio green felt, and in it a casino die of clear red resin with white pips; below it Roll for your scoop, the rule (Every face is half price today. Six is on us.), the six flavours in two columns, each with the die's face drawn beside it (Lemon sorbet, Strawberry, Hazelnut, Pistachio, Dark chocolate, and Free scoop for the six), the Roll the die button and a line that explains. As the page opens the die drops into the tray turning and settles. Pressing on the tray, with a finger or the mouse, the die jumps into the hand and rises above the felt: it follows the hand turning a little between the fingers, and its shadow, far and blurred, tells how high it is. Let go with a flick, it leaves with the speed of the hand, a little upwards and tumbling the way it goes; Roll does the same with a pretend hand that carries it to the front of the tray and throws it at the back. In the air the die really tumbles: it hits the felt with a corner and bounces, knocks against the sides, rolls from face to face slower and slower, rocks and stops. Each face lights up as it turns to send the light our way, the faces seen edge-on are redder, and through the resin the pips of the far faces show, dimmed. On the felt the shadow changes shape with the die, sharper as it lands, and inside it there is a red patch, the light that went through the resin. Once the die has stopped, the flavour of the face up lights up in the list with a scoop of its colour that spreads behind the name with a bounce, the name and Half price today appear below, and the button becomes Roll again. On a six a handful of sprinkles in the colours of the flavours rains into the tray, tumbling in the air, and stays scattered on the felt until the die is picked up again. A die set down instead of thrown does not count: That was a drop.

How it works

The tray and the die in CSS 3D

The 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 rigid body

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.

Stopping and reading the face

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.

The hand and Roll

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 resin and the light

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.

The sprinkles and the preview

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.

Values to change

NameFileDefaultWhat it changes
FLAVOURSscript.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.
GRAVITYscript.js2100How fast the die falls, in pixels a second squared.
BOUNCEscript.js[0.45, 0.45]How much the die bounces off the felt and off the wooden sides.
GRIPscript.js0.45How much the felt holds the die as it slides and turns.
THROWscript.js[520, 320]How Roll throws: the speed forwards and upwards, in pixels a second (a little more or less each time).
SPINscript.js22How fast the die tumbles as it leaves Roll's hand, in radians a second.
JACKPOTscript.js6The face that also rains sprinkles into the tray.
SPRINKLESscript.js60How many sprinkles fall.
FLICKscript.js320How fast a throw by hand must leave the hand to count, in pixels a second.

Try them in the Playground

When to use it

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.

On phones

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.

With reduced motion

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.

Pitfalls

  • With contacts only for the corners already inside the felt, the die at rest sank by a hair at one step and was pushed back up at the next: its speed never went under the threshold and it never stopped. The contact now also takes the corners a hair above the felt and lets them come down by that hair only.
  • A die leaning against a side touches the felt with two corners: if only the corners on the felt are counted to say it has stopped, it never stops. Every contact counts, and a die that is not flat gets a nudge.
  • The die follows the hand on a spring, and the spring's speed at letting go is much lower than the hand's: the throw takes the speed of the hand over the last tenth of a second, and a hand that stopped before letting go does not throw.
  • Opacity on an element with transform-style preserve-3d flattens it: for the fade of reduced motion the faces fade one by one, never the die.
  • With clear resin the pips of the face up mixed with those of the face below, seen through it: the pips of the far faces are dimmed the more they look away, and the tray is tipped 46 degrees instead of 52, so the face up reads.
  • Thrown in an arc the die rises well above the tray: the tray sits at the top of the component with room above it, and the words sit below, where the die never reaches to cover them.

Libraries

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.

Testing

Tested on 21 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

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.

All components