
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
A phone’s storage being optimized like an old defragmenter: a grid of blocks, one per chunk of a file, with system, apps, photos and files scattered among free gaps and hatched cache. Press Optimize and the cache goes in a wave, then every block is lifted, grows, flies in an arc to its place and lands with a squash, while the one in the way flies the other way. The blocks in place take their full colour and the grid fills like a progress bar, kind after kind. A file that is open will not move and stays up in red until Retry; at the end a ripple crosses the grid, and Optimize again sends every block back where it came from.
A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>The storage defragmenter</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/martian-mono@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the storage defragmenter: a phone's storage as a grid of blocks, one per file chunk, scattered with gaps and
cache. Optimize clears the cache in a wave, then every block is lifted and flown, in an arc, to its place:
system, apps, photos, documents, and the free space all in one piece at the end. The grid is drawn by script.js -->
<section class="df" aria-labelledby="df-title" data-defrag>
<div class="df__head">
<div class="df__words">
<h2 class="df__title" id="df-title"><span class="df__swap" data-title>Optimize storage</span></h2>
<p class="df__meta"><span class="df__swap" data-meta>128 GB · 115 fragments</span></p>
</div>
<p class="df__figure" aria-hidden="true">
<span class="df__pct" data-pct>0%</span>
<svg class="df__tick" viewBox="0 0 28 28" data-tick><path d="M6.5 14.8l5 5 10-11.4" pathLength="1"/></svg>
</p>
</div>
<div class="df__grid" role="progressbar" aria-labelledby="df-title" aria-valuemin="0" aria-valuemax="100"
aria-valuenow="0" aria-valuetext="Not started" data-bar>
<canvas class="df__canvas" aria-hidden="true" data-grid></canvas>
</div>
<p class="df__legend" aria-label="What is stored">
<span class="df__kind" style="--kind: var(--system)"><span>System</span> <b data-size="system">11 GB</b></span>
<span class="df__kind" style="--kind: var(--apps)"><span>Apps</span> <b data-size="apps">23 GB</b></span>
<span class="df__kind" style="--kind: var(--photos)"><span>Photos</span> <b data-size="photos">35 GB</b></span>
<span class="df__kind" style="--kind: var(--docs)"><span>Files</span> <b data-size="docs">15 GB</b></span>
<span class="df__kind df__kind--cache" style="--kind: var(--cache-ink)"><span>Cache</span> <b data-size="cache">2.4 GB</b></span>
</p>
<div class="df__foot">
<span class="df__status"><span class="df__swap" data-status>Last optimized 3 months ago</span></span>
<button class="pill" type="button" data-btn data-autoclic>
<span class="pill__fill" aria-hidden="true"></span>
<span class="pill__label" data-label>Optimize</span>
</button>
</div>
<p class="sr" role="status" aria-live="polite" data-live></p>
</section>
</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.
53 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 storage defragmenter” from Animorio and fit it into my siteThe blocks of each kind (how many is MIX) and the empty places are shuffled with a seeded pseudo-random generator (Park and Miller’s multiplicative one), so the mess is always the same and the preview’s round repeats exactly. Two passes swap a few lonely blocks with a matching neighbour: a real disk’s mess has small clumps, it is not pure noise. Every block remembers where it started, which is where Optimize again sends it.
Like a real defragmenter, the work is worked out first on a copy of the grid: the cache is cleared first, then for every place in turn the script checks which kind belongs there (system, apps, photos, files); if the right block is already there it stays, if not the nearest one after that place is taken and the block in the way goes where that one was. Every operation has a weight (a flight 1, a clearing 0.16, a block already in place 0.12) and so its moment in the work, from 0 to 1.
While it works a clock moves on in GSAP’s ticker (its step is at most 1/30 of a second) and starts every operation as it passes its moment: the percentage is the clock itself. Pause stops the clock and lets the flights in the air finish; the open file is the first flight after FAIL, which holds the clock until Retry lets it go.
A block flies along the straight line between the two places plus a push to one side, sin(πt) times a height that grows with the distance, and the two blocks of a swap bend to opposite sides, so they cross without covering each other. At the top of the arc the block is bigger by LIFT and has a widening shadow; landing it squashes in width and height with an elastic.out and settles back. Positions live in cells, not pixels, so if the window changes size halfway through a flight the drawing is redone and nothing jumps.
Everything is one canvas twelve pixels bigger than the grid on every side, so a block in the air is never cut: first the hairline places, then the blocks on the ground, then those in the air from the lowest to the highest, then the frame. The paleness of a block out of place is its transparency, and the cache is hatched through a clip. When nothing moves and the work is still the loop leaves the ticker and draws nothing.
| Name | File | Default | What it changes |
|---|---|---|---|
GRID | script.js | [24, 10] | Columns and rows of the grid of blocks. |
MIX | script.js | { system: 18, apps: 38, photos: 62, docs: 30, cache: 28 } | How many blocks of each kind; the rest of the grid is free space (at least a fifth stays free). |
CACHE_GB | script.js | 2.4 | How much the cache that gets cleared weighs. |
SECONDS | script.js | 7 | How many seconds an optimization takes in the demo. |
GAP | script.js | 2 | The gap between two blocks, in pixels. |
ROUND | script.js | 0.22 | A block’s round corner, as a share of its side. |
LIFT | script.js | 0.55 | How much a block grows at the top of its flight. |
ARC | script.js | 0.32 | How high a flight arcs, as a share of its length. |
FLIGHT | script.js | 0.6 | How long a flight lasts, in seconds. |
SEED | script.js | 11 | The starting mess: another number, another scatter. |
FAIL_EVERY | script.js | 2 | One run in how many meets a file that is open (0 never). |
FAIL | script.js | 0.58 | Where the open file is met, from 0 to 1. |
PREVIEW | script.js | 1.45 | In the preview’s round: how many times faster. |
LABELS | script.js | ['Optimize', 'Pause', 'Resume', 'Retry', 'Optimize again'] | The words of the pill, state by state. |
--photos | style.css | #ff7442 | The colour of the photo blocks. |
--apps | style.css | #4f56e8 | The colour of the app blocks. |
For optimizing or cleaning up storage in a phone or computer app, for an antivirus or a cleaner, for compacting an archive or a database in a dashboard, for any long job that puts lots of small things in order.
The panel takes the whole width less the margins and the grid shrinks with it: it always has 24 columns, with smaller blocks. The flights are the same, because positions live in cells; the legend stays in five columns. The pill answers a touch as it does the mouse.
Nothing flies: a block that changes place fades out where it was and in where it goes, the cache fades instead of shrinking, the frame jumps from place to place and there is no final ripple. The full colour of the blocks in place, the percentage and the tick stay.
GSAP 3.15.0, Martian Mono (font variabile, asse wdth) 5.3.0.
Compressed with gzip, the three demo files weigh 11 KB; the libraries: GSAP 28 KB.
Tested on 19 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.