
The layered button
Buttons, Hover, Physics
Free
The Run stress test button of a load testing tool, made of a slab of clear plastic seen between two crossed polarising filters, the way engineers look at the stress inside a model: wherever the plastic is under stress, fringes of colour appear. At rest only the rim glows, with the stress left in it by the mould. The pointer is a fingertip pressing on the top edge: a drop of coloured rings opens under it and follows it, and pressed, the rings multiply. Clicked, the test becomes a four point bending test whose load climbs in stages with the virtual users: the fringes pour in from the faces and curl round the pins, until the words say Passed at 5,000 users. The colours are not picked: they are worked out pixel by pixel from the stress and the light.
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 button under stress</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/ibm-plex-sans@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the button under stress: the Run stress test button of a load testing tool, a slab of clear plastic seen
between two crossed polarising filters, so the stress inside it shows as bands of colour. The fringes are
worked out on the canvas; the gloss, the edge and the words stay on top -->
<div class="stress" data-stress>
<button class="stress__btn" type="button" data-btn data-autoclic>
<canvas class="stress__fringes" aria-hidden="true" data-fringes></canvas>
<canvas class="stress__fringes stress__ghost" aria-hidden="true" data-ghost></canvas>
<span class="stress__label" data-label>
<span class="stress__icons" aria-hidden="true" data-icons>
<svg class="stress__icon" viewBox="0 0 24 24" data-load>
<path d="M3 13.5Q12 13.5 21 13.5" data-beam/>
<path d="M6 14.6 4.1 18h3.8z M18 14.6 16.1 18h3.8z" data-supports/>
<g data-arrow>
<path d="M12 3.6v6.6"/>
<path d="M9.5 7.9 12 10.4l2.5-2.5"/>
</g>
</svg>
<svg class="stress__icon stress__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="stress__words" data-words>Run stress test</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.
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 button under stress” from Animorio and fit it into my siteBetween two crossed polarising filters, a stressed plastic lets the light through by sin²(π δ / λ), where δ is the delay between the two polarisations and λ the wavelength: in white light every delay has its own colour, the Michel-Lévy scale (black, grey, white, yellow, red, blue, green and round again, paler every time). At the start the script works it out once, in 2048 steps up to 6000 nanometres: it adds the formula up over a hundred wavelengths with the light of a black body at 6500 kelvin and the CIE 1931 colour matching functions (in Wyman, Sloan and Shirley's gaussians), goes from XYZ to sRGB and pulls the colours the screen cannot show towards the grey of the same brightness. A photographer's curve (power 1.5) sinks the faint light into the black, and a breath of blue recalls that real filters never close completely.
A moulded slab cools from the outside first: the skin stays squeezed along the edge and the core pulls a little. For every pixel the script finds the nearest point of the pill's edge, the tangent there and the distance, and writes a stress tensor along the tangent: RIM nanometres of delay at the edge, falling off like a Gauss bell, with a small change along the way round. It is worked out once for every size, in three Float32Arrays, and at rest it is the only thing that shows.
A load P on an edge pushes the plastic straight out from itself with a stress 2P cos θ / π r (Flamant's solution for the half plane): the rings of colour are circles touching the edge at the load. The finger is such a load on the top edge, CONTACT pixels wide so the stress does not go to infinity at the point; below, the table pushes back over a wide patch. TOUCH and PRESS say how many fringes lie under the finger: under a contact c wide the stress peaks at 0.2067 P / c, and from that the script finds P.
During the test two pins press from above and two supports hold from below. Besides Flamant under each of them, the slab bends like a beam: σ = M y / I, growing towards the faces and nothing on the neutral axis, where the words are, plus the shear τ = V (h²/4 − y²) / 2I. In beam theory the moment changes all at once at the pins and the supports; in a real slab the change spreads over about its height (Saint-Venant), so the moment is smoothed with a bell and the shear is its slope. The load is scaled so that PEAK fringes lie on the faces between the pins.
The finger's load, its position and the test's load are three springs (STIFF and DAMP), moved on in two half steps a frame with the step held under 1/30 of a second: a load that changes rings for a moment before it settles, and the fringes come and go. The stages of the test are the USERS, one every STAGE seconds, and at the top the load shivers with the traffic. For every pixel the tensors are added up, the difference of the principal stresses √((σx − σy)² + 4τ²) gives the delay and the table the colour, written into a Uint32Array and laid down with putImageData. The canvas works at SHARP times the screen's density (about a millisecond and a half a frame on a computer) and draws again only when a load changes.
| Name | File | Default | What it changes |
|---|---|---|---|
TOUCH | script.js | 1.3 | The fringes the pointer's touch opens under it, in orders (one order is one round of colours). |
PRESS | script.js | 3 | The fringes under the finger while the button is held down, in orders. |
PEAK | script.js | 3.6 | The fringes on the slab's faces when the test is at its peak, in orders: more, closer and paler bands. |
USERS | script.js | [1000, 2500, 4000, 5000] | The virtual users of every stage of the test: the load follows them, and the highest is the one the words give at the end. |
STAGE | script.js | 0.5 | The seconds of every stage. |
HOLD | script.js | 0.85 | The seconds the test holds the highest load. |
RIM | script.js | 950 | The stress left in the rim, in nanometres of delay: 280 is white, 450 orange, 550 purple, 700 blue. |
CONTACT | script.js | 3 | How wide the fingertip and the test's pins are, in pixels: less, tighter rings. |
STIFF | script.js | 160 | How stiff the slab is: more, it takes a new load sooner. |
DAMP | script.js | 10 | How quickly the slab stops ringing after a change of load. |
LIGHT | script.js | 1 | How bright the light behind the slab is. |
SHARP | script.js | 0.7 | How fine the fringes are drawn against the screen: more, sharper and costlier. |
RUN | script.js | users | The word after the number while the test runs. |
DONE | script.js | Passed at {users} users | The words once the test has passed: {users} becomes the highest number. |
BACK | script.js | 2.4 | The seconds before the button says Run stress test again. |
For the action that matters in a tool for developers or engineers on a dark background: running a load test, a benchmark, a simulation, a check that puts the system to the test. It belongs on sites about materials, engineering, physics and optics, where polarised light says something by itself. It is a button to show off, one to a page, not one for a toolbar.
The slab is 60 pixels tall like the other buttons and 300 wide, or the screen minus 48; the stresses are worked out again for the real size. With no mouse a finger resting on it opens the rings under itself and, dragged across, takes them along (touch-action: pan-y, so up and down the page still scrolls). A tap starts the test; a finger that has been dragged lifts without starting it, as on any button. The canvas works at 0.7 times the screen's density, at most 1.4 pixels per pixel.
No rings following the pointer and no springs. At the click the image of the test at its highest load, worked out once at the start, fades in on the slab and fades away at the end; the words change in a fade, with no slide and no blur, and pressed the slab darkens a little instead of shrinking.
GSAP 3.15.0, IBM Plex Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 10 KB; the libraries: GSAP 28 KB.
Tested on 12 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.