Skip to content
animorio
IT
All components

The coin stack transfer

Loading, Physics, TransitionsCanvas, GSAPAdvancedCompressed weight: Demo 17 KBGSAP 28 KBFreeDownload the zip

A freelancing app’s payout going to the bank as gold coins: each one comes in from above turning over, lands on the stack with a clink of light, the stack rocks on its spring and the amount beside it climbs by its share. The height of the stack is the progress; at the end a glint runs up it. If the bank does not answer, the coin hits the rim, lands on its edge and rolls away.

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, viewport-fit=cover">
  <title>The coin stack transfer</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the payout card of a freelancing app: the money goes to the bank as coins that fall turning over and stack up
         in a column on the right. The height of the stack is the progress, the amount beside it climbs as every coin
         lands. The coins are drawn in pseudo 3D on the canvas by script.js; the words and the button are HTML -->
    <section class="pay" aria-labelledby="pay-title" data-pay>
      <div class="pay__words">
        <h2 class="pay__title" id="pay-title">Payout · Fernhill Studio</h2>
        <p class="pay__amount" aria-hidden="true"><span class="pay__figure" data-amount>€2,480.00</span></p>
        <p class="pay__status">
          <span class="pay__swap" data-status>Ready to transfer</span>
          <svg class="pay__tick" viewBox="0 0 20 20" aria-hidden="true" data-tick><path d="M4.6 10.4l3.5 3.5 7.4-8" pathLength="1"/></svg>
        </p>
        <p class="pay__sub"><span class="pay__swap" data-sub>To account ending 4821 · instant</span></p>
        <button class="pay__btn" type="button" data-action data-autoclic><span class="pay__label" data-label>Transfer to bank</span></button>
      </div>
      <div class="pay__stack" role="progressbar" aria-labelledby="pay-title" aria-valuemin="0" aria-valuemax="100"
           aria-valuenow="0" aria-valuetext="Not transferred yet" data-stage>
        <canvas class="pay__coins" aria-hidden="true" data-coins></canvas>
      </div>
      <p class="sr" role="status" 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="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.

32 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 coin stack transfer” from Animorio and fit it into my site

What it does

On a black background, alone in the middle, a graphite card with round corners: on the left Payout · Fernhill Studio, the big amount €2,480.00, Ready to transfer, To account ending 4821 · instant and the pale Transfer to bank button; on the right, dashed and barely visible, the outline of the full stack. Pressed, the amount goes back to €0.00, the button becomes an outlined Sending… and the coins start to fall: each one appears from the top of the card, comes down turning about a level axis (the face with the euro sign and the back with a rosette flash as they catch the light), arrives flat on the stack, gives a little hop and wobbles out like a coin spun on a table, while a ring of light runs over its face and a spark flicks on its rim. The stack leans a little and rights itself on a spring, and the amount on the left climbs by 155 euros with a small nudge upwards; the line below says how long is left. When the bank confirms a big chunk the coins pour one after another. With the last coin the dashed outline goes, a glint runs up the stack, the amount swells for a moment, a green tick is drawn beside Transfer complete and the button becomes Done, which picks the coins up from the top and puts the card back at rest. One time in two, in the demo, the bank stops answering halfway: the coin on its way hits the right rim of the stack, bounces, the stack shudders, the coin comes down on its edge in front and rolls out of the card; Transfer interrupted in red says how much has already arrived and Retry goes on from there.

How it works

A coin is a cylinder with its own frame

Every coin has three vectors: its normal and two axes in its face. The camera is orthographic and looks down at VIEW degrees, so a vector of the world lands on the screen as (x, −(y cos φ − z sin φ)). The face towards the camera is a circle carried onto its ellipse by a single affine transform (ctx.transform with the projected axes), so the relief is drawn on it with drawImage; the edge is a ring of 72 quads (128 for the stacked coins), split into three bands, a bevel at each side and the reeding in the middle, each coloured by its own normal. Only the surfaces facing the camera are drawn: on a convex solid they never overlap, and no sorting is needed.

A metal’s light

The colour of every surface depends less on where the light comes from than on where its reflection points, r = 2(n·v)n − v: a soft box above and to the left, a bright horizon and a dark floor. The number that comes out picks the colour on a ramp of three golds (deep, plain, pale) read from the CSS. So a turning face flashes when its reflection crosses the box, and the upper bevel of every stacked coin catches a line of light that sets the coins apart.

The relief, lit from eight sides

The euro sign, the rim, the ring of beads and the rosette on the back are drawn in white on black as a height map, blurred a little and differentiated in x and y. From it come eight images, one for every direction of the light: the lit slopes in light paint, the ones turned away in dark, and the raised parts veiled like satin metal. Every frame the face works out where the light falls in its own axes and blends the two nearest images: the relief stays right while the coin turns.

Falling flat, wobbling, rocking

A coin starts above the card with a small speed and falls with GRAVITY; its time to reach its place is solved in advance, and its turn about the level axis has the speed that brings it down flat (a whole number of turns). Landed, its tilt dies away while the axis turns faster and faster, like a coin on a table, and it gives a one pixel hop. The stack is a rigid column on an angular spring, integrated in steps of 1/240 of a second, softer the taller it is: it turns about the edge of its bottom coin on the side it leans to, and every coin pushes it from the side it came from. The coins below the top are a picture painted once; the top one is drawn for real.

The coin that gets away

When the bank stops answering, the coins already in the air land and then one is sent at the right rim. Striking the rim it bounces to the right and towards us, the stack takes a knock, and its turn changes speed so that it meets the floor on its edge, face towards us; there it bounces a little more, rights itself with a dying wobble and rolls: the distance over the radius turns the design on its face. Retry goes on from the share already confirmed and the lost coin is sent again.

For those who cannot see it and those who want no motion

The stack is a role=progressbar with aria-valuenow and an aria-valuetext that says how many euros have arrived, the card has aria-busy while it works and an aria-live line says when the transfer starts, is interrupted and ends. The button changes its word (Transfer to bank, Sending…, Retry, Done) and takes the new width with a little bounce; while the coins fall it is aria-disabled. The dashed outline of the full stack shows from the start how much is left.

Values to change

NameFileDefaultWhat it changes
AMOUNTscript.js2480The payout, in euros.
ACCOUNTscript.js'4821'The last digits of the account it goes to.
SECONDSscript.js4.8How long a transfer takes in the demo, in seconds (a real one follows the bank).
COINSscript.js16How many coins make the full stack: each one is an equal share of the amount.
FAIL_EVERYscript.js2In the demo, one transfer in how many is interrupted (0 never).
FAIL_ATscript.js0.56How far the interrupted transfer gets, from 0 to 1.
FLIPSscript.js2.4How many times a coin turns over in a second of falling.
GRAVITYscript.js2600How hard the coins fall, in pixels a second squared.
SWAYscript.js1How much the stack rocks when a coin lands: 0 not at all, 2 a lot.
SPRINGscript.js120How stiff the stack’s spring is: higher rights it faster.
VIEWscript.js24How far down we look on the coins, in degrees.
SHEENscript.js0.9The glint that runs up the finished stack: 0 none, 1.5 bright.

Try them in the Playground

When to use it

For a wait of a few seconds that is about money: an outgoing payment, a withdrawal from a balance, an instant transfer, a refund. The coins say at once what is going on and the amount climbing beside them reassures; for any loading that is not about money the metaphor is out of place.

On phones

The card takes the screen’s width less 32 pixels (358 on a 390 screen) and stays 238 tall; the coin column narrows to 132 pixels and the amount drops to 31. The canvas paints at the screen’s real density, up to twice. The button is a pill 36 tall: under a finger it squashes and springs back, and its words widen on the width axis only under the mouse.

With reduced motion

No falls, turns, hops or rocking: every coin fades in at its place in the stack, without rings or sparks, and there is no stray coin (the red words stay). No glint at the end and no coins picked up: they fade out. The stack still grows, because it is what says how far along things are.

Pitfalls

  • The raised euro sign could not be read: the light drew only its edges, and the raised plateau had the same colour as the field. The raised parts now have a pale veil, like satin metal beside the polished field, and the sign is bigger and outlined.
  • With 64 quads round the edge the reeding looked like a chessboard: the stack uses 128, painted once into a picture, and the alternate lines are softer.
  • A coin coming in from above covered the thin line of the card’s edge: the canvas has a mask that brings it out of the dark over the first 30 pixels.
  • The stray coin left the card in a quarter of a second, still in the air, without it being clear that it rolled: it now bounces gently, comes forward and lands on its edge in front of the stack, and rolls out at 125 pixels a second.
  • The sign on the top face jumped when the coin landed (the stack’s picture has its own fixed turn): the top is always drawn for real, with its own turn, and the faces of the coins below are hidden.
  • The amount did not go back to €0.00 from the second transfer on: the function that changes the words thought it had already written it. Every write of the amount now also updates what that function remembers.
  • After Done the dashed outline of the full stack did not come back: it lights up again while the coins are picked up.

Libraries

GSAP 3.15.0, Instrument Sans (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 17 KB; the libraries: GSAP 28 KB.

Testing

Tested on 8 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