
The star rating
Ratings, SVG, Hover
NewFree
In the rich style: the Menu button of a music bar is the top of a fifties jukebox; pressed, the whole jukebox opens under it, the glass slides up and the records stand in their rack. Every menu item is a title strip: choosing it, the mechanical arm takes its record, lays it on the turntable, the record spins, the needle drops and notes rise from the record.
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 jukebox menu</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 menu of a music bar: the Menu button is the top of a fifties jukebox; pressed, the whole jukebox opens
under it, the glass slides up and the records stand in their rack. Pointing at a title strip lifts its record;
choosing it, the mechanical arm takes the record to the turntable, the record spins, the needle drops and
notes come out of the speaker grille. -->
<nav class="juke" aria-label="Main" data-jukebox>
<button class="juke-btn" type="button" aria-expanded="false" aria-controls="juke-cabinet" data-toggle>
<svg class="juke-btn__icon" viewBox="0 0 44 44" aria-hidden="true">
<defs>
<linearGradient id="btn-chrome" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ffffff"/>
<stop offset="0.45" stop-color="#9aa3ad"/>
<stop offset="0.55" stop-color="#e9edf1"/>
<stop offset="1" stop-color="#6f7882"/>
</linearGradient>
<clipPath id="btn-arch"><path d="M7 41 V 20 A 15 15 0 0 1 37 20 V 41 Z"/></clipPath>
</defs>
<path class="juke-btn__body" d="M7 41 V 20 A 15 15 0 0 1 37 20 V 41 Z"/>
<g clip-path="url(#btn-arch)">
<path class="juke-btn__band juke-btn__band--red" d="M7 41 V 20 A 15 15 0 0 1 37 20 V 41 H 33.5 V 20 A 11.5 11.5 0 0 0 10.5 20 V 41 Z"/>
<path class="juke-btn__band juke-btn__band--cream" d="M10.5 41 V 20 A 11.5 11.5 0 0 1 33.5 20 V 41 H 31 V 20 A 9 9 0 0 0 13 20 V 41 Z"/>
<path class="juke-btn__glass" d="M13 30 V 20 A 9 9 0 0 1 31 20 V 30 Z"/>
<g class="juke-btn__disc">
<circle cx="22" cy="24" r="6.4" class="juke-btn__vinyl"/>
<circle cx="22" cy="24" r="2.4" class="juke-btn__label"/>
</g>
<rect class="juke-btn__strips" x="13" y="30" width="18" height="3.4" rx="0.6"/>
<g class="juke-btn__grille">
<rect x="13" y="34.6" width="18" height="6.4" rx="1"/>
<path d="M15.5 34.6 V 41 M18.5 34.6 V 41 M21.5 34.6 V 41 M24.5 34.6 V 41 M27.5 34.6 V 41"/>
</g>
<rect class="juke-btn__sweep" x="-12" y="0" width="7" height="44" transform="skewX(-18)"/>
</g>
<path class="juke-btn__rim" d="M7 41 V 20 A 15 15 0 0 1 37 20 V 41"/>
</svg>
<span class="juke-btn__word" data-word>Menu</span>
</button>
<div class="cabinet" id="juke-cabinet" data-cabinet inert>
<div class="cabinet__body" data-body>
<!-- the arch around the window: three bands of coloured plastic with light running through them -->
<div class="arch">
<div class="arch__band arch__band--red"><span class="arch__light" data-light></span></div>
<div class="arch__band arch__band--cream"><span class="arch__light" data-light></span></div>
<div class="arch__band arch__band--mint"><span class="arch__light" data-light></span></div>
<div class="window" data-window>
<svg class="mech" viewBox="-20 0 440 200" preserveAspectRatio="xMidYMid meet" aria-hidden="true" data-mech>
<defs>
<linearGradient id="chrome-h" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fbfdff"/>
<stop offset="0.38" stop-color="#a7b0ba"/>
<stop offset="0.52" stop-color="#eef2f6"/>
<stop offset="1" stop-color="#5d6670"/>
</linearGradient>
<linearGradient id="chrome-v" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#5d6670"/>
<stop offset="0.4" stop-color="#eef2f6"/>
<stop offset="0.6" stop-color="#a7b0ba"/>
<stop offset="1" stop-color="#fbfdff"/>
</linearGradient>
<radialGradient id="vinyl" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#1b1a1d"/>
<stop offset="0.8" stop-color="#0c0b0d"/>
<stop offset="1" stop-color="#232127"/>
</radialGradient>
<linearGradient id="sheen" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#ffffff" stop-opacity="0"/>
<stop offset="0.5" stop-color="#ffffff" stop-opacity="0.28"/>
<stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
</linearGradient>
<radialGradient id="felt" cx="0.5" cy="0.4" r="0.6">
<stop offset="0" class="stop-cherry-dark"/>
<stop offset="1" stop-color="#2a070b"/>
</radialGradient>
<linearGradient id="plinth" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2a2427"/>
<stop offset="1" stop-color="#0d0b0c"/>
</linearGradient>
<linearGradient id="plinth-top" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#3a3236"/>
<stop offset="0.6" stop-color="#141113"/>
<stop offset="1" stop-color="#241e21"/>
</linearGradient>
<linearGradient id="tray" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="stop-cherry"/>
<stop offset="1" class="stop-cherry-dark"/>
</linearGradient>
<clipPath id="disc-clip"><circle r="30"/></clipPath>
</defs>
<!-- the turntable: a lacquered drum with a chrome band, the platter with its felt mat and chrome rim, the spindle -->
<g class="table" transform="translate(22 0)">
<ellipse class="table__shadow" cx="318" cy="170" rx="66" ry="12"/>
<path class="table__side" d="M258 150 V 162 A 60 15 0 0 0 378 162 V 150 Z" fill="url(#plinth)"/>
<path class="table__band" d="M258 157 A 60 15 0 0 0 378 157"/>
<ellipse class="table__top" cx="318" cy="150" rx="60" ry="15" fill="url(#plinth-top)"/>
<path class="table__edge" d="M258 150 A 60 15 0 0 0 378 150"/>
<ellipse class="table__rim" cx="318" cy="148" rx="52" ry="12.8"/>
<ellipse class="table__mat" cx="318" cy="147.4" rx="49.4" ry="11.8" fill="url(#felt)"/>
<path class="table__mat-rings" d="M276 147.4 A 42 9.8 0 0 1 360 147.4 M286 147.4 A 32 7.4 0 0 1 350 147.4"/>
<rect class="table__spindle" x="316.8" y="139" width="2.4" height="8.6" rx="1"/>
</g>
<!-- the rack: chrome dividers behind, the records standing in it, the lacquered tray in front -->
<g class="rack">
<g class="rack__comb" data-comb></g>
</g>
<g class="records" data-records></g>
<g class="tray">
<rect class="tray__front" x="4" y="126" width="266" height="22" rx="3" fill="url(#tray)"/>
<rect x="4" y="123" width="266" height="4.6" rx="2" fill="url(#chrome-h)"/>
<path class="tray__line" d="M12 139 H 262"/>
<path class="tray__shine" d="M14 130 H 120"/>
<path class="tray__shadow" d="M6 150 H 268"/>
<g data-tabs></g>
</g>
<!-- the tonearm: a chrome post, the arm hinged on it, the headshell with the needle -->
<g class="arm" data-tonearm>
<g class="arm__swing" data-swing>
<path class="arm__tube" d="M0 0 L 30 -2.4"/>
<path class="arm__tube-shine" d="M0 -1 L 30 -3.2"/>
<g class="arm__head" data-head>
<path class="arm__shell" d="M28 -6 L 40 -5 L 40 1.4 L 28 1 Z"/>
<path class="arm__needle" d="M37 1.2 L 37.6 4.6"/>
</g>
<rect class="arm__weight" x="-11" y="-4.6" width="8" height="7.4" rx="1.6" fill="url(#chrome-v)"/>
</g>
<ellipse class="arm__base" cx="0" cy="3" rx="7" ry="2.6" fill="url(#chrome-h)"/>
<rect class="arm__post" x="-3" y="-3" width="6" height="7" rx="1.4" fill="url(#chrome-v)"/>
<ellipse class="arm__cap" cx="0" cy="-3" rx="3" ry="1.2"/>
</g>
<!-- the gantry: a chrome rail across the top, a carriage that runs on it, a rod that drops, a claw -->
<g class="gantry">
<rect class="gantry__rail" x="-6" y="13" width="412" height="5.2" rx="2" fill="url(#chrome-h)"/>
<rect class="gantry__bracket" x="-8" y="6" width="5" height="16" rx="1.4" fill="url(#chrome-v)"/>
<rect class="gantry__bracket" x="403" y="6" width="5" height="16" rx="1.4" fill="url(#chrome-v)"/>
<g data-carriage>
<rect class="gantry__rod" x="-1.8" y="22" width="3.6" height="10" fill="url(#chrome-v)" data-rod/>
<g data-claw>
<path class="gantry__finger" d="M0 0 C -6 1 -9 5 -8.4 10" data-finger-l/>
<path class="gantry__finger" d="M0 0 C 6 1 9 5 8.4 10" data-finger-r/>
<rect class="gantry__knuckle" x="-4" y="-2.6" width="8" height="4.4" rx="1.4" fill="url(#chrome-h)"/>
</g>
<rect class="gantry__car" x="-13" y="10" width="26" height="12" rx="3" fill="url(#chrome-h)"/>
<rect class="gantry__lip" x="-13" y="19" width="26" height="1.6"/>
</g>
</g>
</svg>
<!-- the glass in front of the records: it slides up into the arch when the jukebox opens -->
<div class="window__glass" data-glass></div>
</div>
</div>
<!-- the title strips: the real links of the menu, one for every record -->
<ul class="strips" data-strips>
<li><a class="strip" href="#menu" data-item style="--tone: var(--cherry)"><span class="strip__band"></span><span class="strip__word">Menu</span></a></li>
<li><a class="strip" href="#drinks" data-item style="--tone: var(--mint)"><span class="strip__band"></span><span class="strip__word">Drinks</span></a></li>
<li><a class="strip" href="#live" data-item style="--tone: var(--gold)"><span class="strip__band"></span><span class="strip__word">Live music</span></a></li>
<li><a class="strip" href="#book" data-item style="--tone: var(--sky)"><span class="strip__band"></span><span class="strip__word">Book a table</span></a></li>
<li><a class="strip" href="#find" data-item style="--tone: var(--tangerine)"><span class="strip__band"></span><span class="strip__word">Find us</span></a></li>
</ul>
<!-- the speaker grille, with the chrome plate that says what is playing -->
<div class="grille" data-grille>
<span class="grille__plate"><span class="grille__brand">Jive</span><span class="grille__now" data-now aria-live="polite"></span></span>
</div>
</div>
<svg class="notes" aria-hidden="true" data-notes></svg>
</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.
24 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 jukebox menu” from Animorio and fit it into my siteEverything that moves inside the jukebox is a 400 by 200 SVG drawn again on every frame from a few numbers: where the carriage is on the rail, how long the rod is, how closed the claw is, the angle of the tonearm, and for every record its position, how far it lies down and how far it has turned. GSAP animates only those numbers; one function turns them into transform attributes.
While a record is in the claw its position is not animated: it is worked out from the claw. Its centre sits one radius under the fingertips, times how far the record lies down; so, while the rod comes down over the platter and the record goes from upright (vertical scale 1) to lying down (0.3), its edge stays held between the fingers and the record lands on the felt exactly where it should.
Every record has two groups: the one that turns (vinyl, grooves, label with its print and hole) and on top, still, two opposite wedges of light clipped to the circle. When the record spins the print on the label runs round and the light stays put, like on a real record. Lying down, the whole record is squashed vertically and the turn becomes a turn on an ellipse.
The arch is three bands of plastic one inside the other with the same curve; through each one runs a stripe of light, a repeating gradient moved by background-position by exactly one period, so the loop never jumps. The glass is a sheet of reflections that slides up and vanishes into the arch when it opens; the jukebox unrolls from the top with a clip-path and settles with an elastic.
The items are real links, the title strips, reachable with the Tab key and the arrows; the button has aria-expanded, the closed jukebox is inert, Esc closes and gives the focus back to the button, and the plate on the grille says the title in an aria-live region. While the record plays, now and then a note is born (an eighth note or two joined ones) drawn in an SVG over the jukebox: it starts from the record on the platter (the point is turned from the mechanism’s coordinates into the page’s with getScreenCTM), rises swaying in the dark behind the glass, where it covers no words, tilts and fades.
| Name | File | Default | What it changes |
|---|---|---|---|
SPEED | script.js | 1 | How fast the mechanism moves (1 is normal). |
RPM | script.js | 45 | The turns a minute of the record on the turntable. |
NOTES | script.js | 2.2 | The notes a second rising from the record. |
LIGHTS | script.js | 1 | How fast the light runs through the arch. |
LIFT | script.js | 9 | How far a record rises when its title strip is pointed at. |
BOUNCE | script.js | 1.6 | The bounce of the records when they drop into the rack. |
For the menu of the site of a music bar, a diner, a venue with live music, a record shop or a radio: the items of the night (menu, drinks, gigs, bookings, where we are). From three to six items, with short titles like on real title strips.
The jukebox is as wide as the screen and at most as tall as the room left under the button; the mechanism is an SVG that fits itself and the title strips get shorter. Tapping a strip chooses it straight away: the record lifts only with a mouse or the keyboard.
The jukebox fades in already open, with no running lights; choosing an item the record fades in already on the platter with the needle down, still, and the plate says the title. No moving claw, no spinning, no notes.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 13 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.