
The inchworm menu
Menus, Hover, Transitions
NewFree
Sign in with Face ID on a black page: in the round end of the pill, inside the four corners of the Face ID frame, a face made only of contour lines, like the map of a hill. The lines come from a height map of a stylised face seen in 3D: the face follows the pointer and the contours slide over the cheeks and crowd on the side that turns away. At a click the face looks straight ahead, a scan line runs down it and the lines take its colour, then they tighten and gather into a tick: Signed in as Noor. Lock opens the tick back into the face.
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 face button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/hubot-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the face button: Sign in with Face ID. In the round end of the pill a face drawn only with contour lines, like a
map: the lines are worked out from a height map of a face seen from where it is turned, so as it follows the
pointer they slide and change. A click sends a scan line down it, the contours tighten and gather into a
tick, and the pill opens on the person signed in -->
<div class="fid" data-fid>
<button class="fid__btn" type="button" data-btn data-autoclic>
<span class="fid__fill" aria-hidden="true" data-fill></span>
<canvas class="fid__sensor" aria-hidden="true" data-scan></canvas>
<span class="fid__faces">
<span class="fid__face" data-face="a">
<span class="fid__word" data-wide="Sign in with Face ID" data-word>Sign in with Face ID</span>
</span>
<span class="fid__face" data-face="b" aria-hidden="true">
<span class="fid__word" data-word></span>
</span>
<span class="fid__face fid__face--done" data-face="done" aria-hidden="true">
<span class="fid__who">
<span class="fid__lead" data-lead-text>Signed in as</span>
<span class="fid__name" data-user-text>Noor</span>
</span>
<span class="fid__lock" data-lock-text>Lock</span>
</span>
</span>
</button>
<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.
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 face button” from Animorio and fit it into my siteThe face is a sum of soft bumps on a low dome: a gaussian for the brow, two hollows for the eyes, a ridge and a tip for the nose with its wings, the cheeks, the lips with the line between them and the chin; outside the ellipse of the head there is no height. RELIEF says how many pixels deep the face is at the height of 1.
On a grid of half a pixel marching squares follows LINES heights, from the edge to the tip of the nose; the segments are chained into polylines through the cell edges they share (a closed ring comes back to its start), and smoothed twice. Every point of every line keeps its height, so the map can be turned in 3D.
Every frame each point of the lines is turned round the upright axis (yaw) and then the crosswise one (pitch), and projected with a light perspective (f = 110 / (110 − z)): the lines slide over the cheeks and crowd on the side moving away. The angle follows the pointer with the tanh of the distance, up to TURN radians, with an exponential chase that gives it weight. With no pointer two slow sines that never repeat make it look about (SWAY); off screen nothing is worked out.
The face turns back to the front in 0.35 seconds and holds still. The scan line comes down in SCAN seconds with sine.inOut, with a gradient band fading upwards behind it; the contours are drawn twice, clipped above and below the line: above in the scan's colour, below in grey. The corners of the frame close in by almost two pixels with back.out.
When the scan ends the contours are frozen as they are. Each line knows the length fraction of every point: an open one is laid on the tick from its left end, a closed one starts from its left-most point and goes along the tick and back, becoming a thin double thread. First the line tightens towards the middle to half its size (power2.inOut), then it settles on the tick (power3.inOut), the outer ones first; at the end the lines fade and a clean tick with round ends remains.
Two faces for a line of words taking turns, plus the face of the person signed in; before every change the tweens still running on the faces are stopped, so two lines never stay on top of each other. It is a real button: aria-disabled while it scans and locks, an aria-live region says Signing in…, Signed in as Noor and Locked. Signed out.; once signed in the aria-label also says Lock. All simulated: no camera is switched on.
| Name | File | Default | What it changes |
|---|---|---|---|
LABEL | script.js | Sign in with Face ID | The words at rest. If they are too long for the room, the type gets smaller. |
BUSY | script.js | Signing in… | The words while it scans and the contours tighten into the tick. |
LEAD | script.js | Signed in as | The small line over the name. |
USER | script.js | Noor | The person signed in. The sign-in is simulated: in a real site this is the name your server returns after WebAuthn. |
LOCK | script.js | Lock | The small pill that signs out. |
LOCKING | script.js | Locking… | The words while the tick opens back into the face. |
LOCKED | script.js | Locked | The words once signed out, before going back to rest. |
SCAN | script.js | 1.1 | The seconds of the scan line. |
LINES | script.js | 7 | How many contour lines, from the edge of the face to the tip of the nose: 4 a bare face, 10 a dense map. |
TURN | script.js | 0.42 | How far the face turns towards the pointer, in radians: 0 it stays still. |
RELIEF | script.js | 1 | How deep the face is: higher, the lines slide more as it turns. |
SWAY | script.js | true | With no pointer the face looks about slowly by itself; false it stays still, facing front. |
For the sign-in button that uses the face or a passkey: a banking app, a password manager, a wallet, a members' area, the confirmation of a payment. It suits dark, technology and security sites. It has to be wired to the WebAuthn API, which asks the system for the face: here it is all simulated and the camera is never switched on.
The pill at rest is about 265 pixels wide and the open one about 215: at 390 pixels they fit with room to spare, and if the words are too long the type gets smaller. The finger sliding over the page is where the face looks; with no finger the face looks about by itself. A tap on the button scans.
The face stays still, facing front, and does not follow the pointer; no lines coming in, no swelling fill or widening words (under the mouse the fill only lightens a little). At a click no line comes down: the contours fade to mint, then fade out and the tick fades in in their place; the pill takes its new width while the words are gone and Signed in as Noor fades in. Lock goes back the same way.
GSAP 3.15.0, Hubot Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 12 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.