Skip to content
animorio
IT
All components

The record crate search

Search, 3D, Physics, TransitionsCSS only, GSAPAdvancedCompressed weight: Demo 16 KBGSAP 28 KBFreeDownload the zip

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.

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

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.

207 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 record crate search” from Animorio and fit it into my site

What it does

On a black background a dark field with rounded corners: on the left, in the lens’s place, a turntable seen from above, with the black record, the grey label and the arm at rest, and next to it the words Search the crates. Below, a wooden crate with the record sleeves standing in it, leaning back a little: the one in front shows its whole cover, drawn (rings, stripes, a sun, waves, a huge letter), and of the ones behind it you see the top, with the title, each a little deeper in the shade. On the front of the crate, in a metal label holder, a card says New in. On the right of the crate the record in front is described: genre and year, the title in large type, the artist, the price, and a Listen button. Typing, every letter gives the turntable’s record a push, and it turns and slows down by itself, and the arm swings over the record’s edge. The sleeves that do not match sink into the crate, the ones that stay close up sliding into place, and the ones coming back rise from the bottom one after another with a small bounce; the card turns over and says how many records there are, for example 5 records. Titles that start that way come first, then those with a word that starts that way, then the artist and the genre. The down and up arrows, the wheel, a drag down the crate or a tap on a sleeve further back flip through the sleeves: the one in front falls forward over the edge, and the next one is left in front; flipping many quickly they fall in a cascade, each bouncing a little as it lands, and a drag let go carries on with the finger’s momentum. With the pointer over it, the sleeve in front tilts towards the hand and the ones behind come out of the shade. Listen, Enter or a tap on the sleeve in front take the record: it slides out of the sleeve at the side, flies to the turntable shrinking and spinning, and lands on it; the label takes its colour, the arm drops into the grooves, the record gets up to speed and the button turns orange and says Playing, with three moving bars. When nothing matches the record on the turntable is scratched back and forth, the arm jumps, the field shakes, all the sleeves sink and a cardboard divider stands up in the crate, its tab carrying the initial and the words Nothing under with the words typed; the card says Not filed and next to the crate three words to try appear: soul, dub and jazz. Esc stops the record or empties the field.

How it works

A 3D crate in CSS

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

One number for the whole crate

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.

Sinking and rising

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.

Hands in the crate

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.

From the sleeve to the turntable

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.

One ticker, which stops

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.

Values to change

NameFileDefaultWhat it changes
SLEEVEscript.js200The side of a sleeve, in pixels (on a phone it shrinks to fit).
GAPscript.js22How far apart the sleeves stand in the crate, in depth, in pixels.
STEPscript.js22How much of each sleeve further back shows over the one before it, in pixels: the room for the title.
LEANscript.js12How far the standing sleeves lean back, in degrees.
FLIPscript.js72How far forward a sleeve falls when it is flipped, in degrees.
DIGscript.js[240, 27]The spring of the hand digging through: stiffness, and damping.
BOUNCEscript.js[520, 16]The spring each falling sleeve lands on: stiffness, and damping (less damping, more bounce).
RPMscript.js33How fast the record spins on the turntable, in turns a minute.
TRIESscript.js['soul', 'dub', 'jazz']The words offered when nothing in the crate matches.
RECORDSscript.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.

Try them in the Playground

When to use it

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.

On phones

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.

With reduced motion

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.

Pitfalls

  • The sleeves only showed after a click: the ticker stopped as soon as everything was still, and the entrance tweens, still in their delay, are not active for gsap.isTweening. Now tweens that are due count too (getTweensOf with progress below 1).
  • Typing polka fast, the sleeves that matched po stayed standing even though nothing matched: their rise, still in its delay, is not stopped by the sinking tween’s overwrite: 'auto', and set off later. Now every old tween is stopped with killTweensOf before each sink tween.
  • Of the sleeves behind only the edges showed, thin as a thread: perspective alone was not enough. Every sleeve behind now rises STEP pixels per place, enough for the title, and the artist sits lower on the cover so it does not peep out halfway.
  • The record coming out of the sleeve passed over the cover: a child without a transform, in a preserve-3d sleeve, was drawn in page order. The cover now sits at translateZ(1px) and the back at -1px, with the record between them.
  • Coming out of the top, the record covered the titles of the sleeves behind, and when the flying copy set off the part hidden by the cover appeared at once. Now it comes out at the side, the flight is under the crate (and over the field) and the crate is wider than its box, so the record is not cut.
  • With the wider crate, its empty room covered the description and the Listen button could not be pressed: now the crate lets the pointer through (pointer-events none) and only the sleeves take it, while the drag is listened for on the crate’s box.
  • The pointer passing over raised the sleeve under it, which covered the title of the one behind: now the one in front tilts towards the hand and the ones behind only come out of the shade, without moving.
  • The cover’s huge letter ended up under the front of the crate and the B looked like a D: now it sits higher and smaller.

Libraries

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.

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