Vai al contenuto
animorio
EN
Tutti i componenti

Il modulo contatti della serra

Moduli, Fisica, SVGGSAP, Solo CSSImpegnativaPeso compresso: Demo 17 KBGSAP 28 KBGratisScarica lo zip

Nello stile ricco, colorato e pieno di cose: il modulo contatti di una serra, Fernhouse. A sinistra un angolo di serra con i vetri a riquadri, la luce che entra, il davanzale con due vasetti e un filodendro in un grande vaso di terracotta; a destra i campi, cartellini di legno da vaso. Mentre scrivi spunta un germoglio arrotolato dal nodo dello stelo; quando il cartellino è giusto il picciolo si allunga e la foglia si srotola e si apre su una molla; se è sbagliato la foglia ingiallisce, si affloscia e cade dal nodo lasciando una cicatrice. Inviato, il messaggio viene chiuso in un pacchetto di semi illustrato che vola nel vaso, un innaffiatoio lo bagna e il bocciolo si apre petalo per petalo.

Il codice

Una cartella che funziona da sola: index.html, style.css, script.js e le librerie da CDN. Nelle schede React e Vue la stessa demo è già un componente, che spegne le sue animazioni quando esce dalla pagina.

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

Cambia valori, colori e testi con le manopole e guarda l’animazione rispondere dal vivo. Oppure scrivi tu nel codice. Poi scarica la cartella con le tue modifiche.

41 valori da cambiare

Oppure chiedila a Claude Code

Col connettore di Animorio, Claude Code la prende da qui con tutta la sua guida e la adatta al tuo progetto. Incolla questa richiesta:

Prendi «Il modulo contatti della serra» da Animorio e adattala al mio sito

Cosa fa

Al centro di una pagina bianca una scheda color crema. A sinistra un angolo di serra: vetri a riquadri in un telaio di ferro verde, il giardino che si vede fuori, tre fasci di luce, qualche goccia di condensa, un davanzale di legno con un vasetto di echeveria e uno di cactus e, nel mezzo, un grande vaso di terracotta con i segni del tornio, il piattino, il terriccio coi sassolini e il muschio. Ci cresce un filodendro: uno stelo coi nodi, due foglie a cuore in basso con il loro picciolo, nervature, due toni e un filo di luce sul bordo, e un bocciolo rosa in cima. A destra il titolo Write to the greenhouse, tre cartellini di legno con le venature, un nodo, il foro e lo spago, Your name, Email e What is growing on your mind?, e la pillola verde Plant message. Mentre scrivi, dal nodo del campo spunta un germoglio arrotolato che cresce con le lettere; appena il cartellino è giusto il picciolo si allunga e la foglia si srotola e si apre su una molla, la pianta ondeggia e le altre foglie tremano. Se lasci un cartellino sbagliato il cartellino trema e dice cosa manca (per esempio Add the end, like .com), la sua foglia ingiallisce, si affloscia appesa al picciolo e poi cade dal nodo fino al davanzale, lasciando una piccola cicatrice; correggendo spunta un germoglio nuovo. Premendo Plant message i cartellini si ripiegano e sale un pacchetto di semi illustrato, col nome del vivaio, il disegno della pianta, Variety: a message from e il nome, il messaggio scritto a penna e una finestrella coi semi: l’aletta si chiude, i semi ballano, e il pacchetto vola nel vaso e si pianta su un bastoncino. Arriva un grande innaffiatoio azzurro, si inclina e dalla rosa escono tanti filetti d’acqua con le gocce e gli schizzi sul terriccio, che si scurisce bagnato; le foglie si alzano bevendo e il bocciolo si apre petalo per petalo in un fiore rosa. A destra resta Message planted con Write another, che riporta tutto all’inizio.

Come funziona

La serra e il vaso, fissi in SVG

Vetri, telaio, giardino, fasci di luce, davanzale, i due vasetti e il vaso grande sono tracciati fissi in un SVG di 300 per 360; le sfumature hanno gli stop con una classe e il colore nel CSS, così la Palette del Playground li ricolora. La condensa sono ventisei goccioline create dallo script.

Ogni foglia ridisegnata a ogni fotogramma

Ogni foglia è un gruppo sul suo nodo dello stelo (punto preso con getPointAtLength, specchiato per il lato sinistro) e ha pochi numeri: cresciuta, afflosciata, gialla, caduta, e il vento su di lei. A ogni tick di GSAP lo script calcola l’angolo e la lunghezza del picciolo, lo disegna come una curva dal nodo alla punta e mette la lamina esattamente sulla punta, ruotata e scalata: mentre è un germoglio è piccola, rivolta in alto e arrotolata (scala verticale 0,14), aprendosi si srotola fino a 1. Così la foglia non si stacca mai dal suo picciolo, qualunque cosa faccia.

Molle per la pianta e per le foglie

La pianta ondeggia sul vaso e ogni foglia trema su due molle smorzate integrate nel ticker (SPRING regola rigidità e smorzamento): quando una foglia si apre la pianta riceve una spinta e le altre foglie una più piccola; mentre l’acqua cade, ogni tanto una goccia ne tocca una.

Germoglio, foglia, foglia secca

A ogni input il campo viene controllato (nome di almeno due lettere, email con chiocciola e fine come .com, messaggio di almeno dieci lettere): se è ancora sbagliato il germoglio cresce un poco con le lettere, se è giusto la foglia si apre in GROW secondi con elastic.out. Lasciando un campo sbagliato la foglia diventa gialla, si affloscia di DROOP gradi con bounce.out e poi cade: tutta l’unità, picciolo compreso, scende al davanzale ruotando e svanisce, e sul nodo compare la cicatrice. Il campo prende aria-invalid e il testo d’errore legato con aria-describedby.

Il pacchetto, l’innaffiatoio, il fiore

Una timeline ripiega i cartellini, fa salire il pacchetto di semi (HTML con il disegno in SVG), chiude l’aletta con rotationX in una prospettiva CSS e fa ballare i semi; poi misura dove sta il bastoncino nel vaso e ci fa volare il pacchetto, che lì diventa quello piccolo in SVG. L’innaffiatoio entra e si inclina: dalla rosa, calcolata dalla sua rotazione, partono sette filetti (curve quadratiche col tratteggio che scorre) e DROPS gocce lungo le stesse curve, con gli schizzi sul terriccio, per RAIN secondi; il terriccio si scurisce, le foglie si alzano e i sei petali si aprono uno dopo l’altro con back.out.

Valori da cambiare

NomeFileDi partenzaCosa cambia
GROWscript.js1.2Quanti secondi ci mette una foglia a srotolarsi e aprirsi.
SPRINGscript.js0.38Quanto sono elastiche la pianta e le foglie: più basso, più ondeggiano.
DROOPscript.js70Di quanti gradi si affloscia una foglia assetata prima di cadere.
SWAYscript.js5Di quanti gradi ondeggia la pianta quando si apre una foglia.
RAINscript.js1.8Quanti secondi versa l’innaffiatoio.
DROPSscript.js28Quante gocce ci sono in aria mentre versa.
WAITscript.js0.5Quanti secondi dura l’invio finto.
TYPE_SPEEDscript.js0.07Secondi per lettera quando l’anteprima scrive da sola.

Provali nel Playground

Quando usarla

Per la pagina dei contatti di un vivaio, di un fiorista, di un orto, di un paesaggista o di qualsiasi sito che parla di piante: il modulo diventa la parte che si ricorda.

Sul telefono

La scheda resta su due colonne a metà, la serra e i campi: la pianta, le foglie, il pacchetto e l’innaffiatoio sono gli stessi; il testo d’errore va sotto il campo, il disegno sul pacchetto si toglie e i caratteri si stringono, così tutto sta in una casella di 341 per 410.

Con il movimento ridotto

Con il movimento ridotto le foglie cambiano posa di colpo e compaiono in dissolvenza, senza molle né ondeggiare; la foglia sbagliata ingiallisce e svanisce invece di cadere. Inviato, il pacchetto compare già chiuso e svanisce, niente volo né innaffiatoio: il terriccio si scurisce e il fiore compare aperto in dissolvenza.

Trappole

  • Il percorso dello stelo parte dal terriccio: getPointAtLength(0) è la base, non la cima (la prima prova aveva il fiore nel vaso e le foglie al contrario).
  • Una foglia animata con transform-origin su un gruppo dentro gruppi già spostati scivola via dal suo picciolo: qui la lamina si ricalcola a ogni fotogramma sulla punta del picciolo, così resta attaccata.
  • Un’opacità messa nel CSS vince sull’attributo opacity dell’SVG: l’innaffiatoio restava invisibile; lo script la scrive in style.
  • Il pacchetto si ingrandisce dal basso (transform-origin 50% 100%) per salire bene, ma per volare nel vaso l’origine torna al centro, se no arriva più in basso del bastoncino.
  • const top in uno script classico va in conflitto con window.top: il nome è stemTop.
  • Gli stop delle sfumature SVG con stop-color="var(--…)" come attributo restano neri: gli stop hanno una classe e il colore sta nel CSS.

Librerie

GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0, Caveat (font variabile) 5.3.0.

Compressi con gzip, i tre file della demo pesano 17 KB; le librerie: GSAP 28 KB.

Collaudo

Provata il 22 ottobre 2026

  • Computer: Fatto
  • Telefono: Fatto
  • Movimento ridotto: Fatto
  • Safari su iPhone: Da fare

Licenza d’uso

Puoi usarla nei tuoi progetti e in quelli dei tuoi clienti. Non puoi rivenderla né ridistribuirla così com’è. GSAP resta sotto la sua licenza Standard. Licenza completa.

Tutti i componenti