
The water button
Buttons, SVG, Physics, Hover
NewFree
Five shapes instead of stars, from a burst of spikes for the worst mark to a flower for the best: the coloured shape runs to the one pointed at and turns into it on the way, its outline a ring of springs that stretches and wobbles, printed in three layers like a risograph that slip out of register as it moves. Once picked, every shape answers in its own way: the flower shuts into a bud and blooms again, the burst bristles and shivers.
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 shape rating</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/recursive@5.3.0/casl.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the rating: five shapes from a spiky burst to a flower, over five real radio buttons. The coloured shape runs
to the one pointed at and turns into it on the way, its three printed layers a little out of step; picked,
it opens out for a good mark and flinches for a bad one. The shapes are drawn by the script -->
<form class="mood" data-mood onsubmit="return false">
<p class="mood__ask" id="mood-ask">How was today’s class?</p>
<fieldset class="mood__row" aria-labelledby="mood-ask" data-row data-autoclic>
<svg class="mood__art" aria-hidden="true" data-art>
<g class="mood__marks" data-marks></g>
<g class="mood__shape" data-shape>
<path class="mood__print" data-print/>
<path class="mood__print" data-print/>
<path class="mood__print" data-print/>
</g>
</svg>
<div class="mood__choices" data-choices></div>
</fieldset>
<p class="mood__word" aria-hidden="true"><span data-word>Pick one</span></p>
<p class="mood__thanks" role="status" aria-live="polite" data-thanks></p>
</form>
</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.
25 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 shape rating” from Animorio and fit it into my siteUnder the shapes there are real radio buttons, see-through, one per column: the arrows change the choice, Tab goes in and out of the group, screen readers read the question (the fieldset’s aria-labelledby) and the word of each choice (aria-label), and the form sends the value. The drawing is an SVG the script redraws, and the line underneath, in a role="status", says the choice made.
Every shape is a radius for every direction, from 0.6 to 1: the burst is an 11-turn cosine raised to 2.2 (narrow spikes, wide valleys) with a 3-turn unevenness, the drop a sum of cosines lower at the top, the square a superellipse of degree 4, the cloud |cos 4a| raised to 0.6, the flower |cos 3a| raised to 0.7. They are worked out once in 132 directions for each of the three prints (the middle one smaller and turned by half a lobe); between two shapes the radius is mixed with a smoothstep, so every point of the row has a shape of its own.
Each of the 132 points of the outline is a spring along its radius towards the radius the shape asks for (JELLY), and pulls its two neighbours by the difference between their errors, so a wave runs along the edge. The springs run on GSAP’s ticker in steps of 1/240 of a second; the shape stretches along its run by STRETCH times its speed, and when it stops the springs make it wobble. Once nothing moves, the ticker is removed and nothing is redrawn.
The three prints run on three springs, each softer than the one before (LAG): each takes the shape and the ink of the point of the row where it is itself, so on the move the dark prints fall behind with the shape they had. The first is solid, the other two are in mix-blend-mode multiply inside an isolated group: where they overlap the ink deepens, as in a risograph. The inks are mixed in OKLCh, lightness, colourfulness and hue on their own, the hue the shorter way round.
On a pick, a GSAP timeline for each print, 45 milliseconds apart, moves four settings of the outline: how far the lobes stand out from the middle radius (amp), the size, a squash and a turn. The flower goes to amp 0 (a round bud) and comes back with elastic.out while the springs jump; the cloud puffs up; the square starts a quarter turned and comes back with back.out, looking the same; the drop squashes and sinks; the burst draws out its spikes and gets a random speed on every point, and shivers.
The word is set in Recursive: the CASL axis goes from 0 to 1 and the weight from 760 to 640 with the mark, read from the middle print at every frame, and its colour is the shape’s ink with the lightness kept under 0.58 so it reads. A finger slides sideways along the shapes and the choice falls where it lifts; the click that may follow is cancelled, so the radio the finger started on does not win. A click from a script (detail 0, the library’s preview) is a pretend hand and picks in turn a flower, a drop, a cloud, a burst and the square.
| Name | File | Default | What it changes |
|---|---|---|---|
LABELS | script.js | ['Rough', 'Not great', 'Fine', 'Good', 'Loved it'] | The word for each choice, from the worst to the best (and how many choices there are: the shapes share out the way from the burst to the flower). |
START | script.js | 0 | The choice when the page opens (0 = none). |
MOVE | script.js | [230, 0.62] | The spring that carries the shape: how stiff it is and how much it is held back. |
LAG | script.js | 0.3 | How far behind the two dark prints follow the light one (0 = all together). |
JELLY | script.js | [700, 0.2] | The outline’s springs: how stiff they are and how much they are held back (held back less, they wobble longer). |
STRETCH | script.js | 0.0008 | How much the shape stretches with its speed. |
PULL | script.js | 0.3 | How far the shape leans from its place towards the pointer (0 = not at all, 1 = it follows it). |
BLOOM | script.js | 1 | How strong the answer to a pick is: the flower that shuts and blooms again, the burst that bristles, the drop that sags. |
To ask how a lesson, a course, a workshop, an appointment or a delivery went, for the mood diary of a wellbeing app, for the question at the end of a session: wherever you ask for a mark from one to five but stars would be too cold, or too much like a review.
The row is 310 pixels wide, 62 per choice, and narrows with the screen if there are more choices. A finger slides sideways along the shapes and the choice falls where it lifts; up and down the page still scrolls (touch-action: pan-y). A tap picks straight away.
No run and no springs: the shape is in its place at once, with its shape and colour, and the prints stay in register. On a pick it fades in; the word changes in a fade without rising or blurring, and the pale shapes do not move.
GSAP 3.15.0, Recursive (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 11 KB; the libraries: GSAP 28 KB.
Tested on 6 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.