
The needle gauge
Counters, Physics, SVG
NewFree
The loyalty card of a small coffee roaster, cream card stock with ten printed coffee beans: with every coffee the punch takes the next bean away. The card dips under the press towards that bean and springs back a pixel past rest; the little piece of paper comes out of the back, you see it fall through its own hole, in the card’s shadow, and tumble out from under the edge, turning over. Every hole shows the page with the card’s real shadow in it. Undo brings it back up into its hole; full, the card gets the Redeemed stamp and the next tap deals a new one.
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 punch card</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 loyalty card of a small coffee roaster, on paper. Every cup bought the card is punched: the punch
bites a coffee bean out of it, the card dips under the press and springs back, and the little paper bean
falls out of the back of the card, seen through its hole, and tumbles out from under it. Through every hole
you see the page, with the card's own shadow in it. Full, the card is redeemed with a stamp, and a fresh one
takes its place. The paper, the beans and the stamp are drawn by the script and the CSS -->
<div class="loyalty" data-loyalty>
<div class="chads" aria-hidden="true" data-chads></div>
<div class="deal" data-deal>
<div class="tilt" data-tilt>
<button class="card" type="button" data-card data-autoclic aria-describedby="loyalty-status">
<svg class="paper" viewBox="0 0 320 420" aria-hidden="true" data-paper>
<defs>
<mask id="loyalty-holes" maskUnits="userSpaceOnUse" x="-10" y="-10" width="340" height="440">
<rect x="-10" y="-10" width="340" height="440" fill="#fff" />
<g data-holes></g>
</mask>
</defs>
<g mask="url(#loyalty-holes)">
<rect class="paper__sheet" width="320" height="420" rx="16" />
<rect class="paper__frame" x="10.5" y="10.5" width="299" height="399" rx="9" />
<path class="paper__rule" d="M24 238.5H296" />
<g data-beans></g>
<g class="stamp" data-stamp>
<rect class="stamp__box" x="-88" y="-27" width="176" height="54" rx="6" />
<rect class="stamp__box stamp__box--in" x="-82" y="-21" width="164" height="42" rx="3" />
<text class="stamp__word" y="-1">Redeemed</text>
<text class="stamp__date" y="13" data-stamp-date>19 Oct 2026</text>
</g>
</g>
</svg>
<span class="card__words">
<span class="card__brand">Halde</span>
<span class="card__kind">Coffee roasters · Loyalty card</span>
<span class="card__headline" data-headline>Ten cups, the eleventh is on us.</span>
<span class="card__status" id="loyalty-status"><span class="card__num" data-num>Four</span><span data-rest> cups to go</span></span>
<span class="card__small">Valid at both Halde counters</span>
</span>
<span class="sr" data-label>Punch a cup</span>
</button>
<button class="undo" type="button" data-undo hidden>
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M5.5 4.5 2.5 7.5l3 3M3 7.5h6.5a3.5 3.5 0 0 1 0 7H8" /></svg>
<span>Undo</span>
</button>
</div>
</div>
<p class="hint" aria-hidden="true" data-hint>Press the card to punch a cup</p>
<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.
26 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 punch card” from Animorio and fit it into my siteThe card is a 320 × 420 SVG: the stock, the hairline, the printed beans and the stamp sit in a group with a mask, and every hole is a black oval in the mask. Punching means switching that oval on: the cut is sharp at any size, and the printed bean under the cut is hidden, because the piece takes it away.
The shadow is not a box-shadow but two drop-shadows on the already cut stock: a tight one that draws the edge of every hole and a soft one that tells how high the card is. As they are worked out on the cut shape, the card’s crescent of shadow falls into every hole by itself, and when the card lifts the crescent grows.
The press and the lift are two springs integrated on the GSAP ticker in four sub-steps with the step capped at 1/30 of a second (SPRING). The press turns the card in 3D towards the bean that is next (rotateX and rotateY in proportion to where it is, PRESS degrees) and pushes it back; released it comes up past rest and wobbles. A key or a script click make the press and the release in one go, with a kick to the spring.
Each cut piece is a little SVG in a layer under the card: it starts exactly behind its hole, so it is seen only through the hole, in shadow, as it shrinks a little moving away. It has gravity (GRAVITY), a sideways push, a spin and a flip made with scaleY = cosine of the angle: when the cosine is negative it shows the back of the stock. Below the edge it leaves the shadow and fades in thirty pixels.
Undo takes the piece from where it is (or from under the edge, if it has already fallen) and a timeline brings it back to the hole, turning it face up; when it arrives the hole closes and the card gets a little knock. The stamp comes down with power4.in, the card jolts at the impact and the stamp settles with an elastic; the new card comes in with back.out.
The card is a button: Enter and Space punch, redeem or ask for a new card, Undo is a button of its own and Ctrl or Cmd+Z do the same. Keyboard focus lifts the card and shows the next bean like the mouse passing over; on a phone the finger dips it where it will punch. An aria-live says how many coffees are left.
| Name | File | Default | What it changes |
|---|---|---|---|
CUPS | script.js | 10 | How many coffees fill the card, in two rows (from 6 to 12). The line at the top writes itself. |
PUNCHED | script.js | 6 | How many coffees are already punched when the page opens. |
PRESS | script.js | 5 | How far the card dips under the punch, in degrees. |
SPRING | script.js | [260, 0.36] | The card’s spring: how stiff it is and how much it is held back (1 = no bounce). |
GRAVITY | script.js | 1600 | How fast the punched piece falls, in pixels per second squared. |
LIFT | script.js | 5 | How far the card lifts under the pointer, in pixels. |
SLANT | script.js | -14 | The slant of the printed beans, in degrees. |
For the loyalty card of a cafe, a coffee roaster, an ice-cream shop, a bakery or a hairdresser, in the app or on the site where customers see their points; also for a stamp collection, a pass of entries or a ten-class card.
The card is 320 pixels wide, or the screen minus 32, and all the words are in container units, so they keep their place at every size. Under a finger the card dips where it will punch and punches on release; Undo has a 36 pixel touch area. Springs, fall and timings are the same as on a computer.
No dip, no lift and no falling pieces: the hole fades in, Undo closes it the same way, the number changes with a fade, the stamp fades in and the new card arrives with a fade.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 9.5 KB; the libraries: GSAP 28 KB.
Tested on 19 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.