
The star rating
Ratings, SVG, Hover
NewFree
The order button of a juice bar, in the rich style: in the round end of the raspberry pill stands a cream enamel blender with a glass jar full of strawberries, banana, kiwi and blueberries on a splash of milk. On a click the dial snaps round, the motor shudders, the blade spins and the milk forms a vortex: the fruit swirls, gets chopped and melts, the smoothie turns pink and rises while the pill fills like a timer. Then the motor stops, the smoothie sloshes, froths up and the words turn to Smoothie's ready.
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 blender button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the blender button: the order button of a juice bar, with a cream enamel blender standing in the round left end
of the pill. Strawberries, banana and kiwi slices and a few blueberries wait on a splash of milk in the glass
jar. A click turns the dial: the motor hums, the blade spins, the fruit is pulled into the vortex and chopped,
the milk turns pink and rises, and the pill fills like a timer. Then the motor stops, the smoothie sloshes,
settles under a crown of froth, and the words turn to Smoothie's ready. A second click pours a fresh one -->
<div class="bl" data-bl>
<button class="bl__btn" type="button" data-btn data-autoclic>
<span class="bl__fill" aria-hidden="true"><span class="bl__timer" data-timer></span></span>
<span class="bl__faces">
<span class="bl__face" data-face="rest">
<span class="bl__word" data-label-text>Blend my smoothie</span>
<span class="bl__note" data-note-text>Berry & banana</span>
</span>
<span class="bl__face bl__face--busy" data-face="busy" aria-hidden="true">
<span class="bl__shine" data-busy-text>Blending…</span>
</span>
<span class="bl__face bl__face--done" data-face="done" aria-hidden="true">
<svg class="bl__tick" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path data-tick d="M3 8.6 L6.6 12 L13 4.4" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="bl__done" data-done-text>Smoothie's ready</span>
</span>
</span>
</button>
<svg class="bl__art" viewBox="0 0 120 172" width="120" height="172" aria-hidden="true" data-art>
<defs>
<linearGradient id="bl-enamel" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" style="stop-color:var(--enamel-deep)"/>
<stop offset="0.28" style="stop-color:var(--enamel-light)"/>
<stop offset="0.62" style="stop-color:var(--enamel)"/>
<stop offset="1" style="stop-color:var(--enamel-deep)"/>
</linearGradient>
<linearGradient id="bl-chrome" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" style="stop-color:var(--chrome-light)"/>
<stop offset="0.48" style="stop-color:var(--chrome)"/>
<stop offset="0.52" style="stop-color:var(--chrome-dark)"/>
<stop offset="1" style="stop-color:var(--chrome-light)"/>
</linearGradient>
<radialGradient id="bl-dial" cx="0.4" cy="0.35" r="0.7">
<stop offset="0" style="stop-color:var(--chrome-light)"/>
<stop offset="0.7" style="stop-color:var(--chrome)"/>
<stop offset="1" style="stop-color:var(--chrome-dark)"/>
</radialGradient>
<linearGradient id="bl-glass" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#cfe6ee" stop-opacity="0.55"/>
<stop offset="0.2" stop-color="#ffffff" stop-opacity="0.08"/>
<stop offset="0.8" stop-color="#ffffff" stop-opacity="0.04"/>
<stop offset="1" stop-color="#cfe6ee" stop-opacity="0.6"/>
</linearGradient>
<linearGradient id="bl-rubber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#4a3c45"/>
<stop offset="1" stop-color="#241b22"/>
</linearGradient>
<linearGradient id="bl-liquid-shade" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#000" stop-opacity="0.16"/>
<stop offset="0.3" stop-color="#000" stop-opacity="0"/>
<stop offset="0.75" stop-color="#fff" stop-opacity="0.1"/>
<stop offset="1" stop-color="#000" stop-opacity="0.2"/>
</linearGradient>
<!-- the inside of the jar: the liquid and the fruit are cut by it -->
<clipPath id="bl-inside">
<path d="M32.6 23 L87.4 23 L82.2 113 Q60 117.5 37.8 113 Z"/>
</clipPath>
<!-- the four kinds of fruit, drawn once -->
<g id="bl-straw">
<path d="M0 -7.6 C5.4 -7.6 7.4 -3.6 6.2 0.6 C5 4.8 2 7.6 0 8.4 C-2 7.6 -5 4.8 -6.2 0.6 C-7.4 -3.6 -5.4 -7.6 0 -7.6 Z" style="fill:var(--berry)"/>
<path d="M0 -5.2 C3.2 -5.2 4.4 -2.6 3.6 0.4 C2.8 3.2 1.2 5 0 5.6 C-1.2 5 -2.8 3.2 -3.6 0.4 C-4.4 -2.6 -3.2 -5.2 0 -5.2 Z" style="fill:var(--berry-flesh)"/>
<path d="M0 -4.4 L0 4.4" stroke="#fff" stroke-opacity="0.55" stroke-width="0.9" stroke-linecap="round"/>
<g fill="#ffe7a8">
<ellipse cx="-4.6" cy="-2.2" rx="0.45" ry="0.7"/><ellipse cx="4.6" cy="-2.2" rx="0.45" ry="0.7"/>
<ellipse cx="-4.4" cy="2" rx="0.45" ry="0.7"/><ellipse cx="4.4" cy="2" rx="0.45" ry="0.7"/>
<ellipse cx="-2.4" cy="5.2" rx="0.4" ry="0.6"/><ellipse cx="2.4" cy="5.2" rx="0.4" ry="0.6"/>
</g>
<path d="M-4.6 -7 C-2.4 -10 -1 -8 0 -9.6 C1 -8 2.4 -10 4.6 -7 C2.6 -6.2 1.4 -7.4 0 -6.4 C-1.4 -7.4 -2.6 -6.2 -4.6 -7 Z" style="fill:var(--leaf)"/>
</g>
<g id="bl-banana">
<circle r="6.4" style="fill:var(--banana-skin)"/>
<circle r="5.5" style="fill:var(--banana)"/>
<circle r="2.2" style="fill:var(--banana-core)"/>
<g style="fill:var(--banana-seed)">
<circle cx="0" cy="-1.3" r="0.42"/><circle cx="1.15" cy="0.65" r="0.42"/><circle cx="-1.15" cy="0.65" r="0.42"/>
</g>
<path d="M-3.6 -3.4 A5 5 0 0 1 2 -4.6" fill="none" stroke="#fff" stroke-opacity="0.6" stroke-width="0.9" stroke-linecap="round"/>
</g>
<g id="bl-kiwi">
<circle r="6.6" style="fill:var(--kiwi-skin)"/>
<circle r="5.7" style="fill:var(--kiwi)"/>
<circle r="3.6" style="fill:var(--kiwi-light)" fill-opacity="0.5"/>
<ellipse rx="1.7" ry="1.4" style="fill:var(--kiwi-core)"/>
<g fill="#1d2414">
<ellipse cx="0" cy="-2.9" rx="0.32" ry="0.7"/><ellipse cx="2.05" cy="-2.05" rx="0.32" ry="0.7" transform="rotate(45 2.05 -2.05)"/>
<ellipse cx="2.9" cy="0" rx="0.7" ry="0.32"/><ellipse cx="2.05" cy="2.05" rx="0.32" ry="0.7" transform="rotate(-45 2.05 2.05)"/>
<ellipse cx="0" cy="2.9" rx="0.32" ry="0.7"/><ellipse cx="-2.05" cy="2.05" rx="0.32" ry="0.7" transform="rotate(45 -2.05 2.05)"/>
<ellipse cx="-2.9" cy="0" rx="0.7" ry="0.32"/><ellipse cx="-2.05" cy="-2.05" rx="0.32" ry="0.7" transform="rotate(-45 -2.05 -2.05)"/>
</g>
</g>
<g id="bl-blue">
<circle r="3.4" style="fill:var(--blueberry)"/>
<circle cx="-1.1" cy="-1.1" r="1.2" fill="#fff" fill-opacity="0.28"/>
<path d="M-1 -2.6 L0 -1.8 L1 -2.6 L0.6 -3.3 L-0.6 -3.3 Z" fill="#1c2050"/>
</g>
</defs>
<!-- the shadow on the pill's foot line -->
<ellipse cx="60" cy="168.6" rx="40" ry="2.6" fill="#3a1424" fill-opacity="0.18"/>
<!-- the power cord, out of the back of the base and down to the floor -->
<path d="M24 158 C14 158 10 162 8 168 C7 170.4 4 171 1 171" fill="none" stroke="#2a1f26" stroke-width="2.2" stroke-linecap="round"/>
<path d="M23 157.2 C15 157.4 11.6 160.6 9.8 165" fill="none" stroke="#fff" stroke-opacity="0.25" stroke-width="0.7" stroke-linecap="round"/>
<g data-body>
<!-- the motor base, cream enamel -->
<path d="M30 122 L90 122 Q93 122 93.6 125 L99 162 Q99.6 167 94.6 167 L25.4 167 Q20.4 167 21 162 L26.4 125 Q27 122 30 122 Z" fill="url(#bl-enamel)"/>
<path d="M30 122 L90 122 Q93 122 93.6 125 L94 128 L26 128 L26.4 125 Q27 122 30 122 Z" fill="#fff" fill-opacity="0.35"/>
<path d="M21.6 159 L98.4 159 L99 162 Q99.6 167 94.6 167 L25.4 167 Q20.4 167 21 162 Z" fill="#000" fill-opacity="0.1"/>
<!-- the chrome band with the name -->
<rect x="27" y="130" width="66" height="7" rx="2" fill="url(#bl-chrome)"/>
<text class="bl__brand" x="60" y="135.3" text-anchor="middle" data-brand-text>WHIRL</text>
<!-- the vent slots on the side and the two screws of the band -->
<g stroke="#000" stroke-opacity="0.16" stroke-width="1.1" stroke-linecap="round">
<path d="M28.6 142 L27.6 150"/><path d="M31.6 142 L30.8 150"/><path d="M34.6 142 L34 150"/>
<path d="M91.4 142 L92.4 150"/><path d="M88.4 142 L89.2 150"/><path d="M85.4 142 L86 150"/>
</g>
<g>
<circle cx="30.6" cy="133.5" r="1.7" fill="url(#bl-dial)" stroke="#7d8a92" stroke-width="0.4"/>
<path d="M29.6 132.7 L31.6 134.3" stroke="#5d6870" stroke-width="0.5" stroke-linecap="round"/>
<circle cx="89.4" cy="133.5" r="1.7" fill="url(#bl-dial)" stroke="#7d8a92" stroke-width="0.4"/>
<path d="M88.4 134.3 L90.4 132.7" stroke="#5d6870" stroke-width="0.5" stroke-linecap="round"/>
</g>
<!-- a small scratch in the enamel -->
<path d="M80.6 156 l4.2 -1.4" stroke="#fff" stroke-opacity="0.55" stroke-width="0.5" stroke-linecap="round"/>
<!-- the speed dial with its ticks -->
<g fill="none" style="stroke:var(--enamel-ink)" stroke-width="1" stroke-linecap="round" stroke-opacity="0.55">
<path d="M48.6 152.8 L46.8 154.2"/><path d="M49.2 144.6 L47.2 143.6"/><path d="M54.4 139.6 L53.8 137.6"/>
<path d="M65.6 139.6 L66.2 137.6"/><path d="M70.8 144.6 L72.8 143.6"/><path d="M71.4 152.8 L73.2 154.2"/>
</g>
<circle cx="60" cy="149" r="10.2" fill="#000" fill-opacity="0.12"/>
<circle cx="60" cy="148.4" r="9.6" fill="url(#bl-dial)"/>
<circle cx="60" cy="148.4" r="6.6" fill="none" stroke="#fff" stroke-opacity="0.45" stroke-width="0.8"/>
<g data-dial>
<rect x="58.9" y="139.6" width="2.2" height="6.6" rx="1.1" style="fill:var(--berry-deep)"/>
</g>
<!-- the rubber feet -->
<rect x="26" y="164" width="12" height="5" rx="2.2" fill="#2a1f26"/>
<rect x="82" y="164" width="12" height="5" rx="2.2" fill="#2a1f26"/>
<!-- the jar, which shakes a little more than the base when the motor runs -->
<g data-jar>
<!-- the collar that locks the jar on the base -->
<path d="M34 112 L86 112 L88.6 122.4 L31.4 122.4 Z" fill="url(#bl-rubber)"/>
<path d="M35 113.6 L85 113.6" stroke="#fff" stroke-opacity="0.18" stroke-width="1"/>
<!-- the handle -->
<path d="M86.4 34 C101 34 104.6 40 104.6 52 L104.6 82 C104.6 94 100 99 84.2 99" fill="none" style="stroke:var(--handle)" stroke-width="7" stroke-linecap="round"/>
<path d="M88 37.4 C98 37.6 100.8 42 100.8 52 L100.8 82" fill="none" stroke="#fff" stroke-opacity="0.3" stroke-width="1.6" stroke-linecap="round"/>
<!-- what is inside, cut by the glass -->
<g clip-path="url(#bl-inside)">
<rect x="30" y="20" width="60" height="100" fill="#eaf6fa" fill-opacity="0.55"/>
<g data-bubbles></g>
<path data-liquid d="M30 92 L90 92 L90 120 L30 120 Z" style="fill:var(--milk)"/>
<path data-liquid-shade d="M30 92 L90 92 L90 120 L30 120 Z" fill="url(#bl-liquid-shade)"/>
<path data-surface d="M30 92 L90 92" fill="none" stroke="#fff" stroke-opacity="0.55" stroke-width="1.1"/>
<g data-foam opacity="0"></g>
<g data-bits></g>
<g data-fruit></g>
<!-- the blade, seen from the side: it turns by squeezing -->
<g transform="translate(60 109.6)">
<rect x="-1.4" y="-1" width="2.8" height="5" rx="0.6" fill="#7a868e"/>
<g data-blade>
<path d="M-12 -1.2 C-6 -3.4 6 -3.4 12 -1.2 C6 0.4 -6 0.4 -12 -1.2 Z" fill="url(#bl-chrome)"/>
</g>
<circle r="1.6" fill="#5d6870"/>
</g>
</g>
<!-- the glass in front: its edge, its marks and the light on it -->
<path d="M31 21 L89 21 L83.6 112.6 Q60 117.6 36.4 112.6 Z" fill="url(#bl-glass)" stroke="#9fbfcc" stroke-opacity="0.9" stroke-width="1.6" stroke-linejoin="round"/>
<g stroke="#7fa3b2" stroke-opacity="0.7" stroke-width="1" stroke-linecap="round">
<path d="M78.6 46 L84 46"/><path d="M80.2 58 L83.4 58"/><path d="M78.4 70 L82.8 70"/><path d="M80 82 L82.2 82"/><path d="M77.8 94 L81.6 94"/>
</g>
<!-- the paper label of the juice bar, stuck on the glass a little crooked -->
<g transform="rotate(-3 54 32)">
<rect x="42" y="26" width="25" height="12" rx="1.6" fill="#fffaf2" stroke="#e7d6bd" stroke-width="0.5"/>
<rect x="42" y="26" width="25" height="2.4" rx="1" style="fill:var(--pill)"/>
<text class="bl__label" x="54.5" y="33.6" text-anchor="middle">PULP</text>
<text class="bl__label-small" x="54.5" y="36.6" text-anchor="middle">juice bar</text>
<path d="M64.4 37.4 L67 34.8 L67 37.4 Z" fill="#000" fill-opacity="0.08"/>
</g>
<!-- a few drops of condensation on the cold glass -->
<g fill="#fff" fill-opacity="0.7">
<ellipse cx="74" cy="42" rx="0.7" ry="1"/><ellipse cx="76.4" cy="50" rx="0.5" ry="0.75"/>
<ellipse cx="44" cy="44" rx="0.55" ry="0.8"/><ellipse cx="71" cy="31" rx="0.5" ry="0.7"/>
</g>
<g data-splash></g>
<path d="M37.6 28 L36.8 54" stroke="#fff" stroke-opacity="0.85" stroke-width="2.6" stroke-linecap="round"/>
<path d="M38.6 62 L38.2 70" stroke="#fff" stroke-opacity="0.7" stroke-width="2" stroke-linecap="round"/>
<path d="M84 30 L82.6 60" stroke="#fff" stroke-opacity="0.4" stroke-width="1.2" stroke-linecap="round"/>
<!-- the lid and its cap -->
<g data-lid>
<path d="M28.6 13.6 Q28.6 11 31.2 11 L88.8 11 Q91.4 11 91.4 13.6 L91.4 20.4 Q91.4 22.6 89.2 22.6 L30.8 22.6 Q28.6 22.6 28.6 20.4 Z" fill="url(#bl-rubber)"/>
<path d="M31 13.4 L89 13.4" stroke="#fff" stroke-opacity="0.22" stroke-width="1.2" stroke-linecap="round"/>
<g data-cap>
<path d="M51 11.4 L51 6.6 Q51 4.4 53.2 4.4 L66.8 4.4 Q69 4.4 69 6.6 L69 11.4 Z" style="fill:var(--berry)"/>
<path d="M53 6.4 L67 6.4" stroke="#fff" stroke-opacity="0.4" stroke-width="1.1" stroke-linecap="round"/>
</g>
</g>
</g>
</g>
</svg>
<p class="sr" aria-live="polite" data-live></p>
</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.
47 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 blender button” from Animorio and fit it into my siteA single SVG over the left end of the pill, which takes no pointer. Inside the jar everything is cut by a clipPath shaped like the inside: first the back of the glass, then the liquid, the froth, the flecks, the fruit and the blade; in front the glass with its edges, marks, label and reflections, so the contents look behind the glass.
The liquid is a path of 19 points from wall to wall: its height is the level, minus a Gaussian dip in the middle that is the vortex (deeper the faster the motor runs), plus the liquid climbing the walls, plus a standing wave for the slosh. The colour goes from milk to pink with gsap.utils.interpolate, read from the :root variables.
Each piece has its spot in the pile and an orbit in the vortex, at its own height and radius: the motor's pull takes it from the pile to the orbit, where it turns on an ellipse seen from the side and shrinks at the back. Each piece has the moment the blade gets it: from then it shrinks and fades, while flecks of its colour are born, go round and melt into the pink.
When the motor stops the script kicks the wave's amplitude, which on every frame is multiplied by an exponential that damps it: the smoothie goes from wall to wall less and less, like a spring. The shudder of the base and the jar is made of sines at different frequencies, scaled by the motor speed.
A GSAP timeline turns the dial, starts the motor, chops, colours and raises the level for BLEND_TIME seconds, fills the timer, stops the motor, sloshes, adds the froth and makes the cap jump. The drawing loop sits on the GSAP ticker and switches off when nothing moves. It is a button: Enter and Space work like a click, with aria-busy, aria-disabled and an aria-live region.
| Name | File | Default | What it changes |
|---|---|---|---|
LABEL | script.js | Blend my smoothie | The words at rest. |
NOTE | script.js | Berry & banana | The small words under them. |
BUSY | script.js | Blending… | The words while the motor runs. |
DONE | script.js | Smoothie's ready | The words once it is blended. |
BRAND | script.js | WHIRL | The name on the chrome band of the base. |
BLEND_TIME | script.js | 2.8 | How long the motor runs, in seconds. |
SWIRL | script.js | 1 | How deep the vortex gets: 0.4 gentle, 1.6 wild. |
SLOSH | script.js | 1 | How hard the smoothie sloshes when the motor stops. |
FRUIT | script.js | 11 | How many pieces of fruit go in the jar. |
SPEED | script.js | 1 | How fast the whole thing runs: 0.6 slow, 1.6 quick. |
For the order button of a juice bar, a breakfast bar, a delivery app or a kitchen appliance shop, or for a button that starts a job that takes a few seconds.
The component is 318 pixels wide or the screen minus 32, and 172 tall with a 60 pixel pill; at rest the small words sit under the big ones, so both fit. With no hover, the finger resting on it gives the motor its little burst like the mouse.
No shudder, no vortex, no fruit going round and no splashes: on a click the fruit fades, the milk turns pink and rises in a fade, the pill fills and the words change by fading.
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 13 KB; the libraries: GSAP 28 KB.
Tested on 23 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.