
The layered button
Buttons, Hover, Physics
Free
The deep blue footer of a small surf school, with the sea along its top edge: a row of springs tied to each other on three planes, with waves that run, bounce off the sides and die down, foam on the crests and the sun glinting under the surface. The pointer stirs the water, a link pointed at draws a small wave to itself, signing up to the newsletter sends a long wave from one side to the other and brings the tide up for a moment; a buoy and a board float for real.
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 tide footer</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the footer of a small surf school: the sea runs along its top edge, a row of coupled springs drawn on three
planes with foam on the crests and the light glinting under the surface. The pointer stirs the water, a link
pointed at draws a small wave to itself, and a sign-up to the swell report sends a long wave across the whole
footer while the tide comes up for a moment; a buoy and a board float on it -->
<footer class="tw" data-footer data-autoclic>
<canvas class="tw__sea" aria-hidden="true" data-sea></canvas>
<div class="tw__panel" data-panel>
<div class="tw__top">
<form class="tw__form" action="#" novalidate aria-labelledby="tw-news" data-form>
<h2 class="tw__title" id="tw-news" data-in>The Thursday swell report</h2>
<p class="tw__lede" data-in>The week’s best tides, the wind for the weekend and the lessons with places left.</p>
<label class="sr" for="tw-email">Email address</label>
<div class="tw__field" data-field data-in>
<input class="tw__input" id="tw-email" name="email" type="email" inputmode="email" autocomplete="email" autocapitalize="off" spellcheck="false" enterkeyhint="send" placeholder="you@example.com" aria-describedby="tw-note" aria-invalid="false" data-input>
<button class="tw__send" type="submit" data-send>
<span class="tw__send-bg"></span>
<span class="tw__send-label" data-send-label>Subscribe</span>
<svg class="tw__spin" viewBox="0 0 24 24" aria-hidden="true" data-spin><circle cx="12" cy="12" r="8"/></svg>
<svg class="tw__done" viewBox="0 0 24 24" aria-hidden="true" data-done><path d="M6.4 12.6l3.7 3.7 7.6-8.6"/></svg>
</button>
</div>
<p class="tw__note" id="tw-note" aria-live="polite" data-note data-in><span class="tw__line">One email a week. Leave with one click.</span></p>
</form>
<nav class="tw__cols" aria-label="Footer">
<div class="tw__col" data-in>
<h3 class="tw__head">Learn</h3>
<ul>
<li><a class="tw__link" href="#" data-link>First waves</a></li>
<li><a class="tw__link" href="#" data-link>Private coaching</a></li>
<li><a class="tw__link" href="#" data-link>Kids’ camps</a></li>
<li><a class="tw__link" href="#" data-link>Surf and yoga</a></li>
</ul>
</div>
<div class="tw__col" data-in>
<h3 class="tw__head">Hire</h3>
<ul>
<li><a class="tw__link" href="#" data-link>Boards</a></li>
<li><a class="tw__link" href="#" data-link>Wetsuits</a></li>
<li><a class="tw__link" href="#" data-link>Lockers</a></li>
<li><a class="tw__link" href="#" data-link>Gift cards</a></li>
</ul>
</div>
<div class="tw__col" data-in>
<h3 class="tw__head">School</h3>
<ul>
<li><a class="tw__link" href="#" data-link>Surf report</a></li>
<li><a class="tw__link" href="#" data-link>Our coaches</a></li>
<li><a class="tw__link" href="#" data-link>Water safety</a></li>
<li><a class="tw__link" href="#" data-link>Contact</a></li>
</ul>
</div>
</nav>
</div>
<div class="tw__base" data-in>
<p class="tw__brand">
<svg class="tw__mark" viewBox="0 0 28 28" aria-hidden="true"><path d="M2.5 19.5c2.6 0 3.9-1.6 5.6-3.9 2.3-3.2 4.4-6.4 9.2-6.4 3.9 0 6.3 2.6 6.3 5.6 0 2.4-1.7 4-3.8 4-1.8 0-3-1.2-3-2.8 0-1.2.8-2 1.8-2"/><path d="M2.5 23.6c2.3 0 3.4-1.2 5.1-1.2s2.8 1.2 5.1 1.2 3.4-1.2 5.1-1.2 2.8 1.2 5.1 1.2"/></svg>
<span class="tw__name">Tidewell Surf School</span>
<span class="tw__copy"><svg viewBox="0 0 12 12" aria-hidden="true"><circle cx="6" cy="6" r="5.1"/><path d="M7.9 4.5a2.3 2.3 0 1 0 0 3"/></svg><span class="sr">Copyright</span> 2026 Tidewell Surf Ltd</span>
<span class="tw__hours">Beach hut open daily, 7:30 to 19:00</span>
</p>
<nav class="tw__legal" aria-label="Legal">
<a class="tw__link tw__link--small" href="#" data-link>Privacy</a>
<a class="tw__link tw__link--small" href="#" data-link>Terms</a>
<a class="tw__link tw__link--small" href="#" data-link>Cookies</a>
</nav>
</div>
</div>
</footer>
</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.
56 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 tide footer” from Animorio and fit it into my siteThe surface is a row of springs every 7 pixels: each has a height and a speed and is pulled by its two neighbours (the wave equation, at the speed WAVE_SPEED), a little towards the calm level, held back by the water (DAMPING) and with a term that smooths the shortest ripples away. The ends send the waves back. The frame's step is at most 1/30 of a second and is split into steps of 1/240 inside, so the springs stay stable even with fast waves.
There are three rows, one behind the other: the far ones stand higher, are darker and only get part of the pushes. On each row a swell is added, made of three long travelling waves whose size grows and falls in sets (SWELL); the tide lifts all three. The height is eased with a hyperbolic tangent instead of being cut, so a big crest or a deep trough never goes flat.
A link pointed at starts a bell-shaped push running towards it at the same speed as the waves: that way it keeps feeding the crest it makes, and the wave builds up on the way until it arrives over the link. The sign-up does the same from one side to the other with a wide push, harder at the start, while GSAP brings the tide up by TIDE pixels and down again. The pointer pushes down where it goes down and under itself, and lifts the water ahead of it in proportion to its speed.
Every frame foam grows where the crest is steep, pointed or rising fast, and slowly fades: it is drawn as a white line in four thicknesses, plus a dashed lace just under it. The light is a line along the surface, stronger on the faces turned to the sun, and some fifty short streaks under the water that come and go each at its own pace, tilted like the surface and only where it faces the light.
The board is made of five slices: each gets a push in proportion to how far it sits under the surface, square to the surface itself, so on the slope of a wave the board slides forward; the water holds back its motion and carries it with its flow, worked out from the waves' slope. The slices' pushes also give its turning and pitching. The buoy is a ball with its weight low, which rights itself and leans with the slope, held by its mooring. Moving up and down, both push the water and send off small rings.
| Name | File | Default | What it changes |
|---|---|---|---|
SWELL | script.js | 1 | How much the sea rolls on its own: at 0 it stays flat until something stirs it, and once it is calm again the canvas stops redrawing. |
WAVE_SPEED | script.js | 260 | How fast a wave runs along the water, in pixels a second. |
DAMPING | script.js | 0.5 | How quickly the waves die down: less lets them bounce off the sides for longer. |
STIR | script.js | 1 | How much the pointer stirs the water as it passes through. |
FOAM | script.js | 1 | How much foam the crests throw up (and how much spray): at 0 no foam. |
TIDE | script.js | 10 | How many pixels the tide comes up for a moment after a sign-up. |
FLOATERS | script.js | 2 | How many things float: 0, 1 (the buoy) or 2 (the buoy and the board). |
WAIT | script.js | 1.1 | How long the sign-up takes in the demo, in seconds. |
For the footer of a surf school, a beach club, a board or kayak hire, a sailing club, a swimming pool, a beach festival or a swimwear brand: anywhere the bottom of the page can be the sea, and signing up deserves a wave.
The footer takes the width of the screen: the newsletter goes on top, then the columns stack, two side by side and the third underneath, with taller rows to tap; the mark, the copyright, the hours and the legal links go one under the other. The sea stays as tall with fewer springs, and the board is a little shorter and further left. A finger dragged sideways through the water stirs it like the pointer (up and down scrolls the page), a tap is a pebble dropping in, and tapping a link sends the wave its way and the letters ride it as with the hover.
The sea is drawn once, still, with its waves and its glints, and fades in: no swell, no waves from the pointer, the links or the sign-up, and the tide does not move. The buoy and the board lie still on the water. Pointing at a link the word only changes colour; the button does not narrow and the arc keeps still; a wrong field does not shake.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 19 KB; the libraries: GSAP 28 KB.
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.