Skip to content
animorio
IT
All components

The vault sign-in

Forms, Physics, SVG, 3DGSAP, CSS onlyAdvancedCompressed weight: Demo 17 KBGSAP 28 KBFreeDownload the zip

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.

The code

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>

Playground

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

Or ask Claude Code for it

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 site

What it does

On a black background a card split in two. On the left, in a wall of brushed steel, a round vault door in its dark frame with seven hex bolts and the hinge on the left: the turned steel rim, under smoked glass a bed grained with perlage like the plate of a watch, the steel ring gear with eight spiral slots, three brass planets on their blued screws, eight polished bolts coming out of the rim into the keepers of the frame, and in the middle the black dial with a hundred marks, a number every ten and the knurled knob, under a brass index. On the right Open your vault, Sign in to your private account., the Email field, the Passphrase field with its Show key and the Forgot it? link, the brass Open vault button and under it the demo passphrase. Every character typed turns the dial by an amount of its own, between 20 and 75 numbers, once right and once left like a real combination: on long turns it runs at a steady speed, then passes its number, swings back and stops clicking from mark to mark; deleting turns it back where it was. Pressing Open vault with an empty field or an incomplete email, a red line with a triangle drops under the field. With the wrong passphrase the dial goes back to zero, the train starts, the bolts draw in a little and hit the relocker, springing back out, the door trembles in its frame, the gap round it lights up red, the passphrase is cleared and the dial spins a whole turn back by itself. With the right one the button’s words become Opening, the dial goes back to zero and turns by 54 degrees: the planets turn, the ring turns by 24 degrees and its slots pull in all eight bolts; a dull knock, then the door, slow to start like a heavy thing, swings open towards you, goes a little past its stop and comes back, while the warm light comes on over the safe deposit boxes inside and the glow spreads on the wall. On the right come Vault open, Welcome back, Mara. (the name taken from the email), the address, the last visit, the box, and the Lock the vault button: the door shuts faster and faster, lands in its frame, the bolts are thrown home and the form comes back empty.

How it works

A real sign-in next to the door

A 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.

The dial, a spring with friction

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.

A planetary train that really meshes

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 slots that pull the bolts

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 in 3D, with its thickness

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.

Values to change

NameFileDefaultWhat it changes
PASSPHRASEscript.jsgoldleafThe passphrase that opens the vault in the demo: it also shows under the button.
SPINscript.js1How far each character turns the dial: 1 is between 20 and 75 numbers.
STIFFNESSscript.js170The dial’s spring: higher reaches its number sooner.
DAMPINGscript.js13The dial’s friction: lower swings past its number and back more.
DETENTscript.js0.75How much the dial clicks from mark to mark as it slows, 0 to 0.95.
BOLTSscript.js0.8The seconds the gear train takes to draw the eight bolts.
SWINGscript.js1.4The seconds the heavy door takes to swing open.
OPEN_ANGLEscript.js74How many degrees the door swings open.
SHAKEscript.js5How many pixels the door trembles with a wrong passphrase.

Try them in the Playground

When to use it

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.

On phones

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.

With reduced motion

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.

Pitfalls

  • With a free spring, a return of three turns would reach thirty turns a second and go a hundred degrees past zero. The pull is capped at 60 degrees: on long turns the dial runs at a steady speed, as if turned by hand, and only the last stretch swings.
  • With three planets the ring cannot mesh with all three for any count of teeth: with 20, 15 and 50 (20 + 50) / 3 is not whole and one planet ended up tooth on tooth. With 24, 15 and 54 the sum 78 divides by three and the ring’s phase, computed from one planet, suits the other two.
  • The door goes over the vault’s side: with the hinge on the right, opening towards you, it would have ended up over the form. The hinge is on the left and the door swings 74 degrees: you see its face with the gears and its thickness at an angle, and nothing leaves the bay.
  • The error line under the passphrase made the card taller and the door, centred in its half, jumped down and up at every wrong try. The card has a minimum height that holds the line; the form and the welcome share a grid cell, the one off with visibility hidden and inert, so the swap does not change the height.
  • The lit vault was a flat sheet of gold that dazzled: the boxes are now a darker brass, with the light coming from above and the bottom fading out, and the top brightness is lower.
  • The welcome title takes the focus when the door opens (for screen readers) and the browser drew the controls’ outline round it: the outline is removed only there, since it is not a control.

Libraries

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.

Testing

Tested on 13 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

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.

All components