
The mechanical key button
Buttons, 3D, Physics, SVG
NewFree
The sign-in of a private bank as a round vault door with its movement under smoked glass: every character of the passphrase turns the combination dial, which runs, overshoots its number, swings back and clicks from mark to mark. With the right passphrase the dial goes back to zero and drives a planetary gear train that draws the eight bolts, then the heavy door swings open in 3D on its hinge, with its thickness, and the vault lights up inside. With a wrong one the bolts hit the relocker and the door trembles in its frame.
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 vault sign-in</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@5.3.0/opsz.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/hanken-grotesk@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the sign-in of a private bank as a round vault door under glass: every character of the passphrase turns the
combination dial, the right one draws the eight bolts through a planetary gear train and swings the door
open, a wrong one makes the door tremble on its locked bolts. The sign-in is pretended: see signIn() in
the script -->
<form class="vault" action="#" novalidate aria-labelledby="vault-title" data-vault data-autoclic>
<div class="wall" aria-hidden="true">
<span class="wall__spill" data-spill></span>
<div class="bay" data-bay>
<div class="inside" data-inside>
<div class="inside__boxes" data-boxes></div>
<span class="inside__lamp" data-lamp></span>
</div>
<svg class="frame" viewBox="-128 -128 256 256" data-frame></svg>
<div class="door" data-door>
<div class="door__face" data-face>
<span class="plate"></span>
<svg class="mech" viewBox="-128 -128 256 256" data-mech></svg>
<span class="glass" data-glass></span>
<span class="collar"></span>
<div class="dial" data-dial>
<svg class="dial__ring" viewBox="-40 -40 80 80" data-ring></svg>
<span class="dial__knob"></span>
</div>
<svg class="index" viewBox="-6 -6 12 12"><path d="M-3.4 -3.2h6.8l-3.4 5.6z"/></svg>
</div>
</div>
<svg class="hinge" viewBox="-128 -128 256 256" data-hinge></svg>
<span class="bay__alarm" data-alarm></span>
</div>
</div>
<div class="side">
<div class="panel" data-panel>
<div class="head">
<h2 class="head__title" id="vault-title">Open your vault</h2>
<p class="head__sub">Sign in to your private account.</p>
</div>
<div class="field" data-field="email">
<label class="field__label" for="vault-email">Email</label>
<div class="box" data-box>
<input class="box__input" id="vault-email" name="email" type="email" inputmode="email" autocomplete="username" autocapitalize="off" autocorrect="off" spellcheck="false" enterkeyhint="next" placeholder="you@example.com" aria-describedby="vault-email-msg" aria-invalid="false" data-input>
</div>
<div class="field__msg" id="vault-email-msg" data-msg></div>
</div>
<div class="field" data-field="pass">
<div class="field__top">
<label class="field__label" for="vault-pass">Passphrase</label>
<a class="field__link" href="#">Forgot it?</a>
</div>
<div class="box box--pass" data-box>
<input class="box__input" id="vault-pass" name="passphrase" type="password" autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" enterkeyhint="go" aria-describedby="vault-pass-msg vault-pass-dial" aria-invalid="false" data-input>
<button class="reveal" type="button" aria-controls="vault-pass" aria-pressed="false" data-reveal>Show</button>
</div>
<p class="sr" id="vault-pass-dial">Every character turns the dial of the vault door.</p>
<div class="field__msg" id="vault-pass-msg" data-msg></div>
</div>
<button class="open" type="submit" data-open>
<span class="open__bg" aria-hidden="true"></span>
<svg class="open__dial" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="7.2"/><path d="M10 2.8v3.4M10 13.8v3.4M2.8 10h3.4M13.8 10h3.4"/><circle cx="10" cy="10" r="2.2"/></svg>
<span class="open__label" data-open-label>Open vault</span>
</button>
<p class="hint">Demo passphrase · <span class="hint__word" data-hint></span></p>
</div>
<section class="welcome is-gone" aria-labelledby="vault-welcome-title" inert data-welcome>
<p class="welcome__kicker"><svg viewBox="0 0 20 20" aria-hidden="true"><rect x="4" y="9" width="12" height="8.4" rx="2"/><path d="M7 9V6.6a3 3 0 0 1 5.8-1.1"/></svg>Vault open</p>
<h2 class="welcome__title" id="vault-welcome-title" tabindex="-1" data-welcome-title>Welcome back.</h2>
<dl class="welcome__facts">
<div><dt>Signed in as</dt><dd data-out="email"></dd></div>
<div><dt>Last visit</dt><dd>Tuesday at 9:41 pm</dd></div>
<div><dt>Safe deposit box</dt><dd>Row F, number 214</dd></div>
</dl>
<button class="lock" type="button" data-lock><span class="lock__bg" aria-hidden="true"></span><span class="lock__label">Lock the vault</span></button>
</section>
</div>
<p class="sr" role="status" aria-live="polite" data-live></p>
</form>
</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.
41 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 vault sign-in” from Animorio and fit it into my siteA form with two real inputs (autocomplete username and current-password, inputmode email, text at 16 pixels so Safari on phones does not zoom), a Show key with aria-pressed that changes the field’s type, aria-invalid and aria-describedby pointing at the line under each field and an aria-live region saying what happens. The door is all aria-hidden: it tells what the form does. The check is pretended in one function, signIn(), which compares with PASSPHRASE: that is the one to replace with the real call.
Every character is worth between 20 and 75 numbers (from its code), once one way and once the other: the dial’s target is the sum, so it is a function of the text and deleting turns it back by itself. On every frame of GSAP’s ticker a damped spring takes it towards the target, with the time step capped at 1/30 of a second; the spring’s pull is capped at 60 degrees, so a long turn runs at a steady speed and only the last stretch swings. The click from mark to mark is in the drawing: the angle shown is a − k·sin(2πa/3.6)·3.6/2π, which stays monotonic for k under 1 and lingers on each of the hundred marks as the dial slows. At rest, the loop leaves the ticker.
The gears are drawn in SVG by one function (teeth with flanks and arcs at tip and root, module 2.6), the internal ring gear too. A 24 tooth sun under the dial, three 15 tooth planets on fixed axles, a 54 tooth ring: (24 + 54) / 3 is whole, so the three planets mesh at once. The phase of each planet and of the ring is computed from the sun’s angle so the teeth fall into the gaps: planet = θ + 180 − 180/15 + (24/15)(θ − sun), the ring from the first planet with the ratio 15/54. Turning the sun by 54 degrees turns the ring by 24 the other way.
The ring has eight spiral slots, each 24 degrees long, with the radius dropping by 8.5 units: each bolt’s pin sits on its own line and the ring, turning, pulls it towards the centre. So the bolts move in proportion to the sun’s turn, and with a back.out or an elastic.out on the train the bolts bounce too: that is how on a wrong try they hit the relocker and come back out.
The door is an element with transform-style preserve-3d inside a bay with perspective, pivoting on the hinge with transform-origin; behind its face nine steel discs moved with translateZ make the thickness, the first ones lighter like a turned edge, and the last one is the back. GSAP turns it with rotationY: power2.inOut for a heavy thing, four degrees past its stop and then back. The vault behind lights up with a CSS variable --lit that GSAP animates inside a brightness filter, and the warmth on the wall is a layer in mix-blend-mode screen.
| Name | File | Default | What it changes |
|---|---|---|---|
PASSPHRASE | script.js | goldleaf | The passphrase that opens the vault in the demo: it also shows under the button. |
SPIN | script.js | 1 | How far each character turns the dial: 1 is between 20 and 75 numbers. |
STIFFNESS | script.js | 170 | The dial’s spring: higher reaches its number sooner. |
DAMPING | script.js | 13 | The dial’s friction: lower swings past its number and back more. |
DETENT | script.js | 0.75 | How much the dial clicks from mark to mark as it slows, 0 to 0.95. |
BOLTS | script.js | 0.8 | The seconds the gear train takes to draw the eight bolts. |
SWING | script.js | 1.4 | The seconds the heavy door takes to swing open. |
OPEN_ANGLE | script.js | 74 | How many degrees the door swings open. |
SHAKE | script.js | 5 | How many pixels the door trembles with a wrong passphrase. |
In the sign-in of a bank, a private account, a document archive or a password manager, where feeling safe is part of the product and the moment you get in should be remembered. The dial answering every character says the field is listening without showing the passphrase; the trembling door says no without a harsh message. For an everyday sign-in keep the dial and the tremble and shorten the opening.
Under 760 pixels the door goes above the form, up to 300 pixels wide, and the card takes the width of the screen without spilling out. Fields are 52 tall, buttons 54, the Show key 36: all easy for a finger. The text is at 16 pixels, the email opens the keyboard with the @ and the Enter key says Next in the email and Go in the passphrase. The open door stays inside its bay, towards the vault. The glass reflection follows the pointer only where there is a mouse.
The dial does not spin: it goes straight to its number. The gears and the bolts take their new place without moving. With a wrong passphrase the door does not tremble: only the red gap round it fades in and out. With the right one the door does not swing in 3D but fades away, while the vault lights up and the form gives way to the welcome with a fade; Lock the vault does the same backwards.
GSAP 3.15.0, Newsreader (font variabile) 5.3.0, Hanken Grotesk (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 17 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.