
Words that melt into each other
Text, Scroll, SVG
NewFree
Scroll inside the preview
A sea museum’s page: as you scroll, a little yellow sub dives off the waves and goes all the way down to the seabed. The light fades band by band, schools of fish open as it passes, jellyfish, a squid and a dumbo octopus drift by, and in the dark its lamp throws a cone of light that sweeps over a wreck at 4,800 metres. A mechanical counter reads the depth.
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 submarine in the deep</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/standard.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/martian-mono@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- the header of the made-up museum -->
<header class="testata">
<p class="marchio">Bellwether <span>Sea Museum</span></p>
<a class="testata__link" href="#fine"><span>Plan a visit</span></a>
</header>
<main>
<!-- the dive: a tall section with a still stage; scrolling moves the camera down the water column -->
<section class="discesa" data-discesa>
<div class="palco" data-palco>
<svg class="mondo" data-mondo aria-hidden="true"></svg>
<svg class="buio" data-buio aria-hidden="true"></svg>
<svg class="sotto" data-sotto aria-hidden="true"></svg>
<!-- the opening, in the sky above the water -->
<div class="apertura" data-apertura>
<p class="apertura__etichetta" data-entra>New in the Deep Gallery</p>
<h1 class="apertura__titolo">
<span class="riga"><span data-riga>Down to</span></span>
<span class="riga"><span data-riga>the <em>dark.</em></span></span>
</h1>
<p class="apertura__testo" data-entra>Ride the little yellow sub <em>Pip</em> from the waves to the seabed, 4,800 metres below.</p>
</div>
<p class="cenno" data-cenno>
Scroll to dive
<svg viewBox="0 0 24 32" aria-hidden="true"><path d="M12 3V27M4 19L12 28L20 19" /></svg>
</p>
<!-- one caption for every band of depth -->
<div class="didascalie" data-didascalie>
<article class="didascalia" data-zona="0">
<p class="didascalia__quota"><span>0 to 200 m</span></p>
<h2 class="didascalia__nome"><span>The sunlight zone</span></h2>
<p class="didascalia__testo"><span>Where the light still reaches. Most of the life in the sea crowds into this thin top layer.</span></p>
</article>
<article class="didascalia" data-zona="1">
<p class="didascalia__quota"><span>200 to 1,000 m</span></p>
<h2 class="didascalia__nome"><span>The twilight zone</span></h2>
<p class="didascalia__testo"><span>The blue fades to grey. Jellies drift past and whole schools rise every night to feed.</span></p>
</article>
<article class="didascalia" data-zona="2">
<p class="didascalia__quota"><span>1,000 to 4,000 m</span></p>
<h2 class="didascalia__nome"><span>The midnight zone</span></h2>
<p class="didascalia__testo"><span>No sunlight at all. From here on, the only light is the one Pip carries.</span></p>
</article>
<article class="didascalia" data-zona="3">
<p class="didascalia__quota"><span>4,000 to 6,000 m</span></p>
<h2 class="didascalia__nome"><span>The abyss</span></h2>
<p class="didascalia__testo"><span>Near-freezing water and hundreds of times the pressure of the air on your shoulders.</span></p>
</article>
<article class="didascalia" data-zona="4">
<p class="didascalia__quota"><span>On the seabed</span></p>
<h2 class="didascalia__nome"><span>The Argo Bell</span></h2>
<p class="didascalia__testo"><span>A cargo steamer lost in a winter storm in 1923, resting upright where no storm can reach her.</span></p>
</article>
</div>
<!-- the depth gauge: real numbers, counted as the sub goes down -->
<aside class="strumento" data-strumento aria-label="Depth gauge">
<p class="strumento__nome">Depth</p>
<p class="strumento__metri"><span class="contatore" data-contatore></span><span class="strumento__unita">m</span></p>
<dl class="strumento__dati">
<div><dt>Pressure</dt><dd data-pressione>1 atm</dd></div>
<div><dt>Sunlight</dt><dd data-luce>100%</dd></div>
</dl>
</aside>
<div class="scala" data-scala aria-hidden="true">
<div class="scala__binario"></div>
<svg class="scala__segno" data-segno viewBox="0 0 40 20"><path d="M6 10C6 5 11 3 20 3C29 3 35 6 35 10C35 14 29 17 20 17C11 17 6 15 6 10Z" /><path d="M16 3L18 0H24L25 3Z" /><path d="M1 6L6 10L1 14Z" /></svg>
</div>
</div>
</section>
<footer class="fine" id="fine">
<p class="fine__frase">Come down <em>with us.</em></p>
<p class="fine__testo">The Deep Gallery, open every day from ten. Pip is waiting at the bottom of the stairs.</p>
<a class="fine__bottone" href="#"><span>Plan your visit</span></a>
</footer>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.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.
64 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 submarine in the deep” from Animorio and fit it into my siteThe section is DIVE_LENGTH+1 screens tall and holds a sticky stage 100lvh tall with three SVGs on top of each other: the world, the dark and the sub, each with a viewBox as big as the stage in pixels. The world is a group more than six screens tall (sky, water with a four-stop gradient, fish, jellyfish, wreck, seabed) that slides up every frame: the camera is just a translate that keeps Pip still at 42% of the height.
Real depths do not fit on a screen evenly: 200 metres of light against 3,000 of midnight. A small table of knots says where each depth sits in screens below the waves (0, 1.5, 3, 4.6, 5.8) and two functions, profondita(u) and quota(d), go from one to the other in straight pieces. So every band gets its own stretch of scroll, and the counter always reads the right metres for Pip’s position.
A single timeline tied to the scroll with scrub moves an object of numbers: p (how far down, with power1.inOut), luce (the lamp, with elastic.out, so it flickers on), fine, mira and spazza (the final move aside and the beam running over the wreck). The captions come in when Pip passes 200, 1,000 and 4,000 metres: that moment is found by inverting the dive’s ease with thirty halvings.
Over the world lies a rectangle the colour of the dark, its opacity growing with depth, and a mask: a white rectangle with the lamp’s cone in black, softened with feGaussianBlur. Where the cone is, the dark disappears and the world shows through; on top, the same cone filled with a warm gradient in mix-blend-mode screen makes the light. The cone follows the sub’s nose and turns with it.
Every fish has a place in a school that slowly swims sideways, and a damped spring: if the sub or the pointer is within a radius, the spring’s goal becomes a push outwards, stronger the closer it is, and the fish gets there with a small bounce, turned along its speed. Once Pip has passed the spring brings it back and the school closes. The jellyfish use the same spring, softer.
| Name | File | Default | What it changes |
|---|---|---|---|
DIVE_LENGTH | script.js | 9 | How many screens of scrolling the dive lasts. |
MAX_DEPTH | script.js | 4800 | The depth of the seabed in metres, where the wreck lies. |
FISH | script.js | 36 | How many fish in each school; a phone gets about three quarters. |
PART | script.js | 1 | How wide the schools open around the sub and the pointer. |
JELLIES | script.js | 6 | How many jellyfish in the twilight zone. |
BEAM | script.js | 1 | How long and wide the lamp’s beam is. |
WEAVE | script.js | 1 | How far the sub drifts from side to side on the way down. |
SNOW | script.js | 70 | How many specks of marine snow drift through the beam. |
--sottomarino | style.css | #ffc629 | The sub’s yellow; next to it are the colours of the water at each depth, of the dark and of the beam. |
To tell a descent or a scale of sizes: a sea or science museum, an aquarium, an expedition, a diving centre, the page of an ocean research project, but also a story that goes from light to deep, or the tiers of a product.
The same dive with the sizes adapted: smaller sub, fish and creatures, about three quarters of the fish, the school wider for the screen, a longer beam, the wreck smaller and closer to the middle. The scale on the edge is shorter and has no labels. A tap on Pip makes it shake and puff bubbles.
No travelling camera, no swimming, no bubbles and no smooth scrolling: the stage shows one still picture per band (the surface, sunlight, twilight, midnight, the abyss, the wreck) and scrolling fades in the next, with the lamp already on where it is dark, the counter on that band’s number and its caption.
GSAP 3.15.0, Lenis 1.3.26, Bricolage Grotesque (font variabile) 5.3.0, Martian Mono (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 18 KB; the libraries: GSAP + ScrollTrigger 45 KB, Lenis 5.3 KB.
Tested on 10 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.