
The variable weight menu
Menus, Text, Hover, Physics
NewFree
The top bar of a sailing school, with a strip of water along its bottom and a small sailboat lying under the page you are on. Choosing another link the boat turns about, fills its sail and runs there, and its wake rolls along the strip, bounces off the ends and rocks it once it has stopped; the letters of the links ride the waves that pass. The water is a real wave equation, worked out cell by cell. On a phone the links open in a panel of moorings, one for each link, and the boat sails out of one and into another.
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 sailboat nav</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the top bar of a sailing school. Along the bottom of the bar runs a strip of water, worked out as a real wave
equation, and a small sailboat lies under the page you are on. Choosing another link the boat turns, fills its
sail and runs there, leaving a wake that rolls along the strip, bounces off the ends and rocks it when it has
stopped; the words ride the waves that pass under them. Pointing at a link drops a ripple under it. On a phone
the links open in a panel of moorings, one strip of water each, and the boat sails out of one and into another -->
<header class="bar" data-bar>
<canvas class="sea" aria-hidden="true" data-sea></canvas>
<a class="brand" href="#" aria-label="Halyard Sailing School, home">
<svg class="brand__mark" viewBox="0 0 22 24" aria-hidden="true">
<path class="brand__pole" d="M4 2v20"/>
<path class="brand__flag" d="M5 3.2l13 4.6L5 12.4z"/>
</svg>
<span class="brand__name">Halyard</span>
</a>
<nav class="nav" id="halyard-links" aria-label="Main" data-nav>
<canvas class="nav__sea" aria-hidden="true" data-panel-sea></canvas>
<ul class="list" data-list>
<li class="item"><a class="link" href="#" aria-current="page" data-link><span class="link__word" data-word>Lessons</span></a></li>
<li class="item"><a class="link" href="#" data-link data-autoclic><span class="link__word" data-word>Fleet</span></a></li>
<li class="item"><a class="link" href="#" data-link data-autoclic><span class="link__word" data-word>Regattas</span></a></li>
<li class="item"><a class="link" href="#" data-link><span class="link__word" data-word>Clubhouse</span></a></li>
<li class="item"><a class="link" href="#" data-link data-autoclic><span class="link__word" data-word>Contact</span></a></li>
</ul>
</nav>
<a class="cta" href="#" data-cta><span class="cta__bg" aria-hidden="true" data-cta-bg></span><span class="cta__label">Book a lesson</span></a>
<button class="toggle" type="button" aria-expanded="false" aria-controls="halyard-links" data-toggle>
<svg class="toggle__icon" viewBox="0 0 24 24" aria-hidden="true">
<path class="toggle__line" d="M4 9 Q7 7 10 9 T16 9 T22 9" data-line/>
<path class="toggle__line" d="M4 15 Q7 13 10 15 T16 15 T22 15" data-line/>
</svg>
<span class="toggle__label" data-toggle-label>Menu</span>
</button>
</header>
</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 sailboat nav” from Animorio and fit it into my siteThe strip is a row of cells every 3 pixels, each with its height and its speed. At every step each cell is pulled towards its neighbours (c² times the second difference of the heights, over dx²: the waves run at WAVE_SPEED pixels a second), slowed by a friction set by CALM, by a little viscosity on the speeds that keeps the crests smooth and by a very weak spring that brings back the level. At both ends a cell mirrors its neighbour, so the waves bounce off the ends of the bar. The steps are as small as it takes for a wave never to move more than 0.7 of a cell in one (the Courant condition), and never more than 1/30 of a second a frame.
While the boat moves, the cells round it get a push downwards at every step shaped like a Ricker wavelet, (1 − d²)·e^(−d²/2), as strong as the boat's speed times WAKE: a trough under the hull and the water it moves heaped at the bow and the stern. The shape adds up to zero, so the strip keeps its level however far the boat goes (with a plain dip under the hull the water behind it drained away). The equation does the rest: the boat runs faster than the waves and leaves them behind.
The boat's position is a spring towards its mooring, damped at 0.74 with its frequency taken from SAIL_TIME, but the spring's push is capped (2600/SAIL_TIME² pixels a second squared): from far away the boat does not jump, it gathers way, glides and arrives going just a little too far. Its heading is another spring, from 1 to −1: when it goes through zero the shape drawn with scale(face, 1) narrows and opens again turned round, like a boat seen from the side coming about.
At every step the boat reads the height of the water at its bow, its stern and its middle: the mean height is the target of a stiff spring (heave, with a little inertia), the slope from bow to stern plus the acceleration (the bow lifts as it speeds up and dips as it brakes) is the angle of another. The sail fills with the speed (the leech is a quadratic curve that bellies out) and the pennant streams aft. The water in front of the hull is drawn again over it with the same gradient as the strip, so the boat sits in it.
Every word is cut into letters (the link keeps its name in aria-label); the middle of each letter is measured once with offsetLeft, which does not see transforms. Every frame each letter takes translateY(−height·BOB) and a turn from the slope of the water under it, and the style is only written when it changes. The mouse on a link adds a bell to the speeds of the cells under the word: that is the drop, which opens into two waves going both ways.
Below 760 pixels the links go into a panel under the bar, and every row has its own strip of water with its own equation. The Menu button opens the panel with a clip-path coming down, the rows come in one after the other, the water in each row rises with a back.out (so it brims over a little and settles) and the boat comes in from the right edge to its mooring, next to the word. Choosing another row the boat sails out of its mooring to the right and comes into the new one from the right. The waves on the button flatten and turn into a cross, Esc closes and gives the focus back to the button, the arrow keys move along the links.
| Name | File | Default | What it changes |
|---|---|---|---|
SAIL_TIME | script.js | 1.1 | About how many seconds the boat takes to sail under a new link. |
WAKE | script.js | 1 | How big a wake the boat leaves behind it (0, none). |
WAVE_SPEED | script.js | 230 | How fast the waves run along the water, in pixels a second. |
CALM | script.js | 0.9 | How quickly the water goes still again: higher, sooner. |
RIPPLE | script.js | 1 | How big the ripple dropped under a link pointed at is (0, none). |
BOB | script.js | 0.7 | How much the letters of the links ride the waves that pass under them (0, they stay still). |
BOAT_SIZE | script.js | 1.2 | The size of the boat. |
For the header of anyone who deals with the sea and water: a sailing school, a yacht club, a boat or kayak hire, a seaside hotel or holiday homes, a beach club, a site for regattas or boat trips. It suits a few short links on a dark bar; the colours of the water, the boat and the bar change in the variables.
Below 760 pixels the bar is 60 pixels tall and as wide as the screen less 32 (358 at 390), the yellow button goes and on the right comes the Menu button with two drawn waves. Tapped, a panel of the same blue comes down under the bar with the five links in a column, each with its own strip of water: the rows come in one after the other, the water rises in each row brimming over a little, and the boat comes in from the right and stops next to the current link. Tapping another link the boat sails out of its mooring to the right and comes into the new one from the right; a finger down on a link drops the ripple, like the mouse. The waves on the button turn into a cross and the word Close; Esc closes.
The water stays flat and still, the letters do not move and no drops fall: choosing another link the boat fades out where it was and fades in under the new link, already at rest. On a phone the panel fades in and out without coming down, and the boat is already at its mooring. The buttons neither press in nor widen.
GSAP 3.15.0, Bricolage Grotesque (font variabile, con gli assi del peso e della larghezza) 5.3.0.
Compressed with gzip, the three demo files weigh 13 KB; the libraries: GSAP 28 KB.
Tested on 13 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.