
The layered button
Buttons, Hover, Physics
Free
In the rich style: the Menu button of an aquarium is a little fishbowl; pressed, a tank opens under it, the water rises over the sand, the plants lift and five fish swim in from both sides and line up, each one holding the tag of a menu item on a string.
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 aquarium menu</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fredoka@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the menu of a public aquarium: the Menu button is a little round fishbowl; pressed, a tank opens under it,
the water rises over the sand, the plants lift and sway, the bubbler starts, and five fish swim in from the
sides and line up, each one holding the tag of a menu item on a string. Pointing at an item, its fish turns
round and lets out a bubble; choosing it, the fish loops and the tag lights up. Closing, the fish swim away
and the water drains. -->
<nav class="acquario" aria-label="Main" data-acquario>
<button class="bowl" type="button" aria-expanded="false" aria-controls="acquario-tank" data-toggle>
<svg class="bowl__glass" viewBox="0 0 48 48" aria-hidden="true">
<defs>
<radialGradient id="bowl-water" cx="0.4" cy="0.35" r="0.75">
<stop offset="0" class="stop-turq"/>
<stop offset="1" class="stop-deep"/>
</radialGradient>
<clipPath id="bowl-clip"><circle cx="24" cy="25" r="17.4"/></clipPath>
</defs>
<g clip-path="url(#bowl-clip)">
<g class="bowl__slosh">
<path class="bowl__water" d="M2 21 Q 13 18.5 24 21 T 46 21 V 48 H 2 Z"/>
<path class="bowl__sand" d="M2 38 Q 14 35 26 37.5 T 46 37 V 48 H 2 Z"/>
</g>
<g class="bowl__fish">
<path class="bowl__fish-tail" d="M-6.5 0 L -10.5 -3.4 L -9.3 0 L -10.5 3.4 Z"/>
<path class="bowl__fish-body" d="M5 0 C 3 -4.2 -3.5 -4.4 -6.8 0 C -3.5 4.4 3 4.2 5 0 Z"/>
<path class="bowl__fish-stripe" d="M0.8 -3.6 C 1.6 -1.2 1.6 1.2 0.8 3.6"/>
<circle class="bowl__fish-eye" cx="2.4" cy="-0.8" r="0.9"/>
</g>
</g>
<circle class="bowl__rim" cx="24" cy="25" r="18.2"/>
<path class="bowl__neck" d="M16 8.4 H 32"/>
<path class="bowl__shine" d="M13.5 18 C 15 13.5 18.5 11 22 10.4"/>
</svg>
<span class="bowl__word" data-word>Menu</span>
</button>
<div class="tank" id="acquario-tank" data-tank inert>
<canvas class="tank__canvas" data-canvas aria-hidden="true"></canvas>
<ul class="tank__items" data-items>
<li><a class="tag" href="#tickets" data-item><span class="tag__hole" aria-hidden="true"></span><span class="tag__word">Tickets</span></a></li>
<li><a class="tag" href="#exhibits" data-item><span class="tag__hole" aria-hidden="true"></span><span class="tag__word">Exhibits</span></a></li>
<li><a class="tag" href="#feeding" data-item><span class="tag__hole" aria-hidden="true"></span><span class="tag__word">Feeding times</span></a></li>
<li><a class="tag" href="#shop" data-item><span class="tag__hole" aria-hidden="true"></span><span class="tag__word">Shop</span></a></li>
<li><a class="tag" href="#visit" data-item><span class="tag__hole" aria-hidden="true"></span><span class="tag__word">Visit</span></a></li>
</ul>
</div>
</nav>
</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.
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 aquarium menu” from Animorio and fit it into my siteThe whole tank is drawn again on every frame on a single canvas, from the back to the glass: the water (a shape whose top is the rippling surface, two waves added together), the far rocks, the rays of light, the sand with its grains, the stones, the plants, the light on the sand, the fish with their strings, the bubbles, the plants in front, the tint of the water over everything that is under it, the reflections on the glass, the frame and the lid. The sand, the stones and the plants come from a seeded random, so they are the same at every visit and every size.
Every blade of weed is a line of twelve pieces starting from its root in the sand: the angle is added up from the root to the tip, with a wave that grows towards the tip, so the root never moves and the tip sways most. Without water the plants lie down short; as the water rises they stand up with an elastic.
Each fish is drawn in its own coordinates: the body with four curves, the tail hinged at the end of the body and turning with the beat, the back and belly fins drawn before the body so their base stays hidden, the side fin hinged behind the gill, the scales, the stripes kept inside the body, the eye with its glint. The tail beats faster with the speed.
The fish come along a Bézier curve from the edge to their place, SWIM seconds with power2.out, one every STAGGER. The way they face follows their speed; turning is a spring on the horizontal scale, so while it turns the fish goes thin and then back. In their places they drift gently and face their tag.
The items are real links over the canvas: on every frame each one moves towards the mouth of its fish a little late, like something carried on a string, and the string is drawn from the mouth to the hole in the tag. The button has aria-expanded, the closed tank is inert, Esc closes and gives the focus back to the button, the arrows go from item to item, a tap outside closes.
| Name | File | Default | What it changes |
|---|---|---|---|
FILL | script.js | 1.1 | The seconds the water takes to rise. |
SWIM | script.js | 1.15 | The seconds a fish takes to swim to its place. |
STAGGER | script.js | 0.14 | The seconds between one fish and the next. |
WAG | script.js | 1 | How hard the fish wag their tails. |
SWAY | script.js | 1 | How much the plants sway in the current. |
BUBBLES | script.js | 8 | The bubbles a second coming out of the bubbler stone. |
CAUSTICS | script.js | 0.55 | How bright the light of the water running over the sand is. |
FISH | script.js | 1 | The size of the fish. |
For the menu of the site of an aquarium, a marine park, a fish shop or a science museum: the visit items (tickets, exhibits, times, shop, how to get there). From three to six items: with more the rows get too tight.
The tank is as wide as the screen and at most as tall as the room left under the button; the fish and the tags get smaller with the rows. Tapping a tag chooses it, and the tap also makes its fish turn round.
The tank fades in already full, with the fish still in their places and the plants upright; no swimming, bubbles, tail beat or waves. Pointing at an item only changes the colour of the tag.
GSAP 3.15.0, Fredoka (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 14 KB; the libraries: GSAP 28 KB.
Tested on 22 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.