
The needle gauge
Counters, Physics, SVG
NewFree
In the rich style, colourful and full of things: the contact form of a greenhouse, Fernhouse. On the left a corner of the greenhouse with paned glass, light coming in, a sill with two little pots and a philodendron in a big terracotta pot; on the right the fields, wooden plant labels. While you write a rolled-up sprout pushes out of the node on the stem; when the label is right the petiole stretches and the leaf unrolls and opens on a spring; when it is wrong the leaf turns yellow, wilts and drops off the node, leaving a scar. Sent, the message is sealed in an illustrated seed packet that flies into the pot, a watering can waters it and the bud opens petal by petal.
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 greenhouse contact form</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/caveat@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the contact form of a greenhouse: every field is a wooden plant label, and the philodendron in the pot beside
it pushes a sprout while you write, unrolls a leaf when the label is right, lets it wilt and drop when it is
wrong; sent, the message is sealed in a seed packet, planted in the pot and watered until the plant blooms -->
<section class="serra" aria-labelledby="serra-title" data-serra data-autoclic>
<div class="serra__scene" aria-hidden="true">
<svg class="scene" viewBox="0 0 300 360" data-svg>
<defs>
<linearGradient id="serra-glass" x1="0" y1="0" x2="0.4" y2="1">
<stop offset="0" class="stop-glass-top"/>
<stop offset="1" class="stop-glass"/>
</linearGradient>
<linearGradient id="serra-leaf" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="stop-leaf-light"/>
<stop offset="1" class="stop-leaf"/>
</linearGradient>
<linearGradient id="serra-dry" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="stop-dry-light"/>
<stop offset="1" class="stop-dry"/>
</linearGradient>
<linearGradient id="serra-clay" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" class="stop-clay-dark"/>
<stop offset="0.3" class="stop-clay-light"/>
<stop offset="0.62" class="stop-clay"/>
<stop offset="1" class="stop-clay-dark"/>
</linearGradient>
<linearGradient id="serra-can" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" class="stop-can-dark"/>
<stop offset="0.35" class="stop-can-light"/>
<stop offset="1" class="stop-can"/>
</linearGradient>
<linearGradient id="serra-wood" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="stop-wood-light"/>
<stop offset="1" class="stop-wood"/>
</linearGradient>
<clipPath id="serra-room">
<rect x="0" y="0" width="300" height="360" rx="18"/>
</clipPath>
</defs>
<g clip-path="url(#serra-room)">
<!-- the greenhouse: glass panes in a green iron frame, the garden outside, light coming in -->
<rect x="0" y="0" width="300" height="360" fill="url(#serra-glass)"/>
<g class="outside">
<path d="M-10 300 C 20 250, 40 262, 62 236 C 80 214, 104 240, 120 228 C 146 206, 170 236, 196 222 C 224 206, 250 232, 272 214 C 292 200, 310 218, 320 214 V 330 H -10 Z" class="outside__far"/>
<path d="M-10 312 C 16 282, 44 296, 70 276 C 96 258, 120 288, 150 276 C 182 262, 210 290, 240 272 C 266 258, 292 280, 320 268 V 330 H -10 Z" class="outside__near"/>
<path d="M34 278 C 30 250, 18 236, 6 232 M34 278 C 40 254, 54 242, 64 240 M34 278 C 34 246, 36 226, 32 210" class="outside__fern"/>
<path d="M262 270 C 258 246, 246 232, 236 230 M262 270 C 268 248, 280 238, 292 236" class="outside__fern"/>
</g>
<g class="beams" data-beams>
<path d="M70 -10 L 120 -10 L 240 360 L 150 360 Z"/>
<path d="M150 -10 L 176 -10 L 300 300 L 262 330 Z"/>
<path d="M10 -10 L 34 -10 L 120 360 L 80 360 Z"/>
</g>
<g class="panes">
<path d="M8 120 L 140 120 M160 120 L 292 120 M8 236 L 292 236" class="frame__rail"/>
<path d="M6 0 V 330 M150 0 V 330 M294 0 V 330 M78 0 V 330 M222 0 V 330" class="frame__bar"/>
<path d="M10 112 L 70 20 M84 112 L 144 20 M156 112 L 216 20 M228 112 L 288 20 M18 228 L 70 140 M162 228 L 214 140" class="glass__shine"/>
</g>
<g class="mist" data-mist></g>
<!-- the sill the pots stand on -->
<rect x="-4" y="330" width="308" height="34" class="sill"/>
<rect x="-4" y="326" width="308" height="8" rx="2" class="sill__top"/>
<path d="M-4 334 H 304" class="sill__edge"/>
<!-- two little pots further along the sill: an echeveria and a cactus -->
<g transform="translate(38 326)">
<path d="M-16 -18 H 16 L 12 0 H -12 Z" fill="url(#serra-clay)"/>
<rect x="-18" y="-22" width="36" height="6" rx="2" class="pot__rim-small"/>
<g class="succulent">
<path d="M0 -22 C -10 -26, -16 -34, -12 -40 C -6 -34, -2 -30, 0 -22 Z"/>
<path d="M0 -22 C 10 -26, 16 -34, 12 -40 C 6 -34, 2 -30, 0 -22 Z"/>
<path d="M0 -22 C -6 -30, -6 -40, 0 -46 C 6 -40, 6 -30, 0 -22 Z"/>
<path d="M0 -22 C -14 -22, -22 -26, -22 -30 C -14 -30, -6 -27, 0 -22 Z" class="succulent__back"/>
<path d="M0 -22 C 14 -22, 22 -26, 22 -30 C 14 -30, 6 -27, 0 -22 Z" class="succulent__back"/>
</g>
</g>
<g transform="translate(266 326)">
<path d="M-13 -16 H 13 L 10 0 H -10 Z" fill="url(#serra-clay)"/>
<rect x="-15" y="-20" width="30" height="5" rx="2" class="pot__rim-small"/>
<path d="M-6 -20 V -44 C -6 -52, 6 -52, 6 -44 V -20 Z" class="cactus"/>
<path d="M6 -32 H 11 V -40 C 11 -45, 16 -45, 16 -40 V -30 C 16 -27, 14 -26, 11 -26 H 6 Z" class="cactus"/>
<path d="M-2 -46 V -22 M2 -46 V -22" class="cactus__rib"/>
<circle cx="0" cy="-50" r="3" class="cactus__flower"/>
</g>
<!-- the big pot: saucer, terracotta with the marks of the wheel, soil with pebbles and moss -->
<ellipse cx="150" cy="331" rx="66" ry="5" class="pot__shadow"/>
<path d="M88 322 H 212 L 206 330 H 94 Z" class="pot__saucer"/>
<g data-pot>
<path d="M96 276 H 204 L 194 326 H 106 Z" fill="url(#serra-clay)"/>
<path d="M98 288 H 202 M100 300 H 200 M102 312 H 198" class="pot__wheel"/>
<path d="M96 276 H 204 L 203 282 H 97 Z" class="pot__under-rim"/>
<rect x="88" y="260" width="124" height="18" rx="4" fill="url(#serra-clay)"/>
<path d="M90 263 H 210" class="pot__rim-light"/>
</g>
<ellipse cx="150" cy="261" rx="58" ry="6" class="soil"/>
<ellipse cx="150" cy="261" rx="58" ry="6" class="soil__wet" data-wet/>
<g class="soil__bits">
<ellipse cx="112" cy="261" rx="3" ry="1.6"/>
<ellipse cx="126" cy="263" rx="2" ry="1.2"/>
<ellipse cx="176" cy="260" rx="2.6" ry="1.4"/>
<ellipse cx="190" cy="262" rx="1.8" ry="1"/>
</g>
<path d="M100 262 C 106 256, 116 257, 120 261 C 116 264, 104 265, 100 262 Z M178 262 C 184 257, 196 258, 200 261 C 194 264, 182 265, 178 262 Z" class="moss"/>
<!-- the plant, drawn by the script: stem, leaves on their petioles, the flower bud on top -->
<g data-plant>
<path d="M150 262 C 142 222, 160 196, 151 162 S 144 110, 152 74" class="stem" data-stem/>
<path d="M150 262 C 142 222, 160 196, 151 162 S 144 110, 152 74" class="stem__light"/>
<g data-scars></g>
<g data-leaves></g>
<g data-flower></g>
</g>
<!-- the seed packet once planted: a little packet on a stick in the soil -->
<g transform="translate(184 262)">
<g class="marker" data-marker>
<path d="M0 4 V -28" class="marker__stick"/>
<rect x="-11" y="-50" width="22" height="26" rx="2" class="marker__paper"/>
<path d="M-11 -46 L -7 -50 L -3 -46 L 1 -50 L 5 -46 L 9 -50 L 11 -48 V -44 H -11 Z" class="marker__band"/>
<path d="M0 -28 C -2 -34, -6 -36, -8 -36 C -8 -32, -4 -30, 0 -30 Z M0 -30 C 2 -36, 6 -38, 8 -38 C 8 -34, 4 -32, 0 -32 Z" class="marker__leaf"/>
</g>
</g>
<!-- the watering can and its water, out of the way until the message is sent -->
<g data-water></g>
<g class="can" data-can>
<g data-can-tilt>
<path d="M-4 -32 C -4 -58, 54 -58, 54 -32" class="can__handle"/>
<path d="M-8 -20 L -62 -60" class="can__spout-back"/>
<path d="M-8 -20 L -62 -60" class="can__spout"/>
<g transform="translate(-64 -62) rotate(-38)">
<path d="M-3 -9 L 6 -6 V 6 L -3 9 Z" class="can__rose"/>
<path d="M-3 -9 C -7 -5, -7 5, -3 9" class="can__rose-face"/>
</g>
<path d="M-10 -34 H 60 V 14 C 60 22, -10 22, -10 14 Z" fill="url(#serra-can)"/>
<path d="M-10 -34 C -10 -40, 60 -40, 60 -34 C 60 -28, -10 -28, -10 -34 Z" class="can__top"/>
<path d="M-10 -8 H 60" class="can__band"/>
<path d="M2 -26 V 10" class="can__shine"/>
</g>
</g>
</g>
<rect x="0.5" y="0.5" width="299" height="359" rx="17.5" class="scene__edge"/>
</svg>
</div>
<div class="serra__side">
<form class="serra__form" action="#" novalidate data-form>
<h2 class="serra__title" id="serra-title">Write to the greenhouse</h2>
<div class="tag" data-field="name">
<label class="tag__label" for="serra-name">Your name</label>
<input class="tag__input" id="serra-name" name="name" type="text" autocomplete="name" spellcheck="false" aria-describedby="serra-err-name" data-input>
<span class="tag__err" id="serra-err-name" data-err></span>
<span class="tag__hole" aria-hidden="true"></span>
</div>
<div class="tag" data-field="email">
<label class="tag__label" for="serra-email">Email</label>
<input class="tag__input" id="serra-email" name="email" type="email" autocomplete="email" spellcheck="false" inputmode="email" aria-describedby="serra-err-email" data-input>
<span class="tag__err" id="serra-err-email" data-err></span>
<span class="tag__hole" aria-hidden="true"></span>
</div>
<div class="tag tag--long" data-field="message">
<label class="tag__label" for="serra-message">What is growing on your mind?</label>
<textarea class="tag__input" id="serra-message" name="message" rows="2" aria-describedby="serra-err-message" data-input></textarea>
<span class="tag__err" id="serra-err-message" data-err></span>
<span class="tag__hole" aria-hidden="true"></span>
</div>
<div class="serra__foot">
<button class="send" type="submit" data-send>
<svg class="send__sprout" viewBox="0 0 20 20" aria-hidden="true">
<path d="M10 18 V9"/>
<path d="M10 11 C 10 6, 6 4, 2 5 C 2 9, 6 11, 10 11 Z"/>
<path d="M10 9 C 10 5, 14 3, 18 4 C 18 8, 14 10, 10 9 Z"/>
</svg>
<span class="send__word" data-word>Plant message</span>
</button>
</div>
</form>
<!-- the seed packet the message is sealed in -->
<div class="packet" data-packet aria-hidden="true">
<div class="packet__body" data-packet-body>
<div class="packet__flap" data-flap>
<span class="packet__flap-back"></span>
</div>
<div class="packet__paper">
<div class="packet__head">
<span class="packet__brand">Fernhouse</span>
<span class="packet__kind">Seeds</span>
</div>
<svg class="packet__art" viewBox="0 0 120 70">
<rect x="0" y="0" width="120" height="70" rx="4" class="art__sky"/>
<circle cx="94" cy="20" r="11" class="art__sun"/>
<path d="M0 58 C 30 50, 60 62, 120 52 V 70 H 0 Z" class="art__ground"/>
<path d="M46 62 C 44 46, 50 34, 48 18" class="art__stem"/>
<path d="M47 48 C 30 46, 20 36, 18 28 C 30 28, 42 34, 47 48 Z" class="art__leaf"/>
<path d="M48 36 C 62 32, 70 22, 70 14 C 60 16, 50 24, 48 36 Z" class="art__leaf"/>
<g transform="translate(48 16)" class="art__bloom">
<ellipse cx="0" cy="-6" rx="4" ry="6"/>
<ellipse cx="0" cy="-6" rx="4" ry="6" transform="rotate(72)"/>
<ellipse cx="0" cy="-6" rx="4" ry="6" transform="rotate(144)"/>
<ellipse cx="0" cy="-6" rx="4" ry="6" transform="rotate(216)"/>
<ellipse cx="0" cy="-6" rx="4" ry="6" transform="rotate(288)"/>
<circle r="3.4" class="art__heart"/>
</g>
</svg>
<p class="packet__variety">Variety: <span data-note-name></span></p>
<p class="packet__note" data-note></p>
<div class="packet__foot">
<span class="packet__window" data-seeds>
<i></i><i></i><i></i><i></i><i></i><i></i>
</span>
<span class="packet__sow">Sow today, blooms in two days</span>
</div>
</div>
</div>
</div>
<!-- what stays once the packet is in the soil -->
<div class="done" data-done>
<p class="done__title">Message planted</p>
<p class="done__text">It is in the pot by the window. We water every message within two days.</p>
<button class="again" type="button" data-again>Write another</button>
</div>
</div>
<p class="sr" aria-live="polite" data-live></p>
</section>
</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.
41 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 greenhouse contact form” from Animorio and fit it into my siteGlass, frame, garden, light beams, sill, the two little pots and the big pot are fixed paths in a 300 by 360 SVG; the gradients have stops with a class and the colour in the CSS, so the Playground palette can paint them again. The condensation is twenty-six droplets the script makes.
Every leaf is a group on its node of the stem (a point taken with getPointAtLength, mirrored for the left side) with a few numbers: grown, wilted, yellow, fallen, and the wind on it. On every GSAP tick the script works out the petiole's angle and length, draws it as a curve from the node to its tip and puts the blade exactly on the tip, turned and scaled: while it is a sprout it is small, pointing up and rolled (vertical scale 0.14), and as it opens it unrolls to 1. So the leaf never comes off its petiole, whatever it does.
The plant sways on its pot and every leaf shivers on two damped springs integrated in the ticker (SPRING sets stiffness and damping): when a leaf opens the plant gets a push and the other leaves a smaller one; while the water falls, now and then a drop touches one.
On every input the field is checked (a name of at least two letters, an email with an @ and an ending like .com, a message of at least ten letters): while it is still wrong the sprout grows a little with the letters, when it is right the leaf opens in GROW seconds with elastic.out. Leaving a wrong field the leaf turns yellow, wilts by DROOP degrees with bounce.out and then drops: the whole unit, petiole included, falls to the sill turning and fades, and the scar appears on the node. The field gets aria-invalid and its error text tied with aria-describedby.
A timeline folds the labels away, brings up the seed packet (HTML with its drawing in SVG), closes the flap with rotationX inside a CSS perspective and rattles the seeds; then it measures where the stick in the pot is and flies the packet there, where it becomes the small SVG one. The watering can comes in and tips: from the rose, worked out from its rotation, seven threads start (quadratic curves with a running dash) and DROPS drops along the same curves, with splashes on the soil, for RAIN seconds; the soil darkens, the leaves lift and the six petals open one after the other with back.out.
| Name | File | Default | What it changes |
|---|---|---|---|
GROW | script.js | 1.2 | How many seconds a leaf takes to unroll and open. |
SPRING | script.js | 0.38 | How springy the plant and the leaves are: lower sways more. |
DROOP | script.js | 70 | How many degrees a thirsty leaf droops before it drops. |
SWAY | script.js | 5 | How many degrees the plant sways when a leaf opens. |
RAIN | script.js | 1.8 | How many seconds the watering can pours. |
DROPS | script.js | 28 | How many drops are in the air while it pours. |
WAIT | script.js | 0.5 | How many seconds the pretend sending takes. |
TYPE_SPEED | script.js | 0.07 | Seconds per letter when the preview writes by itself. |
For the contact page of a plant nursery, a florist, a vegetable garden, a landscape designer or any site about plants: the form becomes the part people remember.
The card stays in two half columns, the greenhouse and the fields: the plant, the leaves, the packet and the watering can are the same; the error text goes under the field, the drawing on the packet goes and the type tightens, so it all fits a 341 by 410 box.
With reduced motion the leaves change pose at once and fade in, with no springs or swaying; a wrong leaf turns yellow and fades instead of falling. Sent, the packet appears already closed and fades, no flight and no watering can: the soil darkens and the flower appears open in a fade.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0, Caveat (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 17 KB; the libraries: GSAP 28 KB.
Tested on 22 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.