
The layered button
Buttons, Hover, Physics
Free
The top bar of a football club, with a stand behind it: hundreds of small square cards, one face dark and one gold. Under the current link the cards show gold in a pill; choosing another link the stand does the Mexican wave, a column of cards rising and turning over runs there, the old patch goes plain and the new one turns gold. Pointing at a link the cards shiver in a round wave.
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 Mexican wave nav</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 top bar of a football club. Its background is a stand of supporters holding up hundreds of small cards,
each with a plain face and a face in the club's gold: under the current link the cards show gold, and
choosing another link a Mexican wave runs there, the cards rising and turning over as it passes -->
<header class="ola" data-bar>
<canvas class="ola__stand" aria-hidden="true" data-stand></canvas>
<canvas class="ola__stand ola__stand--veil" aria-hidden="true" data-veil></canvas>
<span class="ola__lights" aria-hidden="true"></span>
<a class="brand" href="#" aria-label="Ostrel Athletic, home" data-brand>
<svg class="brand__crest" viewBox="0 0 24 28" aria-hidden="true">
<path class="brand__shield" d="M12 1.6 21.4 4.6v8.2c0 6.1-3.9 10.6-9.4 13.6C6.5 23.4 2.6 18.9 2.6 12.8V4.6z"/>
<path class="brand__band" d="M3.4 16.2 20.6 7.4v4.4L4.9 19.9z"/>
<path class="brand__ball" d="M12 4.9 13.6 8.3l3.7.4-2.8 2.5.8 3.6L12 13l-3.3 1.8.8-3.6-2.8-2.5 3.7-.4z"/>
</svg>
<span class="brand__name">Ostrel <span class="brand__light">Athletic</span></span>
</a>
<nav class="strip" aria-label="Main" data-strip>
<ul class="list" data-list>
<li><a class="link" href="#" data-link data-autoclic><span class="link__word">Fixtures</span></a></li>
<li><a class="link" href="#" aria-current="page" data-link><span class="link__word">Squad</span></a></li>
<li><a class="link" href="#" data-link data-autoclic><span class="link__word">Tickets</span></a></li>
<li><a class="link" href="#" data-link data-autoclic><span class="link__word">Club</span></a></li>
<li><a class="link" href="#" data-link data-autoclic><span class="link__word">Shop</span></a></li>
</ul>
</nav>
<a class="cta" href="#" aria-label="Become a member" data-cta><span class="cta__long">Become a member</span><span class="cta__short">Join</span></a>
</header>
</main>
<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.
30 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 Mexican wave nav” from Animorio and fit it into my siteThe bar is divided into a grid of cells CARD pixels apart (ten rows in a bar 60 pixels tall), and every cell is a card with an angle, a turning speed and the angle its spring pulls it to. Each also has a small slant at rest, a brightness, a stiffness and a delay of its own, taken from a seeded random generator: the stand looks alive but is the same at every load.
Turning a card means moving the angle its spring pulls it to by π, always forward: the gold face is the one in view when the cosine of the angle is negative. The spring (stiffness FLIP, damping WOBBLE) is integrated in steps of 1/120 of a second, so even a stiff spring stays steady; the frame step is at most 1/30 of a second, and the card goes a little past and comes back, as if turned by a hand.
Choosing a link, every card on the way between the old pill and the new one gets two turns booked: to gold at the moment the column reaches it (the distance divided by SPEED, plus LEAN milliseconds per row, so the column leans, plus its own delay) and to the face it must keep BAND/SPEED seconds later. The cards off the way that must change face turn in order of distance. The new pill is a rounded box round the word, measured on the cards' centres.
Under the cards runs the discrete 2D wave equation, solved 240 times a second: every cell moves towards its neighbours (u'' = c²∇²u), a hand brings it back to rest (−ω²u), it loses a little motion and the edges of the bar reflect. The column of the wave pushes up the cells it crosses and leaves a wake; on arrival and under the pointer a gaussian bell opens a ring that spreads. The wave's value tips and lifts the cards.
Every card is drawn on the canvas as a rectangle as tall as the cosine of its angle, raised and a little enlarged while it turns and on the crest. Its light is the diffuse part (normal times the direction of the floodlights, from above and in front) plus a glint when the face looks at the lights; edge on, its pale edge shows, as thick as the sine of the angle.
Every frame the cards under each word that show gold are counted and written into --on: the CSS mixes the word's colour from light to dark with color-mix and gives it a halo the colour of the cards under it, which covers the gaps between them. When nothing moves any more the loop stops.
| Name | File | Default | What it changes |
|---|---|---|---|
CARD | script.js | 6 | How far apart the cards are, in pixels: less makes more of them. |
SPEED | script.js | 560 | How fast the wave runs from the old link to the new one, in pixels a second. |
BAND | script.js | 150 | How wide the column of gold cards running with the wave is, in pixels. |
FLIP | script.js | 380 | How stiff the spring that turns each card over is: higher, a quicker flip. |
WOBBLE | script.js | 0.34 | How little a card rocks once it has turned: less rocks more. |
LIFT | script.js | 2.6 | How far a card rises as it turns and on the crest, in pixels. |
RIPPLE | script.js | 0.9 | How strongly the cards shiver round the pointer on a link (0, not at all). |
LEAN | script.js | 9 | How much the column leans, in milliseconds between one row of cards and the next. |
For the header of a club, a league, a festival, a bar where people watch the games, a sports shop or a stadium ticket app. With the colours in :root it becomes any club's stand: just change --card and --team.
The bar is 56 pixels tall and as wide as the screen less 32: the crest stays without the name and the button just says Join (screen readers still hear Become a member). The cards shrink with the bar, always ten rows. The links scroll sideways inside the bar, which fades out on the side where more are hidden; the chosen link slides to the middle and the wave already runs to where it will land. Scrolling by hand, the pill follows the word by turning cards. A finger on a link opens the round wave where it touches.
No wave, no springs and no ripples: the gold pill is already in place under the current link and, choosing another link, the old stand fades away over the new one, with the pill already under the chosen word. Presses do not shrink anything.
Saira (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB, and no libraries are needed.
Tested on 8 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.