Skip to content
animorio
IT
Back to the library

The candy factory

SVGGSAP, CSS onlyAdvancedCompressed weight: Demo 14 KBGSAP 28 KBFreeDownload the zip

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.

The code

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 &amp; 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&amp;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>

Playground

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

Or ask Claude Code for it

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 site

What it does

On opening the beam drops from above, the belt unrolls from the left, the pipe slides in from the side, the counter swings down on its little chains and the four machines (pot, press, arm and nozzle) fall into place with a bounce. Then the line starts and works in steps: the belt moves on by one station with its rollers and the motor wheel turning, the moulds stop with a little rock, and all the stations work together. The copper pot tips on its pivot (its two gears turn) and pours a thread of syrup that fills the mould's hollow, then swings back with a settle and a drop falls from the pipe to top it up; the steam keeps rising. The press is a crank gear with a connecting rod: it makes one turn per step, the ram comes down, squeezes the mould and the candy pops out with a flip and lands with a bounce. The arm comes down with the sheet of paper, the candy gets wrapped, two grippers close on the ends and twist them, then open again and the arm goes back up with a new sheet. The nozzle blows (puffs and lines of air) and the candy flies in an arc into the jar, which wobbles a little at each arrival. When the jar is full the lid drops on it, the jar slides away and an empty one drops in from above. The WRAPPED TODAY counter rolls its digits at every candy. A click on the scene or on the «Next flavour» button changes the flavour (Strawberry, Lemon, Mint, Blueberry, Orange, Grape): the valve spins, the pot jolts, the syrup in the pipe and the pot changes colour, and from then on the candies come out in the new flavour, so the jar fills with different colours. Copy for the made-up factory Humbug & Daughters.

How it works

One clock and the story of one step

Everything 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 τ.

Moulds that know where they are

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 crank press

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.

Paper and twisted ends

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 jar and the click

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.

Values to change

NameFileDefaultWhat it changes
PASSOscript.js2.6Seconds for each step of the belt; all the stations work together.
CARAMELLE_PER_BARATTOLOscript.js6Candies that fill a jar before it closes and leaves.
INCLINAZIONEscript.js38Degrees the pot tips to pour.
SALTOscript.js34How high the candy jumps when the press pops it out.
ARCOscript.js150The height of the arc of the candy blown into the jar.
CONTATORE_INIZIOscript.js48210Candies already wrapped today: the number the counter starts from.
--accento, --rame, --nastrostyle.css#E2446C, #D9824B, #4A3F59The button and the lid, the copper pot, the conveyor belt. The flavours live in GUSTI inside script.js.

Try them in the Playground

When to use it

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.

On phones

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.

With reduced motion

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.

Pitfalls

  • In step n the belt carries the moulds from station n − m − 1 to n − m: writing n instead of n − 1 puts everything one station ahead, and the mould reaches the press with its candy already wrapped.
  • The blown candy must move into the layer inside the glass only once it has passed the rim: earlier the glass would cover it while still outside, later it would pass in front of the glass.
  • The pot's jolt on click goes on a child group: the outer one has its transform written every frame for the tilt, and GSAP would wipe it.
  • The counter rolls forward even from 9 to 0: each column has eleven digits (0-9 and 0 again), it goes to 10 and at the end snaps back to 0 unseen.
  • The counter changes with every candy, so the picture never comes back identical: the whole loop (one jar) lasts CARAMELLE_PER_BARATTOLO × PASSO seconds.

Libraries

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.

Testing

Tested on 11 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

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.

All animations