
The cookie banner
Consent, Transitions, SVG, Physics
NewFree
A full screen menu that arrives like a steel lift: at rest there is only the call button, brushed steel with a ring of light that lights up. Pressed, the car appears and the two speed telescopic doors open, the front panels running twice as far as the ones behind; above, a dial with a needle and the names of the sections for floors. Choosing an item, the doors shut, the needle travels to that section with a bounce and the doors open again.
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 elevator menu</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the elevator menu: at rest only a brushed steel call button with a ring of light. Pressed, the lift arrives:
a steel portal with a floor dial over two speed telescopic doors, which open on the cabin and the menu.
Choosing a section, the doors shut, the needle travels to it on the dial and the doors open again -->
<div class="lift" data-lift>
<!-- the call button: the Menu toggle. Open, it moves up into the portal and closes the doors -->
<button class="call" type="button" aria-expanded="false" aria-controls="lift-menu" data-call data-autoclic>
<span class="call__plate" aria-hidden="true">
<span class="call__ring"></span>
<span class="call__face">
<svg class="call__icon call__icon--open" viewBox="0 0 24 24"><path d="M12 5v14"/><path d="M3.5 12L9 7.5v9zM20.5 12L15 7.5v9z"/></svg>
<svg class="call__icon call__icon--close" viewBox="0 0 24 24"><path d="M12 5v14"/><path d="M9.5 12L4 7.5v9zM14.5 12L20 7.5v9z"/></svg>
</span>
<span class="call__label" data-word>Menu</span>
</span>
<span class="sr" data-name>Open the menu</span>
</button>
<!-- the portal: the dial, the doors, the cabin behind them -->
<div class="car" data-car inert>
<div class="car__head">
<svg class="dial" viewBox="0 0 300 168" aria-hidden="true" data-dial>
<defs>
<radialGradient id="lift-face" cx="0.5" cy="1" r="1">
<stop offset="0" stop-color="#2a2622"/>
<stop offset="0.7" stop-color="#14120f"/>
<stop offset="1" stop-color="#0b0a09"/>
</radialGradient>
<linearGradient id="lift-brass" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#f6dc9c"/>
<stop offset="0.45" stop-color="#c2963f"/>
<stop offset="1" stop-color="#6e4f1a"/>
</linearGradient>
<filter id="lift-glow" x="-40%" y="-80%" width="180%" height="260%">
<feGaussianBlur stdDeviation="2.4" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<path class="dial__rim" d="M4 160A146 146 0 0 1 296 160Z"/>
<path class="dial__face" d="M12 158A138 138 0 0 1 288 158Z" fill="url(#lift-face)"/>
<g class="dial__ticks" data-ticks></g>
<g class="dial__names" data-names></g>
<path class="dial__dir" d="M34 139l8 11 8-11z" data-dir="-1"/>
<path class="dial__dir" d="M250 150l8-11 8 11z" data-dir="1"/>
<g class="dial__needle" data-needle>
<path d="M150 158L147.4 153 150 66 152.6 153Z" fill="url(#lift-brass)"/>
<path d="M150 158l-5 9h10z" fill="#6e4f1a"/>
</g>
<path class="dial__hub" d="M128 160A22 22 0 0 1 172 160Z" fill="url(#lift-brass)"/>
</svg>
<span class="car__slot" aria-hidden="true" data-slot></span>
</div>
<div class="car__opening" data-opening>
<div class="cabin">
<span class="cabin__light" aria-hidden="true"></span>
<span class="cabin__rail" aria-hidden="true"></span>
<nav class="menu" id="lift-menu" aria-label="Main" data-menu>
<ul class="menu__list">
<li><a class="floor" href="#" aria-current="page" data-floor><span class="floor__name">Lobby</span><span class="floor__note">Start here</span></a></li>
<li><a class="floor" href="#" data-floor><span class="floor__name">Work</span><span class="floor__note">Selected projects</span></a></li>
<li><a class="floor" href="#" data-floor><span class="floor__name">Studio</span><span class="floor__note">Who we are</span></a></li>
<li><a class="floor" href="#" data-floor><span class="floor__name">Journal</span><span class="floor__note">Notes and essays</span></a></li>
<li><a class="floor" href="#" data-floor><span class="floor__name">Contact</span><span class="floor__note">Say hello</span></a></li>
</ul>
</nav>
</div>
<!-- two speed telescopic doors: on each side a slow panel and a fast one in front of it -->
<span class="door door--slow door--left" aria-hidden="true" data-door="-1"><span class="door__sheen"></span></span>
<span class="door door--slow door--right" aria-hidden="true" data-door="1"><span class="door__sheen"></span></span>
<span class="door door--fast door--left" aria-hidden="true" data-door="-2"><span class="door__sheen"></span></span>
<span class="door door--fast door--right" aria-hidden="true" data-door="2"><span class="door__sheen"></span></span>
<span class="car__pass" aria-hidden="true" data-pass></span>
</div>
<span class="car__sill" aria-hidden="true"></span>
</div>
<p class="sr" aria-live="polite" data-live></p>
</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.
29 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 elevator menu” from Animorio and fit it into my siteThe doors, the portal and the plate are steel made only of gradients: a base shade along the length and three fine repeating-linear-gradients, light and dark, with steps of 3, 7 and 13 pixels that never repeat together, so the brush lines look irregular. The button’s face is a conic-gradient (turned steel) under a repeating-radial-gradient of rings; its angle is a property registered with @property, so under the pointer the reflection turns in a transition.
The four panels are each a quarter of the opening. A single number goes from 0 (shut) to 1 (open): the back panels move by one panel width times that number, the front ones by two, so in the same time they run twice as far and end up stacked outside the opening, which cuts them with overflow: hidden. The front panels have a shadow on their edge and the rubber where they meet.
Inside each panel (with overflow: hidden) there is a layer with the reflection, as wide as the whole opening and moved left by the panel’s distance from the edge (--px); every frame the script translates it back by as much as the panel moves, with a transform the compositor handles without repainting. So the reflection stays still in the room, as in a real mirror, and the panels slide under it; shut, the four panels show it whole.
The dial is an SVG: the ticks and the names are laid on the arc from the menu items, SWEEP degrees in all, outside the needle’s reach so no name is ever covered. Travelling, a timeline moves the needle’s target from floor to floor in FLOOR seconds per floor, and the needle follows it on a damped spring (STIFF, DAMP) in a loop with steps of 1/30 of a second: it lags a little, goes past the name and comes back. The name under the needle lights up, like the floors of an old indicator.
GSAP plays the three scenes: the arrival (the portal appears and the button flies into its place with a FLIP, measured between its position and that of a placeholder in the portal), the ride (doors shut, arrow lit, a light running over the doors for each floor, the dip on arrival, doors open) and the departure. The menu is inert until the doors are half open, the button has aria-expanded, Esc closes and puts the focus back on the button, and keyboard users find the focus on the current item as soon as the doors open.
| Name | File | Default | What it changes |
|---|---|---|---|
OPEN | script.js | 1.15 | The seconds the doors take to open. |
SHUT | script.js | 0.8 | The seconds they take to shut. |
FLOOR | script.js | 0.4 | The seconds of travel for each floor. |
STIFF | script.js | 120 | How hard the spring pulls the needle to where the lift is. |
DAMP | script.js | 0.3 | How much the needle is held back: low, it bounces more as it arrives. |
SWEEP | script.js | 140 | The width of the dial’s arc, in degrees. |
SHEEN | script.js | 1 | How bright the reflection on the doors is (0 = none). |
For the site of a hotel, an architecture practice, an office building, an agency with a soft spot for period design: wherever opening the menu can be a short ride and steel belongs. The sections must be few (three to six) to fit on the dial. For an everyday site it is too much: the ride takes a few seconds.
The portal takes the whole width and becomes tall, with thinner jambs, a smaller dial and the Close button beside it; the doors stay four panels and the menu fits in the cabin without spilling over. A tap opens, chooses and closes; the ring lights up on the press instead of under the mouse.
Nothing slides: the portal and the button fade in already in place, the doors fade away instead of opening and fade back instead of shutting, the needle jumps to the new floor with no spring and no ride, with no passing light and no dip.
GSAP 3.15.0, Archivo (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 9.6 KB; the libraries: GSAP 28 KB.
Tested on 5 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.