
The layered button
Buttons, Hover, Physics
Free
The Shuffle palette button of a colour palette tool, covered in marbled paper like the endpapers of an old book, with a paper label pasted on it. The pointer goes through the bath like a stylus and the ink follows it in swirls. At a click the new palette falls in a shower of drops from the point clicked: each drop pushes the old colours aside into thin rims, then a comb waves them.
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 marbled button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/eb-garamond@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the marbled button: the Shuffle palette button of a colour palette tool, a board covered in marbled paper with
a paper label pasted on it. The pointer drags through the inks like a stylus; a click lets the new palette fall
one drop per colour, each drop pushing the old pattern aside, and a comb draws them into feathers -->
<div class="marble" data-marble>
<button class="marble__btn" type="button" data-btn data-autoclic>
<canvas class="marble__sheet" aria-hidden="true" data-sheet></canvas>
<canvas class="marble__sheet marble__ghost" aria-hidden="true" data-ghost></canvas>
<span class="marble__sheen" aria-hidden="true" data-sheen></span>
<span class="marble__label" data-label>
<span class="marble__icons" aria-hidden="true" data-icons>
<svg class="marble__icon" viewBox="0 0 24 24" data-shuffle>
<path d="M3 7h2.6c2.4 0 3.9 1.1 5.1 3.1l1.6 2.8c1.2 2 2.7 3.1 5.1 3.1H21"/>
<path d="M18.4 13.4 21 16l-2.6 2.6"/>
<path d="M3 16h2.6c1.5 0 2.6-.4 3.5-1.2"/>
<path d="M13.9 8.2c1-.8 2.1-1.2 3.5-1.2H21"/>
<path d="M18.4 4.4 21 7l-2.6 2.6"/>
</svg>
<svg class="marble__icon marble__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="marble__words" data-words>Shuffle palette</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.
50 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 marbled button” from Animorio and fit it into my siteEvery drop is a polygon of points floating on the bath, drawn on the canvas from the oldest to the newest. Every gesture (a drop, the comb, the stylus) moves all the points of all the inks the way the liquid would move them. The maths is Aubrey Jaffer's maths of marbling: the moves are continuous and never fold over themselves, so the polygons never cross and the shapes stay true marbled paper.
A drop of radius r falling at C moves each point P away from C so that its squared distance grows by r²: P' = C + (P − C) · √(1 + r² / |P − C|²). So the area of every ink stays the same and the old colours squeeze into thin rims round the new one. While the drop spreads, each frame applies only the growth of r² since the frame before: drops applied one after another at the same point add up.
The comb has TINES teeth along the board: each point slides across by a bell (SHARPNESS wide) of its distance from the nearest tooth, so the waves come out round. The stylus is the pointer: its path, taken with getCoalescedEvents, is walked in short steps, and at each step the points nearby move with it along a gaussian TRAIL wide. In short steps the move never gets steep enough to fold the liquid over itself.
After every move, an edge stretched past 3.4 pixels gets new points along it, and two points too close become one. Only near the board, though: farther out, where nobody looks and neither the comb nor the stylus can bring anything back in, edges stay long. At the end of a shuffle every ink is painted on a hidden canvas in a colour of its own, at half size: those that show nowhere (buried under the new ones) are let go, and the others are cut to a margin round the board.
There are as many drops as it takes to cover the board COVER times over, on a slightly shaken honeycomb, and they fall from the point clicked outwards, STAGGER seconds apart, each spreading in SPREAD seconds with expo.out. Into half the stones a second, smaller drop of another colour falls. The comb goes one way and the other way on alternate shuffles, so the ink does not drift off to one side.
The pale veins round every ink are the same shape filled three times in the bath's colour, nudged a hair three ways, under the ink: it costs a third of stroking the outline. Over everything lies a paper grain with a few fibres, blended in overlay. The label is HTML: it keeps the width of its longest words, and when the words change the icon slides to its new place while the words swap with a blur.
| Name | File | Default | What it changes |
|---|---|---|---|
COVER | script.js | 1.1 | How much of the board the new palette covers: 1 about all of it, less leaves more of the old. |
SIZE | script.js | 11 | How wide a drop spreads, in pixels: the size of the stones. |
STAGGER | script.js | 0.015 | The seconds between one drop and the next. |
SPREAD | script.js | 0.55 | The seconds a drop takes to spread. |
COMB | script.js | 8 | How far the comb drags the ink after the drops, in pixels (0, no comb). |
TINES | script.js | 3 | The teeth of the comb, across the height of the board. |
SHARPNESS | script.js | 5 | How far from a tooth the ink still follows it, in pixels. |
TRAIL | script.js | 13 | How wide the pointer's trail through the ink is, in pixels. |
DRAG | script.js | 0.8 | How much of the pointer's movement the ink follows: 0 none, 1 all of it. |
VEINS | script.js | 0.55 | The pale veins round every ink: 0 none, 1 strong. |
BUSY | script.js | Mixing inks | The words while the drops spread. |
DONE | script.js | New palette | The words once the new palette is in. |
SEED | script.js | 11 | The first sheet: another number, another pattern. |
PALETTES | script.js | [['#1d3557', '#a63d40', '#e9b44c', '#2f6f68', '#f3e6c8'], …] | The palettes the button goes through in turn, one ink for each colour (the first is the first sheet's). |
For the button that reshuffles or makes something at random in a creative tool: colour palettes, covers, patterns, themes, names. It belongs where paper, bookbinding and handwork belong: publishers, stationers, bookbinders, book and design sites. It is a button to show off, one to a page, not one for a toolbar.
The board is 60 pixels tall like the other buttons and 320 wide, or the screen minus 48; the sheet is drawn again at the real size. A finger dragged across combs the ink like the pointer (touch-action: pan-y, so up and down the page still scrolls); a tap shuffles, and the shower starts from where the finger touched.
No stylus and no gloss following the pointer. At a click the new sheet is worked out all at once and the old one fades away on top of it; the words change in a fade, with no slide and no blur, and the board pressed darkens a little instead of shrinking.
GSAP 3.15.0, EB Garamond (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 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.