
Words that melt into each other
Text, Scroll, SVG
NewFree
Scroll inside the preview
A roastery’s page: as you scroll, a cup seen at three quarters fills up. First the espresso falls in a thin thread from the two spouts and the crema rises, then a milk jug slides in, tilts and pours, rocks and draws a leaf on the surface, and at the end the steam rises in curls. Scroll back up and the cup empties.
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="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Il caffè 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>
<!-- la testata della torrefazione inventata -->
<header class="testata">
<p class="marchio">Ashgrove <span>Roasters</span></p>
<nav class="menu" aria-label="Main">
<a href="#tazza">The cup</a>
<a href="#fine">Beans</a>
<a href="#fine">Visit</a>
</nav>
</header>
<main>
<!-- l'apertura -->
<section class="apertura">
<p class="apertura__etichetta" data-entra>Small-batch coffee, roasted every Tuesday</p>
<h1 class="apertura__titolo">
<span class="riga"><span data-titolo>Pulled slow,</span></span>
<span class="riga"><span data-titolo>poured <em>by hand.</em></span></span>
</h1>
<p class="apertura__testo" data-entra>Our house flat white, from the first drop to the last curl of steam. Scroll down and watch it fill.</p>
<p class="apertura__cenno" data-entra>
Scroll to brew
<svg viewBox="0 0 24 32" aria-hidden="true"><path d="M12 3V27M4 19L12 28L20 19" /></svg>
</p>
<div class="chicchi" aria-hidden="true">
<svg class="chicco" viewBox="0 0 60 80" data-chicco><ellipse cx="30" cy="40" rx="26" ry="36" class="chicco__corpo" /><path d="M30 6C14 26 46 52 30 74" class="chicco__solco" /></svg>
<svg class="chicco" viewBox="0 0 60 80" data-chicco><ellipse cx="30" cy="40" rx="26" ry="36" class="chicco__corpo" /><path d="M30 6C14 26 46 52 30 74" class="chicco__solco" /></svg>
<svg class="chicco" viewBox="0 0 60 80" data-chicco><ellipse cx="30" cy="40" rx="26" ry="36" class="chicco__corpo" /><path d="M30 6C14 26 46 52 30 74" class="chicco__solco" /></svg>
</div>
</section>
<!-- la tazza: il palco resta fermo e, scorrendo, si riempie -->
<section class="storia" id="tazza" data-storia>
<div class="palco">
<div class="racconto">
<nav class="ricetta" aria-label="Steps">
<button type="button" data-voce>Shot</button>
<button type="button" data-voce>Crema</button>
<button type="button" data-voce>Pour</button>
<button type="button" data-voce>Leaf</button>
<button type="button" data-voce>Steam</button>
</nav>
<div class="passi">
<article class="passo" data-passo>
<p class="passo__etichetta" data-linea>The shot</p>
<h2 class="passo__titolo">
<span class="riga"><span data-riga>Eighteen grams,</span></span>
<span class="riga"><span data-riga>a thin <em>thread.</em></span></span>
</h2>
<p class="passo__testo" data-linea>Slow and steady from both spouts. If it gushes the grind is too coarse, if it drips it is too fine.</p>
</article>
<article class="passo" data-passo>
<p class="passo__etichetta" data-linea>The crema</p>
<h2 class="passo__titolo">
<span class="riga"><span data-riga>Tiger stripes</span></span>
<span class="riga"><span data-riga>on <em>top.</em></span></span>
</h2>
<p class="passo__testo" data-linea>The oils rise into a hazelnut foam. The last drops fall and the machine goes quiet.</p>
</article>
<article class="passo" data-passo>
<p class="passo__etichetta" data-linea>The pour</p>
<h2 class="passo__titolo">
<span class="riga"><span data-riga>Milk under,</span></span>
<span class="riga"><span data-riga>not <em>over.</em></span></span>
</h2>
<p class="passo__testo" data-linea>Start high, so the milk dives beneath the crema and lifts the colour from below.</p>
</article>
<article class="passo" data-passo>
<p class="passo__etichetta" data-linea>The leaf</p>
<h2 class="passo__titolo">
<span class="riga"><span data-riga>Rock the jug,</span></span>
<span class="riga"><span data-riga>pull it <em>through.</em></span></span>
</h2>
<p class="passo__testo" data-linea>Drop close, rock side to side as you drift back, then lift and draw one line down the middle. A rosetta.</p>
</article>
<article class="passo" data-passo>
<p class="passo__etichetta" data-linea>The steam</p>
<h2 class="passo__titolo">
<span class="riga"><span data-riga>Drink it while</span></span>
<span class="riga"><span data-riga>it <em>curls.</em></span></span>
</h2>
<p class="passo__testo" data-linea>Warm enough to taste the sweetness, never scalding. Scroll back up and the cup empties again.</p>
</article>
</div>
<p class="misura">In the cup <strong><span data-ml>0</span> ml</strong></p>
</div>
<svg class="scena" data-scena viewBox="0 0 600 760" preserveAspectRatio="xMidYMax meet" aria-hidden="true">
<defs>
<clipPath id="bocca"><ellipse cx="300" cy="440" rx="145" ry="57" /></clipPath>
<linearGradient id="grad-corpo" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" class="stop-tazza-ombra" />
<stop offset="0.38" class="stop-tazza" />
<stop offset="0.62" class="stop-tazza" />
<stop offset="1" class="stop-tazza-ombra" />
</linearGradient>
<linearGradient id="grad-interno" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="stop-tazza-ombra" />
<stop offset="1" class="stop-tazza" />
</linearGradient>
<linearGradient id="grad-piattino" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" class="stop-tazza-ombra" />
<stop offset="0.45" class="stop-tazza" />
<stop offset="1" class="stop-tazza-ombra" />
</linearGradient>
<linearGradient id="grad-acciaio" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" class="stop-acciaio-scuro" />
<stop offset="0.35" class="stop-acciaio" />
<stop offset="0.55" class="stop-acciaio-chiaro" />
<stop offset="1" class="stop-acciaio-scuro" />
</linearGradient>
<filter id="sfuma" x="-50%" y="-20%" width="200%" height="140%"><feGaussianBlur stdDeviation="2.6" /></filter>
<linearGradient id="grad-macchina" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="stop-macchina" stop-opacity="0" />
<stop offset="0.6" class="stop-macchina" />
</linearGradient>
<radialGradient id="grad-crema" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" class="stop-crema-chiara" />
<stop offset="0.7" class="stop-crema" />
<stop offset="1" class="stop-crema-bordo" />
</radialGradient>
<radialGradient id="grad-misto" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" class="stop-misto" />
<stop offset="0.75" class="stop-misto" stop-opacity="0.85" />
<stop offset="1" class="stop-misto" stop-opacity="0" />
</radialGradient>
</defs>
<!-- il piattino -->
<g data-tazza>
<ellipse cx="300" cy="656" rx="238" ry="92" class="piattino-ombra" />
<ellipse cx="300" cy="646" rx="236" ry="90" fill="url(#grad-piattino)" class="piattino" />
<ellipse cx="300" cy="640" rx="156" ry="56" class="piattino-incavo" />
<ellipse cx="300" cy="638" rx="124" ry="38" class="ombra-tazza" />
<!-- il manico -->
<path d="M446 478C522 466 540 566 436 576" class="manico" />
<path d="M446 478C522 466 540 566 436 576" class="manico-luce" />
<!-- l'interno e la parete dietro -->
<ellipse cx="300" cy="440" rx="150" ry="60" fill="url(#grad-interno)" />
<!-- la superficie: tutto quello che sta dentro è disegnato visto dall'alto (raggio 100) e schiacciato -->
<g clip-path="url(#bocca)">
<g data-superficie>
<circle r="100" class="espresso" />
<circle r="100" fill="url(#grad-crema)" data-crema />
<g data-macchie></g>
<circle r="100" fill="url(#grad-misto)" data-misto />
<g data-cerchi></g>
<g data-rosetta></g>
<path class="gambo" data-gambo />
</g>
</g>
<path d="M150 440A150 60 0 0 1 450 440" class="bordo" />
<!-- il vapore -->
<g data-vapore filter="url(#sfuma)"></g>
<!-- i fili che cadono -->
<path class="filo-caffe" data-filo />
<path class="filo-caffe" data-filo />
<ellipse rx="2.6" ry="3.8" class="goccia" data-goccia />
<ellipse rx="2.6" ry="3.8" class="goccia" data-goccia />
<path class="filo-latte" data-filo-latte />
<!-- la parete davanti -->
<path d="M150 440C150 548 196 632 300 636C404 632 450 548 450 440A150 60 0 0 1 150 440Z" fill="url(#grad-corpo)" class="corpo" />
<path d="M450 440A150 60 0 0 1 150 440" class="bordo bordo--davanti" />
<path d="M174 506C184 562 210 600 246 620" class="riflesso" />
</g>
<!-- la macchina: il gruppo e il portafiltro con i due beccucci -->
<g data-macchina>
<rect x="160" y="-60" width="280" height="110" rx="22" fill="url(#grad-macchina)" />
<rect x="160" y="18" width="280" height="10" class="macchina-riga" />
<rect x="222" y="48" width="156" height="50" rx="9" fill="url(#grad-acciaio)" />
<rect x="40" y="104" width="204" height="24" rx="12" transform="rotate(-5 244 116)" class="legno" />
<path d="M228 96H372L364 134C362 142 356 146 348 146H252C244 146 238 142 236 134Z" fill="url(#grad-acciaio)" />
<path d="M279 146H293L291 160H281Z" class="beccuccio" />
<path d="M307 146H321L319 160H309Z" class="beccuccio" />
</g>
<!-- la lattiera: il becco è nell'origine, così si inclina attorno al punto da cui esce il latte -->
<g data-brocca>
<g data-brocca-corpo>
<path d="M-131 30C-178 30 -180 112 -129 114" class="manico-brocca" />
<path d="M4 0C-6 2 -11 6 -13 13L-21 132C-22 144 -31 150 -43 150H-112C-124 150 -132 144 -132 132L-130 13C-130 8 -126 4 -120 4Z" fill="url(#grad-acciaio)" class="brocca" />
<path d="M-126 7H-12" class="brocca-bordo" />
<path d="M-104 22L-108 136" class="brocca-luce" />
</g>
</g>
<!-- dove il puntatore scuote la tazza -->
<ellipse cx="300" cy="560" rx="220" ry="150" class="presa" data-presa />
</svg>
</div>
</section>
<footer class="fine" id="fine">
<p class="fine__frase">Your cup is ready. <em>Come by for the real one.</em></p>
<a class="fine__bottone" href="#tazza"><span>Order the beans</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.
74 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 coffee that fills as you scroll” from Animorio and fit it into my siteThe section is SCROLL_LENGTH screens tall and holds a sticky stage 100lvh tall: the story on the left, on the right an SVG with a fixed viewBox (600 × 760) and preserveAspectRatio xMidYMax meet, so the cup stays sitting at the bottom at any size. The drawing is in layers: saucer, handle, inside of the cup, the coffee’s surface, back rim, steam, threads, front wall and front rim. The threads sit between the back rim and the front wall: up high they pass over the cup, at the bottom they vanish behind the wall.
Everything that moves is a number from 0 to 1 in one object: how far the thread has fallen, how far the tail has let go, the level, the crema, the machine, the jug, the tilt, the milk, the leaf, the steam. A single paused timeline moves them, each with its own curve (back.in for the machine lifting away, back.out for the jug coming in, elastic.out for every layer of the leaf). A tween takes its time from 0 to the end with a scrubbed ScrollTrigger, from when the section touches the top to when it ends: scroll up and the timeline goes back and the cup empties.
The coffee, the crema, the milk lightening it, the rings and the leaf are drawn in a group as if seen from above, in a circle of radius 100. A GSAP ticker gives it translate, scale and rotate every frame: the centre drops and the radius shrinks when the level is low, and the vertical scale (0.4 of the radius) squashes it into the ellipse of the rim. A clipPath with the rim’s ellipse cuts off what would sit behind the wall. The threads are filled shapes rebuilt every frame: a strip that thins out, with a sine that makes it tremble, between the tail and the tip.
Every layer of the rosetta is a crescent made of four quadratic curves: wide and tall far away, narrow close to the jug. The layers overlap, so only the edge of each one shows, thin and crema-coloured (vector-effect: non-scaling-stroke, so it doesn’t squash). A layer grows from v = 0 to 1 with elastic.out, and every open layer pushes the farther ones forward a little. Meanwhile the point where the milk lands moves from far to near, swinging on a sine, and the jug follows it. Then the middle line runs to the far side and, where it passes, drags the tip of each layer forward.
Each curl is a path with pathLength=1: its own endless timeline slides a 0.55-long dash along it with stroke-dashoffset and moves it sideways. The group has a blur filter and its opacity follows the steam in the timeline. The pointer on the cup takes a slosh number to ±1 and back to 0 with elastic.out: the cup turns slightly around the saucer and the surface slides and turns. The text of each step lives in the same timeline: the lines leave upwards with power3.in and come in from below with expo.out.
| Name | File | Default | What it changes |
|---|---|---|---|
LEAVES | script.js | 7 | How many layers the milk leaf has. |
WIGGLE | script.js | 1 | How far the jug rocks while drawing the leaf: 0 keeps it still. |
SCROLL_LENGTH | script.js | 6 | How many screens of scrolling the whole cup takes: more is slower. |
STEAM | script.js | 3 | How many curls of steam rise at the end. |
STEAM_SPEED | script.js | 1 | How fast the steam rises. |
SLOSH | script.js | 1 | How hard the cup rocks when the pointer touches it. |
CUP_ML | script.js | 180 | The millilitres of the full cup, for the counter; next to it is SHOT_ML for the espresso. |
--crema | style.css | #b8743c | The colour of the crema; next to it are those of the espresso, the milk, the cup and the jug. |
For anyone who sells or tells about something made in steps: a roastery, a café, a coffee machine or milk brand, a latte art school, a recipe to follow step by step, or any product that fills as you scroll, like a bottle or a jar.
The same animation with the sizes adapted: the story sits at the top, smaller, with the millilitre counter in small type below it, and the cup in the room left, resting on the bottom of the screen. A tap on the cup rocks it, a tap on the beans makes them hop.
No pouring, no rocking, no smooth scrolling: at every step the cup and the text fade out and come back with the step already done (the espresso in the cup, the crema, the milk poured, the leaf, still steam). The opening title and beans fade in too.
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 13 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.