
The layered button
Buttons, Hover, Physics
Free
The card of a bookshelf speaker, with a dark metal plate on top dusted with pale sand: when the speaker plays the plate vibrates, the sand jumps where the plate moves and stops where it stays still, and draws the figure of that sound, as on Ernst Chladni’s plates. Each sound profile, Warm, Balanced and Bright, is a different tone and a different figure: choosing one the sand runs across the plate from the old figure to the new one while the hertz count. With the pointer over the plate, or a click, the tone plays as long as you like.
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 Chladni sand card</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist-mono@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the card of a bookshelf speaker. On top of it a square metal plate with fine pale sand: when the speaker plays
its tone the plate vibrates, the sand jumps where the plate moves and gathers where it stays still, and draws
the shape of the sound (a Chladni figure). Each sound profile is a different tone and a different figure:
choosing one, the sand runs across the plate into the new figure. With the pointer over the plate (or a press,
or the keyboard) the tone plays and the sand dances. Thousands of grains, moved by the script on a canvas -->
<article class="speaker" aria-labelledby="speaker-name" data-speaker>
<button class="plate" type="button" aria-pressed="false" data-plate data-autoclic>
<canvas class="plate__sand" aria-hidden="true" data-sand></canvas>
<span class="plate__bolt" aria-hidden="true"></span>
<span class="sr" data-plate-label>Play the tone</span>
</button>
<p class="meter" aria-hidden="true"><span class="meter__hz"><span data-hz>440</span> Hz</span><span class="meter__state" data-state>Hover to play</span></p>
<div class="info">
<div class="info__head">
<div>
<h2 class="info__name" id="speaker-name">Mora Two</h2>
<p class="info__kind">Bookshelf speaker · Walnut</p>
</div>
<p class="info__price">€490</p>
</div>
<div class="profiles" role="radiogroup" aria-label="Sound profile" data-profiles></div>
</div>
<p class="sr" aria-live="polite" data-live></p>
</article>
</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.
33 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 Chladni sand card” from Animorio and fit it into my siteA square plate vibrating in one of its modes (n, m) has the shape cos(nπx)·cos(mπy) − cos(mπx)·cos(nπy), with x and y from 0 to 1. Where this function is zero the plate stands still: those are the nodal lines, the ones the sand draws. Each profile in PROFILES has its pair (n, m) and its hertz.
At every frame each grain hops in a random direction, as far as the plate under it vibrates (JUMP times the absolute value of the function): where the plate moves a lot it hops far, on the nodal lines it hardly moves. So, by themselves, the grains come to rest where the plate is still.
With the hops alone the figure would take half a minute: each grain also gets a small push down the slope of the vibration’s square, f·∇f (PULL), which takes it to the nearest still place. The gradient is worked out exactly from the same functions; at the edges the grains bounce back in.
The grains are two Float32Arrays; at every frame a canvas at the screen’s density (2 at most) is cleared and each grain is a fillRect of GRAIN pixels. The strength of the vibration rises and falls softly (in a fifth and a third of a second) and when the plate is still the GSAP ticker lets go and nothing more is drawn.
The plate is a button with aria-pressed: Enter, Space, a click or a tap keep the tone playing or stop it, and the mouse passing over plays it as long as it stays. The profiles are a radiogroup with the arrows, Home and End, and an aria-live says the profile and its hertz. The library preview’s click goes from one profile to the next.
| Name | File | Default | What it changes |
|---|---|---|---|
PROFILES | script.js | [{ name: 'Warm', hz: 180, n: 1, m: 4 }, …] | The sound profiles: the name, the hertz written under the plate and the plate’s mode (n and m, two different numbers from 1 to 12: the higher they are, the finer the figure). Four at most. |
START | script.js | 'Balanced' | The profile chosen at the start (its name, as in PROFILES). |
GRAINS | script.js | 7000 | How many grains of sand are on the plate. |
JUMP | script.js | 0.011 | How far a grain hops where the plate vibrates most, as a fraction of the plate. |
PULL | script.js | 1.4 | How strongly the grains drift towards the still lines (0 = only the hops, the figure comes much more slowly). |
BURST | script.js | 3 | How long the speaker plays by itself when a profile is chosen, in seconds. |
GRAIN | script.js | 1.25 | How big a grain is, in pixels. |
For the card of a speaker, headphones, a turntable or an instrument in an electronics or music shop; also for an equaliser or the presets of an audio app, an electronic music festival or a science museum selling an object.
The card is 340 pixels wide, or the screen minus 32, and the canvas is redrawn at its real size and the screen’s density. There is no hover: a tap on the plate starts the tone and another stops it, and the label says TAP TO PLAY. The same grains and the same physics as on a computer.
The sand never moves: when the page opens it is already in its figure (worked out before showing it) and choosing another profile the plate fades out and comes back with the new figure already drawn. The hertz change without counting.
GSAP 3.15.0, Geist (font variabile) 5.3.0, Geist Mono (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 6.6 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.