
The needle gauge
Counters, Physics, SVG
NewFree
In the minimal style, on white: the sign in form of a computer repair shop, a hairline card with email, password and a Sign in button. In its four corners four small steel cross-head screws sit loose and crooked; every character typed turns one of them a little tighter, on a spring, until with a good email and a long enough password they are all flush, their crosses lined up. Sign in seats the card with a soft click, a light runs over the heads and You’re in arrives; a password that is too short loosens the screws again and the card trembles.
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 login form with tightening screws</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 sign in card of a computer repair shop, on white. Four small cross-head screws sit loose and crooked in its
corners; every character typed turns one of them a little tighter, and once the email and the password are good
all four are flush and lined up. Sign in seats the panel with a soft click; a short password loosens them again -->
<form class="panel" data-panel data-autoclic novalidate>
<span class="screw screw--tl" data-screw aria-hidden="true">
<span class="screw__shadow" data-shadow></span>
<svg class="screw__head" viewBox="0 0 20 20" data-head>
<defs>
<radialGradient id="metal0" cx="35%" cy="30%" r="75%">
<stop offset="0" stop-color="#fbfbfa"/>
<stop offset=".55" stop-color="#cfcfcb"/>
<stop offset="1" stop-color="#9c9c97"/>
</radialGradient>
<clipPath id="cap0"><circle cx="10" cy="10" r="8.4"/></clipPath>
</defs>
<circle cx="10" cy="10" r="8.4" fill="url(#metal0)"/>
<circle cx="10" cy="10" r="8.4" fill="none" stroke="#8d8d88" stroke-width=".7"/>
<circle cx="10" cy="10" r="6.9" fill="none" stroke="#ffffff" stroke-opacity=".55" stroke-width=".5"/>
<g data-slot>
<path d="M10 5.1v9.8M5.1 10h9.8" stroke="#ffffff" stroke-opacity=".8" stroke-width="2.2" stroke-linecap="round" transform="translate(.35 .45)"/>
<path d="M10 5.1v9.8M5.1 10h9.8" stroke="#55554f" stroke-width="1.9" stroke-linecap="round"/>
</g>
<g clip-path="url(#cap0)"><rect class="screw__glint" data-glint x="-14" y="-4" width="5" height="28" fill="#ffffff" opacity="0" transform="rotate(28 10 10)"/></g>
<ellipse cx="7.3" cy="6.4" rx="2.6" ry="1.5" fill="#ffffff" opacity=".7" transform="rotate(-32 7.3 6.4)"/>
</svg>
</span>
<span class="screw screw--tr" data-screw aria-hidden="true">
<span class="screw__shadow" data-shadow></span>
<svg class="screw__head" viewBox="0 0 20 20" data-head>
<defs>
<radialGradient id="metal1" cx="35%" cy="30%" r="75%">
<stop offset="0" stop-color="#fbfbfa"/>
<stop offset=".55" stop-color="#cfcfcb"/>
<stop offset="1" stop-color="#9c9c97"/>
</radialGradient>
<clipPath id="cap1"><circle cx="10" cy="10" r="8.4"/></clipPath>
</defs>
<circle cx="10" cy="10" r="8.4" fill="url(#metal1)"/>
<circle cx="10" cy="10" r="8.4" fill="none" stroke="#8d8d88" stroke-width=".7"/>
<circle cx="10" cy="10" r="6.9" fill="none" stroke="#ffffff" stroke-opacity=".55" stroke-width=".5"/>
<g data-slot>
<path d="M10 5.1v9.8M5.1 10h9.8" stroke="#ffffff" stroke-opacity=".8" stroke-width="2.2" stroke-linecap="round" transform="translate(.35 .45)"/>
<path d="M10 5.1v9.8M5.1 10h9.8" stroke="#55554f" stroke-width="1.9" stroke-linecap="round"/>
</g>
<g clip-path="url(#cap1)"><rect class="screw__glint" data-glint x="-14" y="-4" width="5" height="28" fill="#ffffff" opacity="0" transform="rotate(28 10 10)"/></g>
<ellipse cx="7.3" cy="6.4" rx="2.6" ry="1.5" fill="#ffffff" opacity=".7" transform="rotate(-32 7.3 6.4)"/>
</svg>
</span>
<span class="screw screw--bl" data-screw aria-hidden="true">
<span class="screw__shadow" data-shadow></span>
<svg class="screw__head" viewBox="0 0 20 20" data-head>
<defs>
<radialGradient id="metal2" cx="35%" cy="30%" r="75%">
<stop offset="0" stop-color="#fbfbfa"/>
<stop offset=".55" stop-color="#cfcfcb"/>
<stop offset="1" stop-color="#9c9c97"/>
</radialGradient>
<clipPath id="cap2"><circle cx="10" cy="10" r="8.4"/></clipPath>
</defs>
<circle cx="10" cy="10" r="8.4" fill="url(#metal2)"/>
<circle cx="10" cy="10" r="8.4" fill="none" stroke="#8d8d88" stroke-width=".7"/>
<circle cx="10" cy="10" r="6.9" fill="none" stroke="#ffffff" stroke-opacity=".55" stroke-width=".5"/>
<g data-slot>
<path d="M10 5.1v9.8M5.1 10h9.8" stroke="#ffffff" stroke-opacity=".8" stroke-width="2.2" stroke-linecap="round" transform="translate(.35 .45)"/>
<path d="M10 5.1v9.8M5.1 10h9.8" stroke="#55554f" stroke-width="1.9" stroke-linecap="round"/>
</g>
<g clip-path="url(#cap2)"><rect class="screw__glint" data-glint x="-14" y="-4" width="5" height="28" fill="#ffffff" opacity="0" transform="rotate(28 10 10)"/></g>
<ellipse cx="7.3" cy="6.4" rx="2.6" ry="1.5" fill="#ffffff" opacity=".7" transform="rotate(-32 7.3 6.4)"/>
</svg>
</span>
<span class="screw screw--br" data-screw aria-hidden="true">
<span class="screw__shadow" data-shadow></span>
<svg class="screw__head" viewBox="0 0 20 20" data-head>
<defs>
<radialGradient id="metal3" cx="35%" cy="30%" r="75%">
<stop offset="0" stop-color="#fbfbfa"/>
<stop offset=".55" stop-color="#cfcfcb"/>
<stop offset="1" stop-color="#9c9c97"/>
</radialGradient>
<clipPath id="cap3"><circle cx="10" cy="10" r="8.4"/></clipPath>
</defs>
<circle cx="10" cy="10" r="8.4" fill="url(#metal3)"/>
<circle cx="10" cy="10" r="8.4" fill="none" stroke="#8d8d88" stroke-width=".7"/>
<circle cx="10" cy="10" r="6.9" fill="none" stroke="#ffffff" stroke-opacity=".55" stroke-width=".5"/>
<g data-slot>
<path d="M10 5.1v9.8M5.1 10h9.8" stroke="#ffffff" stroke-opacity=".8" stroke-width="2.2" stroke-linecap="round" transform="translate(.35 .45)"/>
<path d="M10 5.1v9.8M5.1 10h9.8" stroke="#55554f" stroke-width="1.9" stroke-linecap="round"/>
</g>
<g clip-path="url(#cap3)"><rect class="screw__glint" data-glint x="-14" y="-4" width="5" height="28" fill="#ffffff" opacity="0" transform="rotate(28 10 10)"/></g>
<ellipse cx="7.3" cy="6.4" rx="2.6" ry="1.5" fill="#ffffff" opacity=".7" transform="rotate(-32 7.3 6.4)"/>
</svg>
</span>
<div class="panel__body" data-body>
<p class="brand" data-part><span data-brand>Bitfix repairs</span></p>
<h1 class="title" data-part data-title>Sign in</h1>
<p class="sub" data-part data-sub>Check on your repair</p>
<label class="field" data-field="email" data-part>
<span class="field__label">Email</span>
<span class="field__box">
<input class="field__input" type="email" name="email" autocomplete="email" spellcheck="false" data-input="email" placeholder="you@example.com">
</span>
<span class="field__line" aria-hidden="true"><span data-line></span></span>
</label>
<label class="field" data-field="pass" data-part>
<span class="field__label">Password</span>
<span class="field__box">
<input class="field__input" type="password" name="password" autocomplete="current-password" data-input="pass" placeholder="At least 6 characters">
<button class="eye" type="button" data-eye aria-label="Show password" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path class="eye__lid" data-lid d="M2.8 12c2.4-3.9 5.5-5.8 9.2-5.8s6.8 1.9 9.2 5.8c-2.4 3.9-5.5 5.8-9.2 5.8S5.2 15.9 2.8 12Z"/>
<circle class="eye__ball" data-ball cx="12" cy="12" r="2.6"/>
<path class="eye__slash" data-slash d="M4.5 19.5 19.5 4.5"/>
</svg>
</button>
</span>
<span class="field__line" aria-hidden="true"><span data-line></span></span>
<span class="field__msg" data-msg aria-live="polite"></span>
</label>
<button class="go" type="submit" data-go>
<span class="go__bg" aria-hidden="true"></span>
<span class="go__text" data-go-text>Sign in</span>
</button>
</div>
<div class="done" data-done aria-hidden="true">
<svg class="done__tick" viewBox="0 0 40 40" aria-hidden="true">
<circle cx="20" cy="20" r="17" data-ring/>
<path d="M12.5 20.5 18 26l10-11.5" data-tick/>
</svg>
<p class="done__title" data-done-title>You’re in</p>
<p class="done__sub" data-done-sub>Your laptop is on the bench.</p>
</div>
<p class="sr" 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.
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 login form with tightening screws” from Animorio and fit it into my siteEach screw is a blurred disc for the shadow, the SVG head with a radial steel gradient and, inside the head, the group of the cross, which is the only part that turns. The highlight on the head sits outside that group: the light stays still while the screw turns under it, and that is what makes it read as metal.
Each screw has a number p from 0 to 1. At 0 the cross is turned LOOSE degrees anticlockwise (each by its own share), the head is 8% larger and shifted towards the light, the shadow far away; at 1 everything is flush. GSAP takes p to its new value with back.out(2.2), so every turn goes a touch past the mark and settles.
The email is worth 0 to 0.8 counting characters up to sixteen, and 1 only when it is a real address; the password is worth its length over MIN_PASS. Each pair of screws splits its value in half: the first tightens up to 0.5, the second from 0.5 to 1, so they tighten one after the other.
A good Sign in: the card drops 1.2 pixels in 7 hundredths and comes back with back.out(3), every head goes from 0.92 to its size and a white band, clipped to the head’s circle, runs across it. A bad one: every p drops by about 0.4 with power3.out and the card trembles, dying away with elastic.out.
It is a real form with two labelled inputs, the eye button with aria-pressed and an aria-live line that reads the error or the sign in. The preview’s pretend hand stops as soon as you really touch or type. With reduced motion the screws change without turning and the tick fades in.
| Name | File | Default | What it changes |
|---|---|---|---|
BRAND | script.js | Bitfix repairs | The small name of the shop over the title. |
TITLE | script.js | Sign in | The title of the card and the button. |
SUB | script.js | Check on your repair | The line under the title. |
DONE_TITLE | script.js | You’re in | The title once signed in. |
DONE_SUB | script.js | Your laptop is on the bench. | The line once signed in. |
MIN_PASS | script.js | 6 | How many characters the password needs. |
LOOSE | script.js | 150 | How many degrees a loose screw is turned. |
LIFT | script.js | 1.6 | How far a loose screw stands out, in pixels. |
TURN | script.js | 0.42 | The seconds of one turn of a screw. |
SHAKE | script.js | 4 | How far the card trembles when refused, in pixels. |
For the sign in of a support site, a workshop, a repair service or any customer area that wants to say «all fixed» with a hands-on gesture.
The card narrows to 16 pixels from the edges and the vertical spacing is measured on the screen height too, so it fits whole even in a short box. A tap types and presses like the mouse; the screw turning under the pointer happens only with a mouse.
The screws go from loose to tight without turning, the card does not tremble, the fields and the tick fade in.
GSAP 3.15.0, Archivo (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 8.5 KB; the libraries: GSAP 28 KB.
Tested on 24 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.