
Words that melt into each other
Text, Scroll, SVG
NewFree
A small assembly line seen from the front, all in SVG: the copper pot pours coloured sugar into the moulds, the belt carries them to the press that pops the candies out, an arm wraps them in paper twisting the ends and a puff of air blows them into a jar that fills up. A counter counts the candies; a click changes the flavour.
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>La fabbrica di caramelle</title>
<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 class="banco" data-banco>
<header class="testo">
<p class="marchio">Humbug & Daughters Candy Works</p>
<h1 class="titolo">Poured, pressed,<br>twisted shut.</h1>
<p class="frase">Boiled sugar from the copper pot, pressed in steel moulds and wrapped by a very patient arm. One flavour at a time, one jar at a time.</p>
<button class="gusto" type="button" data-gusto>
<span class="gusto__fondo" aria-hidden="true"></span>
<span class="gusto__scritta">Next flavour</span>
</button>
<p class="ora" aria-live="polite"><span class="ora__etichetta">Now pouring</span> <span class="ora__nome" data-ora>Strawberry</span></p>
</header>
<div class="palco" data-palco>
<svg class="scena" data-scena viewBox="0 40 960 540" preserveAspectRatio="xMidYMid meet" role="img" aria-label="A small candy factory seen from the front: a copper pot pours coloured sugar into moulds on a conveyor belt, a press pops the candies out, an arm wraps them in twisted paper and a puff of air blows them into a jar that fills up. Click to change the flavour.">
<defs>
<linearGradient id="g-rame" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" style="stop-color: var(--rame-scuro)" />
<stop offset="0.35" style="stop-color: var(--rame-chiaro)" />
<stop offset="1" style="stop-color: var(--rame-scuro)" />
</linearGradient>
<linearGradient id="g-acciaio" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" style="stop-color: var(--acciaio-chiaro)" />
<stop offset="1" style="stop-color: var(--acciaio)" />
</linearGradient>
<clipPath id="c-incavo"><path d="M-30 0A30 16 0 0 0 30 0Z" /></clipPath>
<clipPath id="c-cifre" data-finestre></clipPath>
</defs>
<!-- il pavimento -->
<rect class="pavimento" x="0" y="556" width="960" height="30" />
<path class="piastrelle" d="M120 556V586M260 556V586M400 556V586M540 556V586M680 556V586M820 556V586" />
<!-- la trave in alto e i suoi due pali -->
<g data-entra="trave">
<rect class="palo" x="48" y="134" width="14" height="422" rx="3" />
<rect class="palo" x="736" y="134" width="14" height="422" rx="3" />
<rect class="trave" x="36" y="118" width="726" height="16" rx="3" />
<path class="bulloni" d="M60 126h.1M200 126h.1M340 126h.1M480 126h.1M620 126h.1M740 126h.1" />
</g>
<!-- il contatore appeso al muro -->
<g data-entra="contatore">
<path class="catenella" d="M800 40V72M920 40V72" />
<rect class="contatore" x="776" y="70" width="168" height="94" rx="10" />
<text class="contatore__etichetta" x="860" y="94">WRAPPED TODAY</text>
<g data-finestre-disegno></g>
<g clip-path="url(#c-cifre)"><g data-cifre></g></g>
</g>
<!-- il tubo dello zucchero e la sua valvola -->
<g data-entra="tubo">
<path class="tubo" d="M-10 150H92Q104 150 104 162V176" />
<path class="tubo-dentro" data-flusso d="M-10 150H92Q104 150 104 162V176" />
<rect class="tubo-bocca" x="96" y="172" width="16" height="8" rx="2" />
<g data-valvola>
<circle class="valvola" cx="50" cy="150" r="14" />
<path class="valvola-raggi" d="M50 136V164M36 150H64" />
<circle class="valvola-mozzo" cx="50" cy="150" r="4" />
</g>
<circle class="goccia" data-goccia cx="104" cy="180" r="4" opacity="0" />
</g>
<!-- la pentola di rame: appesa alla trave, si inclina sul perno -->
<g data-entra="pentola">
<rect class="asta" x="146" y="134" width="8" height="116" />
<g data-ingranaggi-pentola></g>
<g data-vapore></g>
<g data-pentola>
<g data-pentola-scossa>
<path class="pentola" fill="url(#g-rame)" d="M-56 -26L56 -26C56 30 30 46 0 46C-30 46 -56 30 -56 -26Z" />
<path class="pentola-riga" d="M-50 4H50M-40 26H40" />
<ellipse class="sciroppo-pentola" data-sciroppo-pentola cx="0" cy="-34" rx="54" ry="4" />
<rect class="pentola-orlo" x="-60" y="-34" width="120" height="9" rx="4" />
<path class="pentola-becco" d="M54 -34L76 -43L78 -36L58 -25Z" />
<path class="pentola-manico" d="M-60 -18Q-74 -18 -74 -6Q-74 6 -58 6" />
<circle class="perno" cx="0" cy="0" r="9" />
<circle class="perno-dentro" cx="0" cy="0" r="3" />
</g>
</g>
<path class="colata" data-colata d="" />
<path class="colata-luce" data-colata-luce d="" />
</g>
<!-- la pressa: la ruota a manovella e la biella spingono giù il pistone -->
<g data-entra="pressa">
<rect class="guida" x="328" y="200" width="6" height="150" rx="2" />
<rect class="guida" x="406" y="200" width="6" height="150" rx="2" />
<g data-pistone>
<rect class="stelo" data-stelo x="363" y="260" width="14" height="60" />
<rect class="pistone" x="334" y="0" width="72" height="26" rx="4" data-testa />
</g>
<rect class="scatola" x="318" y="134" width="108" height="70" rx="6" />
<path class="biella" data-biella d="" />
<g data-manovella></g>
<g data-pignone></g>
</g>
<!-- il nastro trasportatore -->
<g data-entra="nastro">
<rect class="gamba" x="112" y="444" width="12" height="112" />
<rect class="gamba" x="440" y="444" width="12" height="112" />
<rect class="gamba" x="694" y="444" width="12" height="112" />
<path class="traverso" d="M118 520L446 470M446 520L700 470" />
<rect class="motore" x="22" y="486" width="64" height="44" rx="6" />
<path class="catena" data-catena d="M82 444L52 494M94 436L66 506" />
<g data-ruota-motore></g>
<rect class="nastro" x="68" y="420" width="692" height="24" rx="12" />
<path class="nastro-righe" data-righe-su d="M80 421.5H748" />
<path class="nastro-righe" data-righe-giu d="M80 442.5H748" />
<g data-rulli></g>
<g class="cartellini">
<path class="cartellino-filo" d="M206 444V452M234 444V452M354 444V452M386 444V452M506 444V452M534 444V452M656 444V452M684 444V452" />
<rect class="cartellino" x="190" y="452" width="60" height="18" rx="4" /><text class="cartellino-testo" x="220" y="465">POUR</text>
<rect class="cartellino" x="340" y="452" width="60" height="18" rx="4" /><text class="cartellino-testo" x="370" y="465">PRESS</text>
<rect class="cartellino" x="490" y="452" width="60" height="18" rx="4" /><text class="cartellino-testo" x="520" y="465">WRAP</text>
<rect class="cartellino" x="640" y="452" width="60" height="18" rx="4" /><text class="cartellino-testo" x="670" y="465">BLOW</text>
</g>
</g>
<!-- gli stampi e le caramelle sul nastro -->
<g data-stampi></g>
<g data-caramelle></g>
<!-- il braccio che avvolge -->
<g data-entra="braccio">
<rect class="manicotto" x="508" y="134" width="24" height="112" rx="3" />
<g data-cremagliera-ruota></g>
<g data-braccio>
<rect class="stelo" data-braccio-stelo x="513" y="236" width="14" height="40" />
<g data-mano>
<path class="dito" data-dito-s d="" />
<path class="dito" data-dito-d d="" />
<rect class="mano" data-mano-rect x="456" y="0" width="128" height="12" rx="4" />
<rect class="foglio" data-foglio x="462" y="0" width="116" height="4" rx="1" />
</g>
</g>
</g>
<!-- l'ugello dell'aria -->
<g data-entra="ugello">
<path class="tubo" d="M634 134V328Q634 384 598 384" />
<g data-ugello>
<path class="ugello" d="M592 376L612 379V389L592 392Z" />
</g>
<g data-aria></g>
</g>
<!-- il barattolo -->
<ellipse class="ombra" data-ombra-barattolo cx="860" cy="556" rx="76" ry="6" />
<g data-barattolo>
<g data-barattolo-dentro>
<path class="vetro-dietro" d="M-62 -128Q-70 -128 -70 -116V-12Q-70 0 -58 0H58Q70 0 70 -12V-116Q70 -128 62 -128Z" />
<g data-pila></g>
</g>
<path class="vetro" d="M-62 -128Q-70 -128 -70 -116V-12Q-70 0 -58 0H58Q70 0 70 -12V-116Q70 -128 62 -128Z" />
<path class="vetro-luce" d="M-56 -108V-22M-46 -112V-92M54 -100V-40" />
<rect class="collo" x="-58" y="-140" width="116" height="13" rx="4" />
<rect class="etichetta" x="-40" y="-120" width="80" height="18" rx="3" />
<text class="etichetta-testo" x="0" y="-107">H&D MIXED</text>
<g data-coperchio>
<rect class="coperchio" x="-63" y="-153" width="126" height="15" rx="5" />
<rect class="coperchio-pomo" x="-14" y="-162" width="28" height="10" rx="4" />
</g>
</g>
<!-- le caramelle in volo -->
<g data-volo></g>
</svg>
</div>
</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.
39 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 candy factory” from Animorio and fit it into my siteEverything comes from a single number: a GSAP tween that goes from 0 to the length of a whole jar and repeats, plus a count of the loops. From it come the step n (an integer that keeps growing) and the time τ inside the step, from 0 to 2.6 seconds; PASSO stretches the same story. A function seg(τ, a, b, ease) tells where each movement is: 0 before a, 1 after b, a GSAP ease in between. The draw function of every frame, on GSAP's ticker, only reads n and τ.
Each mould has a number m; in step n it works at station n − m (0 the pot, 1 the press, 2 the arm, 3 the nozzle). From that number and τ everything follows: how full it is, whether the candy has popped out yet, how wrapped it is, whether it is flying. The stations before are done, the ones after not yet. Six SVG groups are enough: mould m uses group m mod 6, and the ones entering or leaving the belt turn around the roller and fade away.
The gear wheel makes a full turn every step with a power2.inOut ease, so the lowest point falls halfway. The crank pin is at (r·sin θ, −r·cos θ) and the ram, sliding vertically, is √(L² − x²) below it: the rod is L long and x is how far the pin is off axis. When the ram touches, the mould squashes; then the candy jumps on an arc of sin(π·p) and turns 180° around its centre, from dome down in the hollow to dome up.
The wrapped candy is a single shape drawn from two numbers: w (0 the flat sheet on the dome, 1 the closed pillow) and tw (the twist). The sheet stretches down to the belt, the ends grow from the sides, and while twisting the neck narrows as the tip widens and closes twice (|cos 2π·tw|), with sliding pleats. The dome below fades once the paper covers it.
The candy flies on a parabola from the nozzle to its place in the jar and, once past the rim, moves from the top layer to the one inside the glass. The places come in staggered pairs, each with its own angle. At the last place the lid drops with bounce.out, the jar slides away and on the next step an empty one drops in: that is also the end of the loop, which starts again identical. The click changes today's flavour: each mould takes the flavour in use when the pot starts pouring into it, and the pot's jolt goes on a child group, because the outer group is rewritten every frame.
| Name | File | Default | What it changes |
|---|---|---|---|
PASSO | script.js | 2.6 | Seconds for each step of the belt; all the stations work together. |
CARAMELLE_PER_BARATTOLO | script.js | 6 | Candies that fill a jar before it closes and leaves. |
INCLINAZIONE | script.js | 38 | Degrees the pot tips to pour. |
SALTO | script.js | 34 | How high the candy jumps when the press pops it out. |
ARCO | script.js | 150 | The height of the arc of the candy blown into the jar. |
CONTATORE_INIZIO | script.js | 48210 | Candies already wrapped today: the number the counter starts from. |
--accento, --rame, --nastro | style.css | #E2446C, #D9824B, #4A3F59 | The button and the lid, the copper pot, the conveyor belt. The flavours live in GUSTI inside script.js. |
For confectioners, sweet shops, candy and chocolate brands, craft workshops and pages that tell how a product is made: a cheerful scene that explains the whole process in a few seconds and invites a touch.
The factory copy goes on top and the scene below, smaller but the same: same pot, same press, same arm, same jar. The flavour changes by tapping the scene or the button.
The line stands still halfway through a step, with the pot pouring, the ram down, one candy wrapped and one in the air above the half-full jar, and it fades in; the click changes the flavour: the label fades out and back in, the syrup changes colour, nothing moves.
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 14 KB; the libraries: GSAP 28 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.