
The layered button
Buttons, Hover, Physics
Free
The Tune speakers button of an app for a pair of speakers, made as the Kundt's tube of physics classes: a glass tube with brass caps and, along its bottom, a bed of very fine golden powder. At a click the frequency climbs and stops on one resonance after another: the air in the tube becomes a standing wave, the powder dances where the air moves most and flows to heap up where the air is still, with fine ridges between one heap and the next. The last resonance leaves nine heaps in a row: Tuned to room.
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 Kundt's tube button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/saira@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the Kundt's tube button: the Tune speakers button of a speaker app, made as a glass tube with brass caps and a
bed of fine golden powder along its bottom. The tuning sweeps the tube through its resonances: at each one the
air stands in a wave, the powder dances where the air moves most and gathers in heaps where it is still, with
fine ridges in between, until the last pattern stays: tuned -->
<div class="kundt" data-kundt>
<button class="kundt__btn" type="button" data-btn data-autoclic>
<span class="kundt__haze" aria-hidden="true"></span>
<canvas class="kundt__canvas" aria-hidden="true" data-canvas></canvas>
<span class="kundt__glass" aria-hidden="true">
<span class="kundt__glint" data-glint></span>
<span class="kundt__glint" data-sweep></span>
<span class="kundt__scale"></span>
</span>
<span class="kundt__cap kundt__cap--left" aria-hidden="true"></span>
<span class="kundt__cap kundt__cap--right" aria-hidden="true"></span>
<span class="kundt__print">
<span class="kundt__label" data-label>
<span class="kundt__icons" aria-hidden="true" data-icons>
<svg class="kundt__icon" viewBox="0 0 24 24" data-fork>
<path d="M8.6 2.6v7.6a3.4 3.4 0 0 0 6.8 0V2.6" data-prongs/>
<path d="M12 13.6v7.8"/>
</svg>
<svg class="kundt__icon kundt__icon--done" viewBox="0 0 24 24" data-check>
<path d="M5 12.6l4.4 4.3L19.2 7.2" data-tick/>
</svg>
</span>
<span class="kundt__words" data-words>Tune speakers</span>
<span class="kundt__hz" aria-hidden="true" data-hz></span>
</span>
</span>
</button>
<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.
28 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 Kundt's tube button” from Animorio and fit it into my siteA tube closed at both ends resonates at n times c / 2L, with c = 343 m/s and L the length of the tube (TUBE): with 50 centimetres, 343 Hz for every n. In resonance n the speed of the air along the tube goes as sin(nπx/L), still at both caps. Every resonance answers the frequency with its own bell, 1/√(1 + Q²(f/fn − fn/f)²) with Q = 13. What matters to the powder is the speed squared, on average: the resonances add up in it each on its own (their phases go their separate ways), so every pattern comes out symmetric, and when the frequency runs over a resonance it does not stop on, the powder fizzes for a moment.
The force of the standing wave pushes the powder down the slope of the speed squared: for a single resonance −sin(2kx), towards the nodes, where the air is still. The top layer, as deep as the shaking (MOBILE), flows with that push; the packed bottom stays where it is (PACK). It is an upwind flux between columns half a pixel wide, in steps short enough that no column gives more than half its moving layer, and nothing piles up past the tallest a heap may be, below the words.
After every step of the flow, a pass of the sandpile: a column steeper than the angle of repose (REPOSE) gives half its excess to its neighbour. Shaken, the powder holds much lower slopes, like a liquid: heaps left by an earlier resonance, where the air now moves, melt away; at the nodes, where the air is still, the powder keeps its angle and the heaps stand as cones. The tuning plays softly on the first resonances and loudly on the last: the flow goes as the fourth power of the loudness, so every stop shows its pattern but only the last gathers all the powder, in heaps that are nearly all the same.
Grains leave the columns shaken past a threshold, as many as the shaking says, with a push towards the node; one in eight jumps higher, and motes of fine dust rise into the light and float. When they land they bring their powder to the column they fall on: no powder is ever lost (tested over thirty seconds of clicks, one every 2.8 seconds). They are drawn as streaks as long as the air's back and forth at that spot. Kundt's ridges are a layer of their own, a ridge every STRIAE pixels with the same volume of powder taken from beside it: they grow where the air moves most, sink while the frequency runs on, and stay when the sound stops.
The bed is an ImageData at two pixels per CSS pixel, made again on every frame in which something moves. For every column the slope says how much light it takes from the lamp, above and a little to the left: the surface by its own slope, the face of a heap by the slope of the heap; under the surface the powder is darker the lower it lies, the valleys are darker, and a fixed grain, anchored to the bottom of the glass, runs through it all. The colours come from a table of 256 tints made from the three powder variables. Shaken, the surface glitters and swells a little.
A pointer drawn along the glass shakes it in a bell 15 pixels wide, the more the faster it goes: the powder fizzes, swells and raises ridges that sink again in half a second. A press is a little knock on the whole bed. To go back to rest the tube shivers (GSAP keyframes) and the bed turns fluid: a diffusion solved implicitly with the Thomas algorithm, which can be as fast as it likes without ever overshooting, plus a pull towards the shape of the bed at rest, scaled on every frame to the powder lying on the bottom, so nothing is lost. When nothing moves the canvas is not drawn again.
| Name | File | Default | What it changes |
|---|---|---|---|
BUSY | script.js | Tuning | The words while the frequency climbs, before the frequency. |
DONE | script.js | Tuned to room | The words once the powder has settled. |
MODES | script.js | [2, 3, 5, 8] | The resonances the tuning stops on, in order: the last one is the pattern that stays (with 8, nine heaps). With high resonances the heaps are many and small. |
SWEEP | script.js | 3.6 | The seconds of the tuning, from the first note to the last. |
TUBE | script.js | 50 | The length of the tube, in centimetres: it sets the frequencies on the label (with 50 centimetres, 343 Hz for every resonance). |
POWDER | script.js | 7 | How deep the powder lies at rest, in pixels. |
REPOSE | script.js | 46 | The steepest slope the powder holds, in degrees: higher, taller and narrower heaps. |
DRIFT | script.js | 1 | How hard the standing wave pushes the powder to its nodes: 0, the powder dances but stays where it is. |
STRIAE | script.js | 3.2 | The spacing of the fine ridges where the air moves most, in pixels: 0, no ridges. |
DUST | script.js | 1 | How much fine dust rises and floats in the light: 0, none. |
HUM | script.js | 1 | How much a pointer drawn along the glass makes the powder tremble: 0, not at all. |
HOLD | script.js | 2.4 | The seconds the button says Tuned to room before it goes back to rest. |
For the action that measures or adjusts something with sound and takes a few seconds: tuning speakers to the room, the sound check before a live stream, calibrating a microphone or a pair of headphones, analysing a track. It belongs in music, audio and smart home apps and on science and instrument sites; the black background makes the powder glow. It is a button to show off, one to a page, not one for a toolbar.
The pill is 60 pixels tall and 340 wide, or the screen less 32; the caps stay 30 pixels long (26 below 360) and the powder fills the glass that is left, at the same scale, so with the same patterns. A finger drawn along the glass makes it sing like the mouse (touch-action: pan-y, so up and down the page still scrolls); a tap gives the knock and starts the tuning. The reflection that follows the mouse is not there on a phone.
No hopping, no flow, no dust in the air, no moving reflections and no shiver. At a click the frequency goes through the resonances as before, and on each one the bed fades into that resonance's pattern, worked out at once: heaps on the nodes with rounded tops and the ridges between them, the first patterns only in part and the last one in full. The words change in a fade, the tick is already drawn, and the way back to rest is a fade to the level bed. The pill pressed darkens a little instead of shrinking.
GSAP 3.15.0, Saira (font variabile, con l'asse della larghezza) 5.3.0.
Compressed with gzip, the three demo files weigh 18 KB; the libraries: GSAP 28 KB.
Tested on 6 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.