
The variable weight menu
Menus, Text, Hover, Physics
NewFree
In the minimal style: the menu of an architecture studio. Pressing Menu the plumb bob drawn in the button drops on a hairline thread, the dashed true vertical is drawn beside the links, and the bob swings and stops with its vermilion tip on the current page. Then it follows the pointer from link to link: the thread lengthens and shortens on a spring and the bob sways like a real pendulum, and the link by its tip takes weight and width.
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 plumb line menu</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the menu of an architecture studio. At rest a small Menu button with a plumb bob drawn in it. Pressed, the bob
drops out of the button on its thread, a dashed true vertical is drawn beside the links, and the bob swings and
settles on the page you are on. It follows the pointer (the finger, the arrow keys) from link to link, its
thread lengthening on a spring and swinging when it moves; the link next to its tip takes weight and width -->
<div class="plumb" data-frame>
<button class="toggle" type="button" aria-expanded="false" aria-controls="plumb-menu" data-toggle data-autoclic>
<span class="toggle__icon" aria-hidden="true">
<svg viewBox="0 0 20 24" data-icon>
<line x1="10" y1="1" x2="10" y2="8" class="toggle__thread"/>
<g data-icon-bob>
<rect x="7.5" y="7.5" width="5" height="2.5" rx="0.6" class="bob__cap"/>
<path d="M5 10.5 H15 L10 21 Z" class="bob__body"/>
<path d="M8.1 17.2 H11.9 L10 21 Z" class="bob__tip"/>
</g>
</svg>
</span>
<span class="toggle__words" aria-hidden="true">
<span class="toggle__word" data-word-open>Menu</span>
<span class="toggle__word" data-word-close>Close</span>
</span>
<span class="sr" data-toggle-name>Open the menu</span>
</button>
<nav class="menu" id="plumb-menu" aria-label="Main" data-menu inert>
<ul class="menu__list" data-list>
<li class="menu__item"><a class="row" href="#projects" aria-current="page" data-row><span class="row__mask"><span class="row__word" data-word>Projects</span></span><span class="row__note" data-note>Built and drawn</span></a></li>
<li class="menu__item"><a class="row" href="#studio" data-row><span class="row__mask"><span class="row__word" data-word>Studio</span></span><span class="row__note" data-note>Nine people, one room</span></a></li>
<li class="menu__item"><a class="row" href="#method" data-row><span class="row__mask"><span class="row__word" data-word>Method</span></span><span class="row__note" data-note>Survey, plan, build</span></a></li>
<li class="menu__item"><a class="row" href="#writing" data-row><span class="row__mask"><span class="row__word" data-word>Writing</span></span><span class="row__note" data-note>Notes from site</span></a></li>
<li class="menu__item"><a class="row" href="#contact" data-row><span class="row__mask"><span class="row__word" data-word>Contact</span></span><span class="row__note" data-note>Visit the studio</span></a></li>
</ul>
</nav>
<!-- the thread, the bob, the true vertical and the level marks, drawn by the script -->
<svg class="line" aria-hidden="true" data-svg>
<line class="line__guide" data-guide/>
<g data-marks></g>
<path class="line__thread" data-thread/>
<g data-bob>
<rect x="-4" y="0" width="8" height="4" rx="1" class="bob__cap"/>
<path d="M-7.5 4.6 H7.5 L0 22 Z" class="bob__body"/>
<path d="M-2.9 15.3 H2.9 L0 22 Z" class="bob__tip"/>
<line x1="-7.5" y1="4.6" x2="7.5" y2="4.6" class="bob__rim"/>
</g>
</svg>
</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.
28 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 plumb line menu” from Animorio and fit it into my siteThe bob is a pendulum: the angle theta follows theta'' = -(g/L) sin theta - 0.7 · (L'/L) theta' - damping · theta', with g of 2200 pixels a second squared. The second term is the real one of pendulums whose thread lengthens and shortens: shortening the thread the swing grows, lengthening it the swing calms, so the bob swings more as it rises towards a link higher up. The time step is 1/240 of a second, never more than 1/30 a frame, and the loop stops when everything is still.
The thread's length goes towards the one that puts the bob's tip at the height of the link, on a spring (SPRING): leaving the button the bob drops, goes a little past the link and comes back. At every change of link the angle gets a push in proportion to how many links it jumped (SWING), and the mouse moving sideways adds a small push.
Thread, bob, vertical and marks are an SVG over the links, redrawn every frame in pixels measured from the page (and measured again on resize). The thread is a quadratic curve with its control point pushed sideways against the angle's speed, so it bends slightly as if the air held it back; the bob turns with the angle, so its tip carries on the thread's direction.
For each link the distance between the bob's tip and the middle of the link is measured, in rows; on a soft curve over REACH rows the link takes Mona Sans weight (WEIGHT) and width (WIDTH), its note fades in and the vertical's mark grows towards the word, darkening when the bob hangs almost straight.
One GSAP timeline: Menu and Close swap, the vertical is drawn, the links rise out of their masks 50 milliseconds apart, and the bob drops on its own. Closing, the thread goes back to zero on the same spring and the bob in the button rocks on an elastic.out. The button has aria-expanded; closed, the menu is inert; the arrows move the bob, Esc closes, Tab goes round the button and the links. The preview's click (detail 0) makes a whole tour.
| Name | File | Default | What it changes |
|---|---|---|---|
SPRING | script.js | [170, 19] | The thread's spring as it lengthens and shortens: how stiff and how much it is held back. |
SWING | script.js | 0.22 | How much the bob swings going from one link to the next (0, it hangs still). |
DAMP | script.js | 1.5 | How soon a swing dies away. |
WEIGHT | script.js | [300, 720] | The weight of a link far from the bob and of the one at its tip. |
WIDTH | script.js | [88, 116] | The width of a link far from the bob and of the one at its tip. |
REACH | script.js | 1.3 | Over how many links the bob is felt. |
STAGGER | script.js | 0.05 | The seconds between one link and the next when opening and closing. |
For the menu of anyone who builds, measures or designs: an architecture or engineering studio, a construction firm, a surveyor, a carpenter, a science museum. It suits four, five or six short links on a light background; each link's note says in a few words what lies behind it.
The frame is as wide as the screen less 32 pixels and at most 86% of the height tall; the links shrink with the width and below 360 pixels the notes are left out. The bob follows a finger sliding over the links, like the mouse, and a tap chooses the link.
No dropping and no swinging: the bob appears already still beside the link, the vertical and the links fade in, and the bob moves from link to link at once. The links still take weight and width beside the tip.
GSAP 3.15.0, Mona Sans (font variabile, con gli assi della larghezza e del peso) 5.3.0.
Compressed with gzip, the three demo files weigh 7.4 KB; the libraries: GSAP 28 KB.
Tested on 23 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.