Skip to content
animorio
IT
All components

The greenhouse contact form

Forms, Physics, SVGGSAP, CSS onlyAdvancedCompressed weight: Demo 17 KBGSAP 28 KBFreeDownload the zip

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.

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

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.

41 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 greenhouse contact form” from Animorio and fit it into my site

What it does

In the middle of a white page a cream card. On the left a corner of a greenhouse: paned glass in a green iron frame, the garden seen outside, three beams of light, a few drops of condensation, a wooden sill with a little pot of echeveria and one of cactus and, in the middle, a big terracotta pot with the marks of the wheel, its saucer, the soil with pebbles and moss. A philodendron grows in it: a stem with nodes, two heart-shaped leaves at the bottom on their petioles, with veins, two tones and a thread of light on the edge, and a pink bud on top. On the right the heading Write to the greenhouse, three wooden labels with grain, a knot, the hole and the string, Your name, Email and What is growing on your mind?, and the green Plant message pill. While you write, a rolled-up sprout pushes out of the field's node and grows with the letters; as soon as the label is right the petiole stretches and the leaf unrolls and opens on a spring, the plant sways and the other leaves shiver. If you leave a label wrong the label shakes and says what is missing (for example Add the end, like .com), its leaf turns yellow, hangs from its petiole and then drops off the node down to the sill, leaving a little scar; correcting it, a new sprout comes. Pressing Plant message the labels fold away and an illustrated seed packet comes up, with the nursery's name, a drawing of the plant, Variety: a message from and the name, the message written in pen and a little window with the seeds: the flap closes, the seeds rattle, and the packet flies into the pot and is planted on a stick. A big blue watering can comes in, tips, and threads of water with drops come out of its rose and splash on the soil, which darkens as it gets wet; the leaves lift as they drink and the bud opens petal by petal into a pink flower. On the right Message planted stays, with Write another, which brings everything back to the start.

How it works

The greenhouse and the pot, fixed in SVG

Glass, 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 drawn again on every frame

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.

Springs for the plant and the leaves

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.

Sprout, leaf, dry leaf

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.

The packet, the watering can, the flower

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.

Values to change

NameFileDefaultWhat it changes
GROWscript.js1.2How many seconds a leaf takes to unroll and open.
SPRINGscript.js0.38How springy the plant and the leaves are: lower sways more.
DROOPscript.js70How many degrees a thirsty leaf droops before it drops.
SWAYscript.js5How many degrees the plant sways when a leaf opens.
RAINscript.js1.8How many seconds the watering can pours.
DROPSscript.js28How many drops are in the air while it pours.
WAITscript.js0.5How many seconds the pretend sending takes.
TYPE_SPEEDscript.js0.07Seconds per letter when the preview writes by itself.

Try them in the Playground

When to use it

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.

On phones

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

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.

Pitfalls

  • The stem's path starts in the soil: getPointAtLength(0) is the base, not the top (the first try had the flower in the pot and the leaves upside down).
  • A leaf animated with transform-origin on a group inside groups that are already moved slides off its petiole: here the blade is worked out again on every frame on the tip of the petiole, so it stays on.
  • An opacity set in the CSS wins over the SVG opacity attribute: the watering can stayed invisible; the script writes it in style.
  • The packet grows from its bottom (transform-origin 50% 100%) to rise well, but to fly into the pot the origin goes back to the centre, otherwise it lands lower than the stick.
  • const top in a classic script clashes with window.top: the name is stemTop.
  • SVG gradient stops with stop-color="var(--…)" as an attribute stay black: the stops have a class and the colour sits in the CSS.

Libraries

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.

Testing

Tested on 22 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 components