
The time wheel
Pickers, 3D, Physics
NewFree
Pressing Claim reward, the gift in the button rattles, opens and throws out a cloud of 3D paper confetti. The strips spin round their long axis and veer sideways, the squares rock and glide like leaves, the gold foil flashes when it catches the light and the curled streamers uncoil in the air; it all falls back in front of and behind the button and settles at the bottom of the window with its shadow.
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 confetti button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- the paper is drawn on two canvases: the one behind the button holds what flies or lies behind it, the one in
front holds the rest -->
<canvas class="paper paper--back" data-back aria-hidden="true"></canvas>
<main class="stage">
<!-- the confetti button: pressed, the gift in it opens. The paper bursts out of the gift, turns and glides in the
air and settles at the bottom of the window -->
<div class="confetti" data-confetti>
<button class="claim" type="button" aria-label="Claim reward, 500 points" data-claim data-autoclic>
<span class="claim__shine" aria-hidden="true"></span>
<span class="claim__face">
<svg class="claim__gift" viewBox="0 0 24 24" aria-hidden="true">
<g data-box>
<rect class="gift__box" x="4.6" y="11.2" width="14.8" height="9.8" rx="1.5"/>
<rect class="gift__band" x="10.7" y="11.2" width="2.6" height="9.8"/>
</g>
<g data-lid>
<path class="gift__bow" d="M12 7.6C10.5 4.7 7.1 3.7 6.5 5.7 6 7.4 9 7.6 12 7.6zM12 7.6c1.5-2.9 4.9-3.9 5.5-1.9.5 1.7-2.5 1.9-5.5 1.9z"/>
<rect class="gift__lid" x="3.4" y="7.4" width="17.2" height="4.2" rx="1.3"/>
<rect class="gift__band" x="10.7" y="7.4" width="2.6" height="4.2"/>
</g>
<path class="claim__check" d="M5.2 12.4l4.4 4.4 9.2-9.6" pathLength="1" data-check/>
</svg>
<span class="claim__labels" aria-hidden="true">
<span class="claim__label" data-label>Claim reward</span>
<span class="claim__label" data-label>Opening your gift…</span>
<span class="claim__label" data-label>+<span data-points>500</span> points</span>
</span>
</span>
</button>
<p class="sr" aria-live="polite" data-live></p>
</div>
</main>
<canvas class="paper paper--front" data-front aria-hidden="true"></canvas>
<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.
24 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 confetti button” from Animorio and fit it into my siteEach piece has a position, a speed, an orientation (a quaternion) and a spin. The air holds it back in two different ways: a lot against its broad face (0.05 times the speed squared) and little along its plane (0.003): that is why a tilted piece does not fall straight but slides sideways and glides. The burst is a blast of air out of the gift that dies down in 0.15 seconds: the pieces follow it and then leave it, so they fan out instead of shooting off like bullets.
The strips spin round their long axis, faster the faster the air goes past; the spin gives a push across it (the Magnus effect, the cross product of the spin and the wind) and makes them veer. The squares and dots instead are turned back towards lying flat by a restoring torque: they overshoot, come back and rock like a leaf, and every swing makes them glide sideways. The physics runs in fixed steps of 1/120 of a second, and the frame's step is capped at 1/30.
The two faces of a piece are projected on the screen with their perspective (the scale DEPTH / (DEPTH − z) and its derivative give the two axes), and become the canvas transform: a rectangle or a circle drawn flat comes out skewed just right. The colour comes from the angle between the face seen and a lamp up on the left, plus a glint aimed at the eye: soft for the paper, sharp and strong for the gold foil, which so flashes.
Each streamer is a chain of 30 points 6.5 pixels apart (verlet: the speed is the difference between two positions, then four rounds that put the distances back). A twist turns each link against the one before round an axis of its own: that is what curls it into a spiral. The air holds it back little along the chain and a lot across it, so it uncoils as it flies. The broad side of the ribbon follows the chain's curve, and each stretch takes its own light.
There are two canvases, one under the button and one over it: a piece with a negative z is drawn on the one under it and the button covers it. The button is solid: paper that goes through it from front to back bounces off. The floor is the bottom of the window: touching it, a piece lies down along the shortest turn that brings its face level, and its shadow, an ellipse squashed by the perspective, darkens as it comes down. Once everything is still, the loop draws nothing more.
Rest, over (a passing light and the lid peeking, also at a touch and with the Tab key), opening (the gift rattles for OPEN seconds, aria-disabled), done (yellow, the tick and the points counting up) and ready again after DONE seconds. The colours of the paper are CSS variables read at every burst, so they can be changed live.
| Name | File | Default | What it changes |
|---|---|---|---|
PIECES | script.js | 150 | How many pieces of paper fly out of the gift. |
STREAMERS | script.js | 7 | How many curled streamers fly out with them. |
POWER | script.js | 1 | How hard the gift throws the paper (1 = up to near the top of the window). |
AIR | script.js | 1 | How thick the air is: more, and the paper glides and rocks for longer. |
GRAVITY | script.js | 1500 | Gravity, in pixels per second squared. |
OPEN | script.js | 0.8 | The seconds the gift rattles before it opens. |
DONE | script.js | 4 | The seconds the button shows the points before it is ready again. |
POINTS | script.js | 500 | The points the reward is worth. |
For the moment when someone earns something: a reward, a milestone reached, a first purchase, a plan activated, the end of a course. It is a party: right once, in the right place, not on every button.
The button is 328 pixels wide, or the screen minus 32; the paper spreads less to the sides on a narrow screen and flies less high on a short one, and the floor always stays inside the window. At a touch the button gives and the light runs across it as with the mouse. The canvases use at most twice the screen's density.
No flight: pressing, the paper appears already lying at the bottom of the window with a fade, the gift turns into the tick at once and the points are already 500. The button does not move and does not come up on arrival.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 13 KB; the libraries: GSAP 28 KB.
Tested on 4 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.