
The cookie banner
Consent, Transitions, SVG, Physics
NewFree
Scroll inside the preview
A wide black navigation bar that, as you scroll, gathers itself into a floating pill like the iPhone’s Dynamic Island, with the name of the section you are reading. Pointed at, or tapped, it swells on a spring into a panel with the links and the upload in progress; while the files go up, a bubble buds off the pill with a ring that fills, stretching a neck of liquid that snaps.
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 bar that becomes an island</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/inter-tight@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- the navigation: a wide black bar at the top of the page that, scrolling, gathers itself into a floating pill
like the Dynamic Island. Pointed at or tapped, the pill swells into a panel with the links; while the upload
runs, a bubble buds off it with a ring that fills. Four layers of content take turns inside the one shape -->
<header class="top">
<nav class="island" aria-label="Main" data-island>
<!-- the bubble that buds off the island while the upload runs, and the liquid neck between them -->
<svg class="island__goo" aria-hidden="true" data-goo>
<path class="island__neck" data-neck/>
<circle class="island__bud" r="0" data-bud/>
</svg>
<div class="island__shape" data-shape>
<!-- the wide bar: the upload's chip, the links, the account -->
<div class="wide" data-layer="wide">
<span class="chip" aria-hidden="true" data-chip>
<svg class="ring ring--chip" viewBox="0 0 36 36"><path class="ring__track" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28"/><path class="ring__fill" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28" pathLength="1" data-ring/><path class="ring__check" d="M12.5 18.5l3.6 3.6 7.4-7.6" pathLength="1"/></svg>
<span class="chip__text" data-chip-text>Uploading</span>
</span>
<ul class="wide__links">
<li><a href="#features" data-go>Features</a></li>
<li><a href="#pricing" data-go>Pricing</a></li>
<li><a href="#changelog" data-go>Changelog</a></li>
<li><a href="#">Docs</a></li>
</ul>
<div class="wide__end">
<a class="wide__signin" href="#">Sign in</a>
<a class="cta cta--light" href="#">Start free</a>
<button class="wide__menu" type="button" aria-expanded="false" aria-controls="island-menu" aria-label="Open the menu" data-menu>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 9h14M5 15h14"/></svg>
</button>
</div>
</div>
<!-- the island: the menu's two lines and the section you are reading -->
<button class="compact" type="button" aria-expanded="false" aria-controls="island-menu" aria-label="Open the menu" data-layer="compact" data-toggle>
<svg class="compact__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 9h14M5 15h14"/></svg>
<span class="compact__label" aria-hidden="true" data-label-box><span class="compact__ghost" data-ghost></span></span>
</button>
<!-- the island saying something for a moment: an upload starting, files synced -->
<div class="notice" data-layer="notice">
<span class="notice__icon"><svg class="ring ring--notice" viewBox="0 0 36 36" aria-hidden="true"><path class="ring__track" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28"/><path class="ring__fill" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28" pathLength="1" data-ring/><path class="ring__check" d="M12.5 18.5l3.6 3.6 7.4-7.6" pathLength="1"/></svg></span>
<span class="notice__text"><b data-notice-title>Uploading 3 files</b><small data-notice-sub>To the team folder</small></span>
</div>
<!-- the open island: the live activity, the links, the account -->
<div class="open" id="island-menu" data-layer="open">
<div class="act">
<span class="act__ring">
<svg class="ring ring--act" viewBox="0 0 36 36" aria-hidden="true"><path class="ring__track" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28"/><path class="ring__fill" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28" pathLength="1" data-ring/><path class="ring__check" d="M12.5 18.5l3.6 3.6 7.4-7.6" pathLength="1"/></svg>
<span class="act__pct" aria-hidden="true" data-pct>0</span>
</span>
<span class="act__text">
<b data-act-title>Ready to sync</b>
<small data-act-sub>3 files waiting</small>
</span>
<button class="act__btn" type="button" data-act-btn>Upload</button>
</div>
<ul class="open__links">
<li><a href="#features" data-go><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4.5 4.5h6v6h-6zM13.5 4.5h6v6h-6zM4.5 13.5h6v6h-6zM13.5 13.5h6v6h-6z"/></svg>Features</a></li>
<li><a href="#pricing" data-go><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3.8 12.6V4.5h8.1l8.3 8.3-8.1 8.1zM8.3 8.6h.01"/></svg>Pricing</a></li>
<li><a href="#changelog" data-go><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 7.5V12l3 2M20.5 12a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0z"/></svg>Changelog</a></li>
<li><a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4.5h9.5L19 9v10.5H5zM14.5 4.5V9H19M8.5 13h7M8.5 16.5h5"/></svg>Docs</a></li>
</ul>
<div class="open__end">
<a class="open__signin" href="#">Sign in</a>
<a class="cta cta--light" href="#">Start free</a>
</div>
</div>
</div>
<span class="bud" aria-hidden="true" data-bud-ring>
<svg class="ring ring--bud" viewBox="0 0 36 36"><path class="ring__track" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28"/><path class="ring__fill" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28" pathLength="1" data-ring/><path class="ring__arrow" d="M18 23V13.4M14 17.2l4-4 4 4"/></svg>
</span>
</nav>
</header>
<main class="read">
<!-- a plain column to scroll under the bar: each section is a name the island shows while you read it -->
<section id="overview" data-section="Overview">
<h2>Overview</h2>
<p>Files saved on one device are on the others a few seconds later, and in the shared folder as soon as the team needs them.</p>
<p>There is nothing to set up: it keeps going in the background while you work.</p>
</section>
<section id="features" data-section="Features">
<h2>Features</h2>
<p>Changes travel in small pieces, so a large file that changed by one line sends only that line.</p>
<p>Every earlier version is kept for a year, and any of them comes back in one click.</p>
</section>
<section id="pricing" data-section="Pricing">
<h2>Pricing</h2>
<p>One plan for a single person and one for teams, paid by the seat and not by the gigabyte.</p>
<p>Either can be cancelled at the end of any month, and the files stay where they are.</p>
</section>
<section id="changelog" data-section="Changelog">
<h2>Changelog</h2>
<p>Uploads now carry on when you leave the page, and their progress shows in the bar at the top.</p>
<p>A folder with thousands of small files now arrives in one go instead of one file at a time.</p>
</section>
</main>
<p class="sr" aria-live="polite" data-live></p>
<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.
57 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 bar that becomes an island” from Animorio and fit it into my siteThe island is a single black element: the script writes its width, height, corner radius and place every frame. Inside are four layers of content, each at its own size and centred: the wide bar, the pill, the notice and the open panel. The layer coming in appears with a blur that clears after 0.1 seconds, the one leaving fades in 0.14 and becomes inert, so the Tab key does not go into it.
Width, height, corners and place are springs on GSAP's ticker, with the stiffness and damping of SPRING and the step capped at 1/30 of a second. Height and corners are less stiff (0.62 times) than the width: the island first widens and then swells, like the iPhone's, instead of scaling all at once.
The bubble is a circle in an SVG behind the island, starting from the circle of the pill's right end and moving right on a softer spring. Between the two circles the script redraws a neck every frame: two Bézier curves leaving each circle at a spread that narrows as they move apart, closed by an arc on the bubble. First the bubble is flung past the length of the neck (NECK), so the neck snaps, then it settles BUD[0] pixels away; the neck joins again only when the bubble touches the island.
On every scroll the script finds which section has passed 30% of the screen and puts its data-section in the pill: the old word rises and blurs, the new one comes from below. The pill's width is measured on an invisible copy of the new word, so the spring heads for the right size at once. Past SHRINK pixels the bar becomes the island, above half SHRINK it goes back to wide: the gap keeps it from flickering on the edge.
The pretend upload is a timeline of six stretches that together last UPLOAD seconds, in fits and starts like a real one: it starts by itself after AUTOPLAY[0] seconds, again AUTOPLAY[1] seconds after it ends, and the panel's button stops it or starts it again. Every frame, if it changed, it updates the rings (stroke-dashoffset on a path that starts at twelve o'clock), the percentage, the file going up (the first one in FILES not yet sent in full) and the MB left, in the bar's chip, in the island's panel and in the bubble. At the start and at the end the island speaks up for NOTICE seconds.
With the mouse the island opens when pointed at and closes GRACE seconds after the pointer leaves; with a finger, or the keyboard, the island's button opens and closes it. Enter opens and puts the focus inside the panel, Esc closes and puts it back on the island, a tap outside closes, and so does scrolling the page. An aria-live says when the upload starts and ends; the notice does not take the pill's place while someone is working in it with the keys.
| Name | File | Default | What it changes |
|---|---|---|---|
SPRING | script.js | [240, 0.62] | The island's spring: how stiff it is, and how much it is held back (1 = no bounce). |
SHRINK | script.js | 80 | How far down the page, in pixels, the bar gathers into the island. |
PILL | script.js | 38 | The height of the island, in pixels. |
BUD | script.js | [8, 0.92] | The bubble of the upload: its gap from the island in pixels, and its size against the island's height. |
NECK | script.js | 20 | How far the liquid neck stretches, in pixels, before it snaps. |
UPLOAD | script.js | 6 | The seconds the pretend upload takes. |
AUTOPLAY | script.js | [1.6, 5] | The upload starts by itself after these seconds, and again these seconds after it ends (0 = only by hand). |
NOTICE | script.js | 2.2 | The seconds the island speaks up when an upload starts or ends. |
GRACE | script.js | 0.3 | The seconds the open island waits, after the pointer leaves, before closing. |
FILES | script.js | 3 file | The files of the pretend upload: name and size in MB. |
For the navigation of a product site or a web app with something happening while you read: an upload, an export, an order on its way, a call, a timer. Where there is no live activity, the bar shrinking into an island with the name of the section is enough.
The wide bar keeps only the upload's chip, Start free and the menu button, which opens the same panel as the island; the island opens and closes with a tap, and a tap on the bubble or on the notice opens it. At 390 pixels the bar and the panel are the screen's width minus 24 pixels and nothing spills over; a file name too long for the panel ends in an ellipsis.
No springs and no neck of liquid: the island changes size at once, with a quick fade of shape and content; the bubble appears beside the pill already in its place. The name of the section and the words change with a fade, the rings still fill, the links jump to the section without scrolling.
GSAP 3.15.0, Inter Tight (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 4 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.