
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
An app install as a ship inside a glass bottle lying on its side: the hull is already in, its masts laid flat on the deck, and as the files are copied a thread that runs out of the neck pulls them up one after another, each on a sprung hinge. Then the sails unroll and fill with wind, the pennants run up the mastheads, the thread is cut and the cork knocks into the neck: installed. If it goes wrong, the mast that was going up falls back onto the deck; Retry raises it again.
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, viewport-fit=cover">
<title>The ship in a bottle installer</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 ship in a bottle: the install of an app shown as a schooner inside a glass bottle lying on a wooden stand.
The hull is already in, sitting in a sea of blue resin, with its three masts laid flat on the deck. As the files
are copied a thread that runs out of the bottle's neck to a little winch pulls the masts up one after another,
each on a sprung hinge; then the sails unroll and fill with wind and the pennants stream out. At the end the
thread is cut and the cork hops into the neck. Everything is painted by script.js on the canvas -->
<section class="ship" aria-label="App installer" data-veliero>
<div class="ship__scene" role="progressbar" aria-label="Install progress" aria-valuemin="0" aria-valuemax="100"
aria-valuenow="0" aria-valuetext="Ready to install" data-scene>
<canvas class="ship__paint" aria-hidden="true" data-paint></canvas>
</div>
<div class="ship__foot">
<div class="ship__words" aria-hidden="true">
<p class="ship__title"><span class="ship__swap" data-title>Harbor 3.0</span></p>
<p class="ship__meta"><span class="ship__swap" data-meta>Ready to install · 84.2 MB</span></p>
</div>
<div class="ship__buttons">
<button class="pill is-quiet" type="button" hidden data-again><span class="pill__label">Reinstall</span></button>
<button class="pill" type="button" data-go data-autoclic><span class="pill__label" data-label>Install</span></button>
</div>
</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.
47 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 ship in a bottle” from Animorio and fit it into my siteChunks of files of every size arrive, now and then after a stall, and a critically damped spring carries the shown part towards the arrived part and never goes back. Everything comes from that number: every mast has its stretch (the foremast between 3 and 23%, the mainmast between 19 and 39, the mizzen between 35 and 55), every sail one to unroll and one to fill, the pennants the last. So Cancel and Reinstall have no animation of their own: they take the number back to zero and the ship goes back to rest by itself, sails rolled up and masts down.
Every mast is an angle with its speed: the thread pulls it towards the angle of its stretch with a lightly damped spring (stiffness 95, damping from SPRING), and when the stretch ends the hinge clicks home with a little kick of speed, so the mast arrives upright and rings a few times. The boom and the gaff are tied to the mast and swing open as it rises: folded along it while it is low, square to it when it is up. When the thread slips the mast has no spring any more: it falls under its own weight (an acceleration in proportion to the sine of its angle) and bounces on the deck losing two thirds of its speed. The winch turns by how high the masts are, so it spins back by itself when one falls.
Every sail is a grid of 9 by 7 points in three dimensions, joined by sticks along the sides and across the diagonals, integrated with Verlet in steps of 1/120 of a second and eight rounds of constraints. The points along the mast, along the gaff and along the rolled edge are held, and the rolled edge comes down to the boom as the sail unrolls. The wind pushes the cloth out of its plane, in gusts that run along the sail, and the sticks across the sail are up to 10% longer than the flat sail: so the cloth can only belly out, and the free leech trembles. The jib is the same thing, a triangle that unrolls off its stay towards its clew.
The sail is drawn quad by quad, in a slightly oblique projection that shifts the points by their depth. For every point the normal comes from its neighbours left and right, above and below; a quad’s light is the average of its four corners, so the cloth reads as one surface and not a mosaic. The colour runs from the shadow of the cloth to the warm white of the sail, then the seams of the cloths and the bolt rope along the edge are drawn on top.
Everything inside the bottle is painted on a second canvas and copied into the bottle one row of pixels at a time: the row at distance u from the axis takes the row u + (arcsin(0.93 u) ÷ arcsin 0.93 − u) × 0.85, the law of a glass cylinder. The ship is magnified in the middle and squeezed towards the glass, as in real bottles. On top, the glass in layers: its green thickness (between the outline and the inner one, with evenodd), the thick base, the long highlight of the window, the shoulder, the neck and the lip, and on the plank the shadow with the light the bottle gathers into a streak.
At the end the thread is cut, the cork follows a Bézier curve from the plank to the mouth turning a full somersault, goes in with power3.in and squashes with elastic.out; at the same moment the bottle jolts, every mast takes a kick of speed and a glint crosses the glass. The scene is a role=progressbar with aria-valuenow and an aria-valuetext that names the phase, the component has aria-busy while it works and an aria-live line says when it starts, stops and ends. A click from a script, the library preview’s, never cancels or opens: after the result it installs again.
| Name | File | Default | What it changes |
|---|---|---|---|
SECONDS | script.js | 7 | How long the demo’s install takes, in seconds (a real one follows its files). |
SIZE | script.js | 84.2 | The megabytes of the app to install. |
FAIL_EVERY | script.js | 2 | In the demo, one install in how many goes wrong (0 never). |
FAIL_AT | script.js | 0.5 | How far the install that goes wrong gets, from 0 to 1: the mast that was going up falls back. |
SPRING | script.js | 1 | How much a mast rings on its hinge when it gets upright: 0 not at all, 1 a lot. |
WIND | script.js | 1 | How much wind fills the sails: 0 none, 1 a steady breeze. |
WAVE | script.js | 1 | The wave of the resin sea: 0 flat, 1 choppy. |
REFRACTION | script.js | 1 | How much the curved glass magnifies the ship in the middle and squeezes it towards the edges: 0 not at all. |
LABELS | script.js | { busy: 'Cancel', retry: 'Retry', done: 'Open', opening: 'Opening…' } | The button’s words while installing, after an error and at the end (at rest it is the one in index.html). |
WORDS | script.js | { copy: 'Copying files', config: 'Configuring', finish: 'Finishing up', done: 'installed', fail: 'Installation failed', why: 'Couldn’t write to disk', canceled: 'Installation canceled' } | The words under the bottle: the three phases, the end, the error and its reason, the cancel (at rest they are the ones in index.html). |
For a wait of a few seconds that ends with something ready: installing an app or an extension, an update, setting up a project, an export «bottled» into a package. The ship rigged piece by piece shows how far along things are without reading the percentage, and the cork going in is a «done» you can feel. For a one second load or a list of files it is too much: a plain bar is better.
The drawing is 380 units wide and 200 tall and fits the width: on a 390 pixel screen the component is 358 wide and about 240 tall, with nothing spilling over. The canvas paints at the screen’s real density, up to twice, and the row by row refraction follows the real pixels. The pills are 38 tall; under a finger they squash and spring back, and touching Install takes up the thread as the pointer does. The animations are the same as on a computer.
No springs, gusts, waves or somersaults: every mast goes from laid down to upright with a fade when it gets halfway along its stretch, every sail appears already filled with a still belly, the pennants appear without streaming, the sea keeps still and the cork vanishes from the plank and reappears in the neck. If the install stops, the mast goes back down with a fade. The words change with a fade, and the ship is still rigged one piece at a time, because it is what says how far along things are.
GSAP 3.15.0, Archivo (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 20 KB; the libraries: GSAP 28 KB.
Tested on 8 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.