
The menu under the page
Menus, 3D, Physics, Transitions
NewFree
The search of a record shop: the lens is a small turntable that turns with every letter, and the results are the sleeves in a wooden crate. The ones that do not match sink, the others close up, and the hand flips them forward one after another, each bouncing as it falls. The record picked slides out of its sleeve and flies onto the turntable; when nothing is there the record is scratched and a divider stands up in the crate.
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 record crate search</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the search of a record shop. The lens is a small turntable: typing nudges the record round and the arm
comes over it. The results are the sleeves in a crate: the ones that do not match sink, the others close up,
and the hand flips through them; the record picked slides out of its sleeve and lands on the turntable.
The sleeves are made by the script from the list at the top of script.js -->
<div class="dig" data-dig data-autoclic>
<form class="dig__form" role="search" action="#" novalidate data-form>
<div class="dig__field" data-field>
<svg class="deck" viewBox="0 0 44 44" aria-hidden="true" data-deck>
<rect class="deck__plinth" x="1.5" y="4.5" width="41" height="35" rx="6"/>
<g data-platter>
<circle class="deck__vinyl" cx="18" cy="22" r="14"/>
<circle class="deck__groove" cx="18" cy="22" r="12.4"/>
<circle class="deck__groove" cx="18" cy="22" r="10.6"/>
<circle class="deck__groove" cx="18" cy="22" r="8.8"/>
<circle class="deck__label" cx="18" cy="22" r="5" data-label/>
<path class="deck__half" d="M18 17a5 5 0 0 1 0 10z" data-half/>
</g>
<path class="deck__shine" d="M18 8a14 14 0 0 1 12.1 7l-3.4 2A10 10 0 0 0 18 12z"/>
<circle class="deck__spindle" cx="18" cy="22" r="0.9"/>
<g data-arm>
<circle class="deck__pivot" cx="36" cy="10" r="2.6"/>
<path class="deck__arm" d="M36 10 L36.5 26 L32.6 31.4"/>
<rect class="deck__head" x="30.4" y="30.2" width="4.2" height="2.6" rx="0.6" transform="rotate(-36 32.5 31.5)"/>
</g>
</svg>
<input class="dig__input" id="dig-input" type="text" inputmode="search" enterkeyhint="search" role="combobox" aria-label="Search the records" aria-autocomplete="list" aria-expanded="true" aria-controls="dig-crate" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" placeholder="Search the crates" data-input>
<span class="dig__caret" aria-hidden="true" data-caret></span>
<button class="dig__clear" type="button" aria-label="Clear the search" tabindex="-1" data-clear>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7.5 7.5l9 9M16.5 7.5l-9 9"/></svg>
</button>
</div>
</form>
<div class="dig__shop">
<div class="crate" data-crate>
<div class="crate__bin" id="dig-crate" role="listbox" aria-label="Records" data-bin>
<span class="crate__side crate__side--l" aria-hidden="true"></span>
<span class="crate__side crate__side--r" aria-hidden="true"></span>
<div class="divider" aria-hidden="true" data-divider>
<span class="divider__tab" data-divider-tab></span>
<span class="divider__card">
<span class="divider__small">Nothing under</span>
<span class="divider__word" data-divider-word></span>
</span>
</div>
</div>
<div class="crate__front" aria-hidden="true">
<span class="crate__holder"><span class="crate__tag" data-tag>New in</span></span>
</div>
</div>
<div class="info" data-info>
<div class="info__record" data-record>
<p class="info__meta" data-meta></p>
<p class="info__title" data-title></p>
<p class="info__artist" data-artist></p>
<p class="info__price" data-price></p>
</div>
<div class="info__none" data-none hidden>
<p class="info__try">Try</p>
<div class="info__tries" data-tries></div>
</div>
<button class="listen" type="button" data-listen>
<span class="listen__icon" aria-hidden="true">
<svg class="listen__play" viewBox="0 0 20 20"><path d="M6.5 4.6v10.8a.8.8 0 0 0 1.2.7l8.6-5.4a.8.8 0 0 0 0-1.4L7.7 3.9a.8.8 0 0 0-1.2.7z"/></svg>
<span class="listen__bars" data-bars><i></i><i></i><i></i></span>
</span>
<span class="listen__words" data-listen-words>Listen</span>
</button>
</div>
</div>
<div class="dig__flight" aria-hidden="true" data-flight></div>
<p class="sr" role="status" aria-live="polite" data-said></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.
207 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 record crate search” from Animorio and fit it into my siteEvery sleeve is a div with transform-style preserve-3d and its pivot at the bottom (transform-origin 50% 100%), inside a box with perspective 900px and the eye above the crate (perspective-origin 50% -40%). It has three faces: the cover a pixel in front (translateZ), the back turned with rotateX(180deg) a pixel behind, and the record between them, so the record really is inside the sleeve and comes out without passing over it. The covers are SVGs drawn by the script with the colours in RECORDS; the front of the crate and the label holder sit over everything, and the box cuts only along the bottom, where the sleeves sink.
c says how far the hand has dug: the sleeve in front, with the falls in between. Every sleeve knows its distance d from c: behind (d above zero) it leans back LEAN degrees, rises STEP pixels per place and goes back GAP; between 0 and -1 it falls forward up to FLIP degrees, on a smoothstep curve. c follows its target on a spring (DIG), and every sleeve follows the lean it is due on a spring of its own (BOUNCE), lightly damped: digging all at once the sleeves fall in a cascade and each bounces as it lands. The z-index is worked out from d on every frame, because the fallen sleeves are in front of the standing ones.
search() compares the words regardless of capitals and accents: first the titles that start that way, then those with a word that starts that way, then artist and genre. The sleeves that do not match sink (sink from 0 to 1, that is down by their own height behind the front of the crate), the ones that stay take their new place and slide there on a spring, and the ones coming back rise from the bottom already in their place, one after the other with back.out. The target of c goes back to zero: the best result stands in front.
A drag on the crate (pointer capture, touch-action none) moves c directly, one place every 44 pixels, with a little give past the ends; let go, the finger’s speed, measured and smoothed, decides how far it carries on. A tap that does not move takes the sleeve tapped: the one in front is played, one further back is brought to the front. The arrows and the wheel move the target of c by one place too. The turntable is an SVG: the record turns at a speed every letter adds to and friction brings back to zero, and the arm turns around its pivot with the transform attribute.
The record slides out of the sleeve at the side in 0.42 seconds, while the sleeve tilts towards the hand. Then its box on the screen is measured (getBoundingClientRect, already in perspective) and a copy sets off from it in a layer that is over the field but under the crate, so at first it is still hidden by the cover like the real one. The copy goes left quickly (power3.out) and rises slowly (power2.in), shrinking at once: it goes round the crate and never passes over the words typed. On arrival the turntable’s label takes its colour and the record speeds up to RPM turns a minute.
Everything that moves, springs, sleeves, turntable, is integrated in steps of 1/240 of a second in a single function added to GSAP’s ticker, with the step capped at 1/30. When the springs are still, no tween is due (counting those still waiting for their delay) and no record is playing, the function takes itself off the ticker. A click from a script (detail 0, the library’s preview) goes one step at a time, without taking the focus and with a drawn caret: soul and a sleeve flipped, the record in front on the turntable, polka, which is not there, dub, offered, and the whole crate flipped through and back.
| Name | File | Default | What it changes |
|---|---|---|---|
SLEEVE | script.js | 200 | The side of a sleeve, in pixels (on a phone it shrinks to fit). |
GAP | script.js | 22 | How far apart the sleeves stand in the crate, in depth, in pixels. |
STEP | script.js | 22 | How much of each sleeve further back shows over the one before it, in pixels: the room for the title. |
LEAN | script.js | 12 | How far the standing sleeves lean back, in degrees. |
FLIP | script.js | 72 | How far forward a sleeve falls when it is flipped, in degrees. |
DIG | script.js | [240, 27] | The spring of the hand digging through: stiffness, and damping. |
BOUNCE | script.js | [520, 16] | The spring each falling sleeve lands on: stiffness, and damping (less damping, more bounce). |
RPM | script.js | 33 | How fast the record spins on the turntable, in turns a minute. |
TRIES | script.js | ['soul', 'dub', 'jazz'] | The words offered when nothing in the crate matches. |
RECORDS | script.js | [['Night Ferry', 'The Calloway Five', 'Jazz', 1961, '£28', 'rings', …], …] | The records in the crate, newest first: title, artist, genre, year, price, the cover (a style among rings, sun, waves, bands, letter, arch, blocks and split, paper, ink, accent) and the colour of the label. |
For the search of a record shop, a label, a radio station, a bookshop or a board game shop: wherever the results have a cover and looking for them should feel like digging through the real things. The search() function is the only one to change to ask a server for the results, and play() is the place to start a real audio preview.
The field takes the width of the screen minus 32 pixels and stays 60 tall, with the words at 17 pixels (from 16 up iPhone does not zoom the page when the field is tapped). The crate sits under the field, the sleeves shrunk to 180 pixels so that crate and titles fit, and the description becomes a row under the crate: title and artist on the left, Listen on the right. You flip through by dragging a finger down the crate, with momentum, and tap a sleeve to bring it to the front or to play it.
No falls, flights or bounces: the sleeves are already in place, standing or fallen, and the ones that do not match fade out instead of sinking. The record does not fly: the turntable’s label fades to its colour and the arm is already in the grooves, but the record does not spin and the button’s bars stay still. No scratch and no shaking when nothing matches: the divider fades in. The search, the keys and the touch work the same.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 16 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.