
Words that melt into each other
Text, Scroll, SVG
NewFree
Scroll inside the preview
A slice of ground seen from the side: as you scroll you go down through grass and roots, loam, clay, gravel and rock. In every layer a shovel digs, throws the earth away a shovelful at a time, and a find comes out of the hole: a coin that flips, a jar that puts itself back together, an arrowhead, an ammonite that draws its coil and a dinosaur skeleton that builds itself bone by bone. The museum label swings in next to it, and a ruler on the side shows 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>Lo scavo allo scroll</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<!-- the opening of the made-up museum -->
<section class="apertura">
<header class="testata">
<p class="marchio" data-entra>Underfield <span>Museum</span></p>
<nav class="menu" aria-label="Main" data-entra>
<a href="#scavo">The trench</a>
<a href="#fine">Visit</a>
</nav>
</header>
<p class="apertura__etichetta" data-entra>Gallery of the Ground</p>
<h1 class="apertura__titolo">
<span class="riga"><span data-titolo>Twelve metres</span></span>
<span class="riga"><span data-titolo>of <em>buried time.</em></span></span>
</h1>
<p class="apertura__testo" data-entra>One trench, five layers, five finds. Grab the shovel: every turn of the wheel digs a little deeper.</p>
<p class="apertura__cenno" data-entra>
Scroll to dig
<svg viewBox="0 0 24 32" aria-hidden="true"><path d="M12 3V27M4 19L12 28L20 19" /></svg>
</p>
</section>
<!-- the trench: the stage stays still and the ground moves up as we dig down -->
<section class="scavo" id="scavo" data-scavo>
<div class="palco" data-palco>
<svg class="terreno" data-terreno viewBox="0 -400 1200 760" preserveAspectRatio="xMidYMid slice" aria-label="A cross-section of the ground, with a find in every layer" role="img">
<defs>
<linearGradient id="grad-cielo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="stop-cielo-alto" />
<stop offset="1" class="stop-cielo-basso" />
</linearGradient>
<linearGradient id="grad-acciaio" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" class="stop-acciaio-scuro" />
<stop offset="0.45" class="stop-acciaio-chiaro" />
<stop offset="1" class="stop-acciaio" />
</linearGradient>
<linearGradient id="grad-legno" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" class="stop-legno-chiaro" />
<stop offset="1" class="stop-legno" />
</linearGradient>
<radialGradient id="grad-argento" cx="0.38" cy="0.34" r="0.75">
<stop offset="0" class="stop-argento-chiaro" />
<stop offset="0.6" class="stop-argento" />
<stop offset="1" class="stop-argento-scuro" />
</radialGradient>
<linearGradient id="grad-selce" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" class="stop-selce-chiara" />
<stop offset="0.55" class="stop-selce" />
<stop offset="1" class="stop-selce-scura" />
</linearGradient>
<radialGradient id="grad-conchiglia" cx="0.42" cy="0.4" r="0.7">
<stop offset="0" class="stop-conchiglia" />
<stop offset="1" class="stop-conchiglia-scura" />
</radialGradient>
<linearGradient id="grad-vaso" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" class="stop-terracotta-chiara" />
<stop offset="0.55" class="stop-terracotta" />
<stop offset="1" class="stop-terracotta-scura" />
</linearGradient>
<linearGradient id="grad-luce" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#fff" stop-opacity="0" />
<stop offset="0.5" stop-color="#fff" stop-opacity="0.85" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<clipPath id="clip-moneta"><circle r="58" /></clipPath>
<clipPath id="clip-punta"><path d="M0 -85C22 -50 38 -6 33 28L18 55L25 78L10 75L0 60L-10 75L-25 78L-18 55L-33 28C-38 -6 -22 -50 0 -85Z" /></clipPath>
<clipPath id="vaso-sagoma"><path d="M-30 -84H30L28 -72C70 -60 74 -12 64 22C54 56 32 80 0 84C-32 80 -54 56 -64 22C-74 -12 -70 -60 -28 -72Z" /></clipPath>
<clipPath id="coccio-0"><polygon points="-110,-110 10,-110 -2,-62 -8,-40 -110,-30" /></clipPath>
<clipPath id="coccio-1"><polygon points="10,-110 110,-110 110,-20 30,10 12,-18 -8,-40 -2,-62" /></clipPath>
<clipPath id="coccio-2"><polygon points="-110,-30 -8,-40 12,-18 -35,25 -110,10" /></clipPath>
<clipPath id="coccio-3"><polygon points="12,-18 30,10 -10,40 -35,25" /></clipPath>
<clipPath id="coccio-4"><polygon points="110,-20 110,110 -10,110 5,62 -10,40 30,10" /></clipPath>
<clipPath id="coccio-5"><polygon points="-110,10 -35,25 -10,40 5,62 -10,110 -110,110" /></clipPath>
<!-- the jar, drawn once and cut into six pieces -->
<g id="vaso-disegno">
<path d="M-34 -76C-62 -76 -64 -48 -50 -40" class="vaso-ansa" />
<path d="M34 -76C62 -76 64 -48 50 -40" class="vaso-ansa" />
<path d="M-30 -84H30L28 -72C70 -60 74 -12 64 22C54 56 32 80 0 84C-32 80 -54 56 -64 22C-74 -12 -70 -60 -28 -72Z" fill="url(#grad-vaso)" />
<g clip-path="url(#vaso-sagoma)">
<rect x="-80" y="-52" width="160" height="9" class="vaso-fascia" />
<path d="M-80 -24L-68 -38L-56 -24L-44 -38L-32 -24L-20 -38L-8 -24L4 -38L16 -24L28 -38L40 -24L52 -38L64 -24L76 -38" class="vaso-onda" />
<rect x="-80" y="-14" width="160" height="4" class="vaso-fascia" />
<rect x="-80" y="30" width="160" height="6" class="vaso-fascia" />
<ellipse cx="44" cy="10" rx="30" ry="80" class="vaso-ombra" />
</g>
<rect x="-38" y="-96" width="76" height="13" rx="5" class="vaso-labbro" />
</g>
<!-- the textures of the five layers, filled in by the script -->
<pattern id="trama-0" patternUnits="userSpaceOnUse" width="480" height="480" data-trama></pattern>
<pattern id="trama-1" patternUnits="userSpaceOnUse" width="480" height="480" data-trama></pattern>
<pattern id="trama-2" patternUnits="userSpaceOnUse" width="480" height="480" data-trama></pattern>
<pattern id="trama-3" patternUnits="userSpaceOnUse" width="480" height="480" data-trama></pattern>
<pattern id="trama-4" patternUnits="userSpaceOnUse" width="640" height="640" data-trama></pattern>
</defs>
<g data-cielo></g>
<g data-strati></g>
<g data-radici></g>
<g data-erba></g>
<g data-picchetti>
<path d="M244 -2V-58M536 -2V-58" class="picchetto" />
<path d="M244 -48Q390 -40 536 -48" class="spago" />
<g class="cartello" data-cartello>
<path d="M590 0V-92" class="picchetto" />
<rect x="536" y="-150" width="108" height="62" rx="6" class="cartello__asse" />
<text x="590" y="-124" class="cartello__titolo">TRENCH B</text>
<text x="590" y="-106" class="cartello__sotto">Underfield Museum</text>
</g>
</g>
<g data-cavita></g>
<g data-reperti>
<!-- topsoil: a silver penny -->
<g data-posto>
<g data-reperto>
<g class="dentro" data-dentro>
<g data-faccia>
<circle r="58" fill="url(#grad-argento)" class="moneta" />
<circle r="49" class="moneta-giro" />
<g data-perline></g>
<path d="M0 -46V46M-46 0H46" class="moneta-croce" />
<g data-pallini-moneta></g>
<g clip-path="url(#clip-moneta)">
<g transform="rotate(22)"><rect x="-14" y="-90" width="26" height="180" fill="url(#grad-luce)" data-luccichio /></g>
</g>
</g>
</g>
</g>
</g>
<!-- loam: a storage jar in six pieces -->
<g data-posto>
<g data-reperto>
<g class="dentro" data-dentro>
<g data-coccio><use href="#vaso-disegno" clip-path="url(#coccio-0)" /><polygon points="-110,-110 10,-110 -2,-62 -8,-40 -110,-30" class="crepa" clip-path="url(#vaso-sagoma)" /></g>
<g data-coccio><use href="#vaso-disegno" clip-path="url(#coccio-1)" /><polygon points="10,-110 110,-110 110,-20 30,10 12,-18 -8,-40 -2,-62" class="crepa" clip-path="url(#vaso-sagoma)" /></g>
<g data-coccio><use href="#vaso-disegno" clip-path="url(#coccio-2)" /><polygon points="-110,-30 -8,-40 12,-18 -35,25 -110,10" class="crepa" clip-path="url(#vaso-sagoma)" /></g>
<g data-coccio><use href="#vaso-disegno" clip-path="url(#coccio-3)" /><polygon points="12,-18 30,10 -10,40 -35,25" class="crepa" clip-path="url(#vaso-sagoma)" /></g>
<g data-coccio><use href="#vaso-disegno" clip-path="url(#coccio-4)" /><polygon points="110,-20 110,110 -10,110 5,62 -10,40 30,10" class="crepa" clip-path="url(#vaso-sagoma)" /></g>
<g data-coccio><use href="#vaso-disegno" clip-path="url(#coccio-5)" /><polygon points="-110,10 -35,25 -10,40 5,62 -10,110 -110,110" class="crepa" clip-path="url(#vaso-sagoma)" /></g>
</g>
</g>
</g>
<!-- clay: a flint arrowhead -->
<g data-posto>
<g data-reperto>
<g class="dentro" data-dentro>
<path d="M0 -85C22 -50 38 -6 33 28L18 55L25 78L10 75L0 60L-10 75L-25 78L-18 55L-33 28C-38 -6 -22 -50 0 -85Z" fill="url(#grad-selce)" class="punta" />
<g class="scheggie">
<path d="M-30 22Q-14 10 -5 -12" pathLength="1" data-scheggia />
<path d="M-33 -4Q-16 -14 -7 -36" pathLength="1" data-scheggia />
<path d="M-22 -40Q-9 -47 -3 -66" pathLength="1" data-scheggia />
<path d="M30 24Q13 13 6 -8" pathLength="1" data-scheggia />
<path d="M33 -2Q17 -12 8 -34" pathLength="1" data-scheggia />
<path d="M22 -42Q9 -48 3 -66" pathLength="1" data-scheggia />
<path d="M0 -78V50" pathLength="1" class="cresta" data-scheggia />
</g>
<g clip-path="url(#clip-punta)">
<g transform="rotate(30)"><rect x="-16" y="-120" width="30" height="240" fill="url(#grad-luce)" data-luccichio /></g>
</g>
</g>
</g>
</g>
<!-- gravel: an ammonite, drawn by the script -->
<g data-posto>
<g data-reperto>
<g class="dentro" data-dentro data-ammonite></g>
</g>
</g>
<!-- bedrock: the bones of a dinosaur, drawn by the script -->
<g data-posto>
<g data-reperto>
<g class="dentro" data-dentro data-scheletro></g>
</g>
</g>
</g>
<!-- the shovel: its tip is at 0 0 -->
<g class="pala" data-pala transform="translate(400 -3000)">
<path d="M-6 -470H6V-140H-6Z" fill="url(#grad-legno)" />
<path d="M-6 -466L-24 -500Q0 -526 24 -500L6 -466" class="pala-impugnatura" />
<path d="M-20 -504H20" class="pala-impugnatura" />
<path d="M-11 -104L-7 -146H7L11 -104Z" class="pala-collo" />
<path d="M0 4C-22 2 -36 -10 -38 -30L-38 -104Q0 -112 38 -104L38 -30C36 -10 22 2 0 4Z" fill="url(#grad-acciaio)" class="pala-lama" />
<path d="M-26 -96Q0 -101 26 -96" class="pala-piega" />
</g>
<g data-zolle></g>
<g data-polveri></g>
</svg>
<!-- the depth ruler on the side -->
<div class="righello" aria-hidden="true">
<svg class="righello__svg" data-righello preserveAspectRatio="xMinYMid slice"></svg>
<svg class="lettura__punta" viewBox="0 0 12 16" data-lettura><path d="M0 0L12 8L0 16Z" /></svg>
<div class="lettura" data-lettura>
<div class="lettura__testo">
<span class="lettura__metri" data-metri>0.00 m</span>
<span class="lettura__strati">
<span data-nome-strato>Ground level</span>
<span data-nome-strato>Topsoil</span>
<span data-nome-strato>Loam</span>
<span data-nome-strato>Clay</span>
<span data-nome-strato>Gravel</span>
<span data-nome-strato>Bedrock</span>
</span>
</div>
</div>
</div>
<!-- the museum labels, one for every find -->
<div class="cartellini">
<article class="cartellino" data-cartellino>
<svg class="cartellino__filo" viewBox="0 0 80 34" aria-hidden="true"><path d="M8 34L40 4L72 34" /><circle cx="40" cy="4" r="3.5" /></svg>
<div class="cartellino__carta" data-carta>
<p class="cartellino__strato" data-riga>Topsoil, found at 0.3 m</p>
<h3 class="cartellino__nome" data-riga>Silver penny</h3>
<p class="cartellino__data" data-riga>Struck around 1270 AD</p>
<p class="cartellino__testo" data-riga>Lost from a purse on the old market road. Small enough to vanish in one step, tough enough to wait seven centuries.</p>
<p class="cartellino__dato" data-riga><span>In the ground</span><strong>about 750 years</strong></p>
</div>
</article>
<article class="cartellino" data-cartellino>
<svg class="cartellino__filo" viewBox="0 0 80 34" aria-hidden="true"><path d="M8 34L40 4L72 34" /><circle cx="40" cy="4" r="3.5" /></svg>
<div class="cartellino__carta" data-carta>
<p class="cartellino__strato" data-riga>Loam, found at 1.0 m</p>
<h3 class="cartellino__nome" data-riga>Storage jar</h3>
<p class="cartellino__data" data-riga>Roman, around 150 AD</p>
<p class="cartellino__testo" data-riga>It came out of the ground in six pieces. Every one was there, so the jar went back together on site.</p>
<p class="cartellino__dato" data-riga><span>In the ground</span><strong>about 1,870 years</strong></p>
</div>
</article>
<article class="cartellino" data-cartellino>
<svg class="cartellino__filo" viewBox="0 0 80 34" aria-hidden="true"><path d="M8 34L40 4L72 34" /><circle cx="40" cy="4" r="3.5" /></svg>
<div class="cartellino__carta" data-carta>
<p class="cartellino__strato" data-riga>Clay, found at 2.3 m</p>
<h3 class="cartellino__nome" data-riga>Flint arrowhead</h3>
<p class="cartellino__data" data-riga>Around 2500 BC</p>
<p class="cartellino__testo" data-riga>Knapped from a single nodule of flint, one flake at a time. The edge can still cut a sheet of paper.</p>
<p class="cartellino__dato" data-riga><span>In the ground</span><strong>about 4,500 years</strong></p>
</div>
</article>
<article class="cartellino" data-cartellino>
<svg class="cartellino__filo" viewBox="0 0 80 34" aria-hidden="true"><path d="M8 34L40 4L72 34" /><circle cx="40" cy="4" r="3.5" /></svg>
<div class="cartellino__carta" data-carta>
<p class="cartellino__strato" data-riga>Gravel, found at 4.5 m</p>
<h3 class="cartellino__nome" data-riga>Ammonite</h3>
<p class="cartellino__data" data-riga>Early Cretaceous</p>
<p class="cartellino__testo" data-riga>The coiled shell of a sea creature, washed into an old river bed long after the sea had gone.</p>
<p class="cartellino__dato" data-riga><span>Age</span><strong>about 130 million years</strong></p>
</div>
</article>
<article class="cartellino" data-cartellino>
<svg class="cartellino__filo" viewBox="0 0 80 34" aria-hidden="true"><path d="M8 34L40 4L72 34" /><circle cx="40" cy="4" r="3.5" /></svg>
<div class="cartellino__carta" data-carta>
<p class="cartellino__strato" data-riga>Bedrock, found at 8.4 m</p>
<h3 class="cartellino__nome" data-riga>The Underfield <span class="intero">plant-eater</span></h3>
<p class="cartellino__data" data-riga>Late Jurassic</p>
<p class="cartellino__testo" data-riga>As long as a bus and gentle as a cow. The bones lay close together, so the skeleton could stand again.</p>
<p class="cartellino__dato" data-riga><span>Age</span><strong>about 150 million years</strong></p>
</div>
</article>
</div>
</div>
</section>
<footer class="fine" id="fine">
<p class="fine__etichetta" data-fine>You have reached bedrock</p>
<p class="fine__frase" data-fine>Come and dig <em>the real thing.</em></p>
<a class="fine__bottone" href="#scavo" data-fine><span>Book a dig day</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.
89 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 dig on scroll” from Animorio and fit it into my siteThe section is SCROLL_LENGTH screens tall and holds a sticky stage 100lvh tall with a single SVG. The ground is drawn in fixed coordinates (the grass at y = 0, the rock below 2760) and the camera is the viewBox: it is rewritten every frame with its y. The viewBox width follows the shape of the screen but always shows at least 520 free units, so on a narrow phone the dinosaur’s hole fits. The ruler is a second SVG with the same y and scale, so the ticks move with the earth; the depth goes through a piecewise metres-to-units table, because the rock is much thicker than the grass.
Each layer is a path between two wavy lines made of sines, filled twice: with its colour and with a pattern (crumbs, small stones, clay lines, pebbles, cracks and veins). The pattern shapes are picked by a seeded random generator, so the ground is always the same, and the ones crossing the tile’s edge are copied on the other side, so the tiles join with no cuts. The roots are paths with pathLength=1 that grow with stroke-dashoffset during the first descent.
Over every find there is an ellipse cut into CLODS pieces: an outer ring of quadrilaterals (the edge has three points per side, nudged at random, to make it rough) and an inner fan around a centre. The pieces fit exactly, and the same outline, darker, draws the hole underneath. Every clod has the colour and the pattern of its layer, so before the dig you can’t see it; then the edges show, like loosened earth. The clods are sorted from top to bottom and split into SCOOPS shovelfuls.
Everything sits in a single paused timeline that a scrubbed ScrollTrigger moves back and forth. The camera and the shovel are objects of numbers (y, x, tilt) that onUpdate writes into attributes; the clods are moved by GSAP. Every shovelful: the approach, the blade going in with power3.in, three little knocks to the clods and the dust, the lever, the lift with the clods gathering on the blade, then the flick: x with power1.out and y in two parts (up with power2.out, down with power2.in), so a real arc, with the spin. Scroll back and everything goes back into place on its own.
Every find has its own entrance in the timeline: the coin turns with scaleX keyframes (1, -1, 1, -1, 1) and a glint cut by a clipPath; the jar is a single drawing in a <use> repeated six times, each with the clipPath of a sherd, and the sherds go back to 0 with back.out; the ammonite’s coil and ribs draw themselves with pathLength=1; the bones (built from segments, vertebrae along quadratic curves and ribs) start from random spots and come back with back.out, one after the other. Every find has an inner group just for the pointer, so its wobble never touches the timeline. The label is HTML: it comes in with elastic.out, turning around its nail, and its lines follow.
| Name | File | Default | What it changes |
|---|---|---|---|
SCROLL_LENGTH | script.js | 9 | How many screens of scrolling the whole dig takes: more is slower. |
SCOOPS | script.js | 3 | How many shovelfuls it takes to uncover each find. |
CLODS | script.js | 12 | How many clods the earth over each find breaks into (the dinosaur’s, being bigger, has more). |
FLING | script.js | 1 | How far the shovel throws the earth. |
WOBBLE | script.js | 1 | How much a find and its label wobble under the pointer: 0 keeps them still. |
GRASS_SWAY | script.js | 1 | How much the grass moves in the wind: 0 keeps it still. |
--strato-2 | style.css | #a9623c | The colour of the clay; next to it are those of the other layers, the holes, the sky and the finds. |
For a museum, an exhibition, an archaeology or geology site, a dinosaur park, a page for schools; but also for anyone telling something in layers to uncover on the way down: a company’s story from today back to its roots, a product taken apart piece by piece, a wine from the soil to the bottle.
The same animation with the sizes adapted: the ruler is a narrower strip with just the numbers, the metre reading sits at the top left, the hole is centred in the space left and the label sits at the bottom, almost as wide as the screen. A tap on a find makes it wobble.
No shovel, no flying clods, no camera going down and no smooth scrolling: at every fifth of the section the ground fades out and comes back already at the right layer, with the hole open, the find in place and its label. The grass and the clouds stay still, the title fades in.
GSAP 3.15.0, Lenis 1.3.26, Fraunces (font variabile) 5.3.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 20 KB; the libraries: GSAP + ScrollTrigger 45 KB, Lenis 5.3 KB.
Tested on 11 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.