
The variable weight menu
Menus, Text, Hover, Physics
NewFree
The top navigation bar of a heritage tram company. The bar is the ceiling of the line: the contact wire hangs under it and under the wire a double-ended tram, cream and tomato red, runs on a rail and stops under the page you are on, its pantograph pressed against the wire. Choosing another link the bell rings, the tram pulls away leaning back, sparks fly from the pantograph, it brakes with sparks under the wheels, sways on its springs and opens its doors. Pointing at a link makes the wire over it shake.
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 tram 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 heritage tram company. The bar is the ceiling of the line: the contact wire hangs under it,
and under the wire a double-ended tram runs on its rail to the page you are on, its pantograph sliding along
the wire. Choosing another link the bell rings, the tram pulls away leaning back, sparks fly where the
pantograph touches the wire, it brakes with sparks under the wheels, sways on its springs and opens its doors.
Pointing at a link plucks the wire over it. -->
<div class="line" data-line>
<header class="bar" data-bar>
<a class="brand" href="#" aria-label="Brassline heritage trams, home">
<svg class="brand__mark" viewBox="0 0 26 22" aria-hidden="true">
<path class="brand__wire" d="M1 2.2h24"/>
<path class="brand__pole" d="M13 2.5l-3.4 4.6h6.8z"/>
<rect class="brand__body" x="3" y="7.4" width="20" height="9.6" rx="2.4"/>
<path class="brand__glass" d="M6 9.6h4v3.2H6zM11.2 9.6h3.6v3.2h-3.6zM16 9.6h4v3.2h-4z"/>
<path class="brand__rail" d="M1 20.4h24"/>
<circle class="brand__wheel" cx="8" cy="18.2" r="1.6"/>
<circle class="brand__wheel" cx="18" cy="18.2" r="1.6"/>
</svg>
<span class="brand__name">Brassline</span>
</a>
<nav class="nav" aria-label="Main">
<ul class="list" data-list>
<li class="item"><a class="link" href="#" aria-current="page" data-link>Routes</a></li>
<li class="item"><a class="link" href="#" data-link data-autoclic>Tours</a></li>
<li class="item"><a class="link" href="#" data-link data-autoclic>Tickets</a></li>
<li class="item"><a class="link" href="#" data-link data-autoclic>Depot</a></li>
</ul>
</nav>
<a class="cta" href="#" data-cta><span class="cta__label">Book a ride</span></a>
</header>
<canvas class="track" aria-hidden="true" data-track></canvas>
</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.
27 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 tram nav” from Animorio and fit it into my siteThe tram's position is a spring towards the stop, damped at 0.86 with its frequency taken from RIDE_TIME, but its push is capped at 2600/RIDE_TIME² pixels a second squared: from far away the tram does not jump, it gathers speed, runs and brakes, and arrives going just a little too far. The step is 1/120 of a second, never more than 1/30 a frame.
The acceleration becomes the angle the body wants (back when it pulls away, forward when it brakes), chased by a spring with a little damping; the passengers follow the body with another, softer spring, so they lean a moment later. On arrival a push upwards and one sideways make the body sway on its leaf springs.
The pantograph is drawn in world coordinates: its foot follows the tilting roof, its head stays on the wire. The wire is a curve worked out point by point: a small bell-shaped bow where the pantograph presses it, plus the shakes left by the links pointed at, each a sine fading inside a bell. While the tram runs fast the head has a little roughness on a stiff spring.
Above 70 pixels a second, with a chance that grows with speed and SPARKS, sparks fly back from the contact with the wire; when the tram brakes hard they fly up from the wheels. Every spark has a speed, gravity and a life of a few tenths of a second, bounces on the rail losing strength and is drawn as a short line in the direction it flies, white and then orange.
The wheels turn by the distance run over their radius, the glint on the glass slides with the position, the cream front lamp and the red tail lamp swap with the direction, the beam on the rail grows with speed, the bell is two arcs that open and fade. The doors are two leaves that slide with GSAP after the stop and show the warm light inside.
The requestAnimationFrame loop stops when the tram is still, the springs are at rest, there are no sparks or shakes and GSAP is not moving the doors; every event wakes it. The stops are the middles of the links measured with getBoundingClientRect, measured again on resize.
| Name | File | Default | What it changes |
|---|---|---|---|
RIDE_TIME | script.js | 1.2 | About how many seconds the tram takes to ride under a new link. |
SPARKS | script.js | 1 | How many sparks fly from the wire and the brakes (0, none). |
SWAY | script.js | 1 | How much the body leans and sways on its springs (0, it stays level). |
WIRE_PLUCK | script.js | 1 | How much the wire over a link pointed at shakes (0, still). |
DOOR_PAUSE | script.js | 0.9 | How many seconds the doors stay open at the stop (0, they stay shut). |
TRAM_SIZE | script.js | 1 | The size of the tram. |
For the header of anyone dealing with cities and transport: a heritage tram or sightseeing company, a transport museum, a public transport app, a city centre hotel, a city tour, a site for events around town. It suits a few short links; the colours of the bar and the tram change in the variables.
Below 640 pixels the bar is 60 pixels tall, the name and the button go, the small drawn tram stays and the four links spread across the whole width. The tram, the wire and the rail are the same, and a finger down on a link shakes the wire like the mouse.
The tram does not run, lean or throw sparks: choosing another link it fades out where it was and appears again, at rest, under the new link. The wire stays still and the doors shut; the words change weight without animation.
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 10 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.