
The star rating
Ratings, SVG, Hover
NewFree
In the rich style, colourful and full of things: the contact form of a seaside inn, The Driftwood Inn. On the left a corner of the beach at sunset, with the sun going down, the waves, a sailing boat on the horizon and the water's edge washing up and back; on the sand an empty bottle, its cork, a coil of twine and a sheet of ruled paper. While you write the letter fills with ink: the words of the message become lines of handwriting with the nib running at the end, the email becomes the return address with a sand dollar stamp, the name the signature with its flourish. Sent, the sheet rolls up, is tied with twine and slides into the bottle, the cork flies into the neck, the bottle tips over, a wave runs up the beach, picks it up and carries it out towards the sun until it is a glint.
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 message in a bottle 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 seaside inn: beside it a corner of the beach at sunset, with an empty bottle standing in
the sand, its cork and a sheet of paper. While you write, the letter fills with ink: the message line by line
with a nib running at the end, the email as the return address with a sand dollar stamp, the name as the
signature. Sent, the letter rolls up, is tied with twine and slides into the bottle, the cork flies in, the
bottle tips over, rolls to the water and drifts away on the waves to the sun -->
<section class="mare" aria-labelledby="mare-title" data-mare data-autoclic>
<div class="mare__scene" aria-hidden="true">
<svg class="scene" viewBox="0 0 300 360" data-svg>
<defs>
<linearGradient id="mare-sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="stop-sky-top"/>
<stop offset="1" class="stop-sky"/>
</linearGradient>
<linearGradient id="mare-sea" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="stop-sea-far"/>
<stop offset="1" class="stop-sea"/>
</linearGradient>
<linearGradient id="mare-sand" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="stop-sand-wet"/>
<stop offset="0.16" class="stop-sand"/>
<stop offset="1" class="stop-sand-low"/>
</linearGradient>
<linearGradient id="mare-glass" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" class="stop-glass-dark"/>
<stop offset="0.3" class="stop-glass-light"/>
<stop offset="0.55" class="stop-glass"/>
<stop offset="1" class="stop-glass-dark"/>
</linearGradient>
<radialGradient id="mare-sun" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" class="stop-sun"/>
<stop offset="0.45" class="stop-sun"/>
<stop offset="1" class="stop-sun-glow"/>
</radialGradient>
<linearGradient id="mare-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="mare-room">
<rect x="0" y="0" width="300" height="360" rx="18"/>
</clipPath>
<clipPath id="mare-above">
<rect x="0" y="0" width="300" height="170"/>
</clipPath>
</defs>
<g clip-path="url(#mare-room)">
<!-- the sky at sunset, two clouds drifting -->
<rect x="0" y="0" width="300" height="172" fill="url(#mare-sky)"/>
<g class="clouds" data-clouds>
<g data-cloud="0" transform="translate(40 52)">
<path class="cloud" d="M0 14 C 0 6, 9 2, 16 6 C 20 -2, 34 -3, 39 5 C 46 2, 56 7, 54 14 Z"/>
<path class="cloud__light" d="M8 10 C 14 7, 20 7, 26 9"/>
</g>
<g data-cloud="1" transform="translate(196 34)">
<path class="cloud" d="M0 11 C 1 5, 8 2, 13 5 C 17 -1, 28 -1, 31 5 C 37 3, 44 7, 42 11 Z"/>
</g>
</g>
<!-- the sun going down behind the horizon -->
<g clip-path="url(#mare-above)">
<circle cx="220" cy="160" r="46" fill="url(#mare-sun)" class="sun__glow"/>
<circle cx="220" cy="160" r="20" class="sun"/>
</g>
<!-- the sea: a band that darkens towards the beach, a sailing boat on the horizon, the glints under the sun -->
<rect x="0" y="168" width="300" height="96" fill="url(#mare-sea)"/>
<path d="M0 168.5 H300" class="horizon"/>
<g class="boat" data-boat transform="translate(56 166)">
<path d="M-7 0 H7 L5 2.6 H-5 Z" class="boat__hull"/>
<path d="M0 -0.6 V-15" class="boat__mast"/>
<path d="M0.8 -14 L8 -2 H0.8 Z" class="boat__sail"/>
<path d="M-0.8 -11 L-6 -2 H-0.8 Z" class="boat__jib"/>
</g>
<g class="glints" data-glints></g>
<g class="waves" data-waves></g>
<!-- the bottle out at sea, drawn again small while it drifts -->
<g data-floater opacity="0">
<g data-floater-tilt>
<path class="bottle__body" d="M-22 -9 C -22 -12, -19 -13, -16 -13 H6 C 10 -13, 12 -10, 14 -6 H22 V6 H14 C 12 10, 10 13, 6 13 H-16 C -19 13, -22 12, -22 9 Z"/>
<rect x="22" y="-4.5" width="7" height="9" rx="2" class="cork"/>
<rect x="-16" y="-4" width="26" height="8" rx="4" class="roll__paper"/>
<path d="M-18 -9 H8" class="bottle__shine"/>
</g>
<path d="M-26 14 C -16 10, 16 10, 28 14" class="floater__ripple"/>
</g>
<!-- the beach: the water's edge that washes up and back, wet sand, then the dry sand -->
<rect x="0" y="240" width="300" height="120" fill="url(#mare-sand)"/>
<path class="wash" data-wash d="M0 248 C 60 252, 120 246, 180 250 C 230 253, 270 247, 300 250 V 236 H 0 Z"/>
<path class="foam" data-foam d="M0 248 C 60 252, 120 246, 180 250 C 230 253, 270 247, 300 250"/>
<g class="grains" data-grains></g>
<!-- things on the sand: a piece of driftwood, two shells, pebbles, a coil of twine -->
<g class="drift" transform="translate(196 342) rotate(-6)">
<path d="M-70 -7 C -50 -12, 10 -11, 52 -8 C 60 -7, 64 -3, 62 2 C 40 6, -30 7, -66 5 C -74 4, -76 -4, -70 -7 Z" fill="url(#mare-wood)"/>
<path d="M-60 -4 C -30 -6, 10 -6, 48 -4 M-50 1 C -20 0, 20 0, 44 2 M-8 -8 C -2 -6, 4 -6, 8 -8" class="drift__grain"/>
<ellipse cx="22" cy="-2" rx="4" ry="2.2" class="drift__knot"/>
</g>
<g class="shell" transform="translate(26 318) rotate(-14)">
<path d="M0 0 C -9 -2, -13 -11, -9 -17 C -5 -22, 5 -22, 9 -17 C 13 -11, 9 -2, 0 0 Z" class="shell__body"/>
<path d="M0 -1 L-7 -16 M0 -1 L-3 -19 M0 -1 L1 -20 M0 -1 L5 -18 M0 -1 L8 -14" class="shell__rib"/>
<path d="M-4 0 H4 L3 3 H-3 Z" class="shell__hinge"/>
</g>
<g class="shell shell--small" transform="translate(278 300) rotate(22) scale(0.7)">
<path d="M0 0 C -9 -2, -13 -11, -9 -17 C -5 -22, 5 -22, 9 -17 C 13 -11, 9 -2, 0 0 Z" class="shell__body"/>
<path d="M0 -1 L-7 -16 M0 -1 L-3 -19 M0 -1 L1 -20 M0 -1 L5 -18 M0 -1 L8 -14" class="shell__rib"/>
</g>
<ellipse cx="128" cy="344" rx="7" ry="4" class="pebble"/>
<ellipse cx="139" cy="347" rx="4" ry="2.6" class="pebble pebble--light"/>
<ellipse cx="56" cy="352" rx="5" ry="3" class="pebble pebble--light"/>
<!-- the bottle on the sand: shadow, the glass behind, the letter when it goes in, the glass in front -->
<g data-bottle-shadow>
<ellipse cx="78" cy="309" rx="22" ry="5" class="bottle__shadow"/>
</g>
<g data-bottle>
<g data-bottle-turn>
<path class="bottle__back" d="M64 308 C 60 308, 58 305, 58 301 V 256 C 58 246, 66 240, 72 236 V 222 H 84 V 236 C 90 240, 98 246, 98 256 V 301 C 98 305, 96 308, 92 308 Z"/>
<g data-inside></g>
<path class="bottle__body" d="M64 308 C 60 308, 58 305, 58 301 V 256 C 58 246, 66 240, 72 236 V 222 H 84 V 236 C 90 240, 98 246, 98 256 V 301 C 98 305, 96 308, 92 308 Z"/>
<path d="M71 222 H85 V 219 C 85 217.5, 84 217, 82.5 217 H 73.5 C 72 217, 71 217.5, 71 219 Z" class="bottle__lip"/>
<path d="M63 258 C 63 250, 67 246, 72 242" class="bottle__shine"/>
<path d="M63 266 V 298" class="bottle__shine"/>
<path d="M93 270 V 296" class="bottle__shine bottle__shine--thin"/>
<path d="M58 300 C 66 304, 90 304, 98 300" class="bottle__base"/>
<!-- a scrap of an old label -->
<path d="M64 270 L 92 268 L 91 284 L 66 285 Z" class="bottle__label"/>
<path d="M68 275 H 86 M 69 279 H 82" class="bottle__label-line"/>
<g data-cork-in opacity="0">
<path d="M72 223 C 72 213, 73 210, 74 209 H 82 C 83 210, 84 213, 84 223 Z" class="cork"/>
<path d="M74 213 H82 M73.5 217 H82.5" class="cork__dots"/>
</g>
</g>
</g>
<!-- the cork and the twine lying on the sand -->
<g data-cork transform="translate(118 312) rotate(70)">
<path d="M-5 -8 L 5 -8 L 6 8 L -6 8 Z" class="cork"/>
<ellipse cx="0" cy="-8" rx="5" ry="1.8" class="cork__top"/>
<circle cx="-1.5" cy="-2" r="0.7" class="cork__dot"/>
<circle cx="2" cy="2" r="0.6" class="cork__dot"/>
<circle cx="-2" cy="4.5" r="0.6" class="cork__dot"/>
</g>
<g class="twine" transform="translate(110 330)">
<ellipse cx="0" cy="0" rx="9" ry="3.4" class="twine__coil"/>
<ellipse cx="0" cy="-1.2" rx="6" ry="2.2" class="twine__coil"/>
<path d="M8 1 C 14 3, 18 1, 22 4" class="twine__end"/>
</g>
<!-- the letter on a flat stone: ruled paper, the return address, the stamp, the ink lines, the signature -->
<g data-letter-shadow>
<path d="M146 268 L 282 262 L 286 342 L 150 348 Z" class="letter__shadow"/>
</g>
<g data-letter transform="translate(214 302) rotate(-3)">
<g data-paper>
<path d="M-66 -40 L 66 -40 L 66 40 C 40 42, -40 42, -66 40 Z" class="paper"/>
<path d="M-66 -40 L 66 -40 L 66 -34 L -66 -34 Z" class="paper__top"/>
<g class="paper__rules">
<path d="M-58 -16 H58 M-58 -6 H58 M-58 4 H58 M-58 14 H58 M-58 24 H58"/>
</g>
<path d="M-52 -40 V 40" class="paper__margin"/>
<text x="-47" y="-25" class="paper__from" data-from></text>
<g data-stamp transform="translate(52 -24)" opacity="0">
<circle r="9" class="stamp"/>
<g class="stamp__petals">
<ellipse cx="0" cy="-4" rx="1.4" ry="3"/>
<ellipse cx="0" cy="-4" rx="1.4" ry="3" transform="rotate(72)"/>
<ellipse cx="0" cy="-4" rx="1.4" ry="3" transform="rotate(144)"/>
<ellipse cx="0" cy="-4" rx="1.4" ry="3" transform="rotate(216)"/>
<ellipse cx="0" cy="-4" rx="1.4" ry="3" transform="rotate(288)"/>
</g>
<circle r="11.5" class="stamp__ring"/>
</g>
<g data-lines></g>
<g data-blots></g>
<text x="56" y="35" class="paper__sign" text-anchor="end" data-sign></text>
<path d="M10 37 C 24 40, 40 38, 58 36" class="paper__flourish" data-flourish/>
<g data-nib opacity="0">
<path d="M0 0 L 4 -11 L 7 -10 Z" class="nib"/>
<path d="M4 -11 L 14 -34" class="nib__quill"/>
<path d="M14 -34 C 8 -30, 6 -22, 5 -14 M14 -34 C 18 -28, 16 -20, 8 -12" class="nib__vane"/>
</g>
</g>
<!-- the roll the letter becomes -->
<g data-roll opacity="0">
<rect x="-6" y="-40" width="12" height="80" rx="6" class="roll__paper"/>
<ellipse cx="0" cy="-40" rx="6" ry="2.4" class="roll__end"/>
<path d="M0 -40 m -3 0 a 3 1.2 0 1 0 6 0 a 1.6 0.6 0 1 0 -3 0" class="roll__spiral"/>
<g data-tie opacity="0">
<path d="M-6.4 -3 H6.4 M-6.4 1 H6.4" class="tie"/>
<path d="M5 -1 C 12 -6, 14 2, 18 -2 M5 -1 C 11 4, 14 8, 19 6" class="tie"/>
</g>
</g>
</g>
<rect x="0.5" y="0.5" width="299" height="359" rx="17.5" class="scene__edge"/>
</g>
</svg>
</div>
<div class="mare__side">
<form class="mare__form" novalidate data-form>
<p class="mare__kicker">The Driftwood Inn</p>
<h1 class="mare__title" id="mare-title">Write to us by sea</h1>
<div class="field" data-field="name">
<label class="field__label" for="mare-name">Your name</label>
<input class="field__input" id="mare-name" name="name" type="text" autocomplete="name" aria-describedby="mare-err-name" data-input>
<span class="field__err" id="mare-err-name" data-err></span>
</div>
<div class="field" data-field="email">
<label class="field__label" for="mare-email">Email</label>
<input class="field__input" id="mare-email" name="email" type="email" autocomplete="email" inputmode="email" aria-describedby="mare-err-email" data-input>
<span class="field__err" id="mare-err-email" data-err></span>
</div>
<div class="field" data-field="message">
<label class="field__label" for="mare-message">Message</label>
<textarea class="field__input" id="mare-message" name="message" rows="2" aria-describedby="mare-err-message" data-input></textarea>
<span class="field__err" id="mare-err-message" data-err></span>
</div>
<div class="mare__foot">
<button class="send" type="submit" data-send>
<svg class="send__wave" viewBox="0 0 22 14" aria-hidden="true">
<path d="M1 9 C 4 5, 7 5, 9 8 C 11 11, 14 11, 16 8 C 18 5, 20 5, 21 6"/>
<path d="M1 13 C 4 10, 7 10, 9 12 C 11 14, 14 14, 16 12" class="send__wave-low"/>
</svg>
<span class="send__word" data-word>Send by sea</span>
</button>
<span class="mare__note">We answer within a day.</span>
</div>
</form>
<!-- what stays once the bottle is at sea -->
<div class="done" data-done>
<p class="done__title">Your bottle is at sea</p>
<p class="done__text">It will reach the inn with the next tide. We write back to <span data-done-mail></span>.</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.
37 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 message in a bottle contact form” from Animorio and fit it into my siteThe beach is a 300 by 360 SVG: sky, sun, sea and sand are fixed, while the waves, the sun's glints and the water's edge are drawn again on every frame from GSAP's ticker with a few sines.
Every word of the message becomes a stroke of handwriting: a zig-zag curve with a loop up for the tall letters (b, d, l, t…) and down for the low ones (g, p, y…), wrapping when the line is full; the nib sits at the end of the last word.
The moving parts (letter, roll, bottle, cork, bottle at sea) each have an object of numbers: GSAP tweens them and the ticker writes them into the transform attribute, so they turn round the right point even inside groups that are already moved.
To roll up, the sheet squeezes towards its left edge (scaleX round that edge) while the roll runs from right to left; then a twin roll, drawn behind the glass, slides down into the bottle.
The wave is the water's edge itself rising: a number added to the height of the edge makes it run up to the bottle and pull back, taking it away.
| Name | File | Default | What it changes |
|---|---|---|---|
ROLL_TIME | script.js | 0.9 | How many seconds the letter takes to roll up. |
DRIFT_TIME | script.js | 4.2 | How many seconds the bottle takes to drift out to the sun. |
WAVE_HEIGHT | script.js | 1 | How high the waves on the sea are: 0 is a flat calm. |
SURF | script.js | 3 | How many pixels the water's edge washes up and back. |
INK_WIDTH | script.js | 2.5 | How wide one letter of handwriting is on the paper. |
WAIT | script.js | 0.4 | How many seconds the pretend sending lasts before the letter rolls up. |
TYPE_SPEED | script.js | 0.07 | Seconds per letter when the preview writes by itself. |
The contact page of a seaside hotel or holiday home, a beach club, a sailing school, a beach bar: where writing in should feel like a small holiday gesture.
On the phone the beach and the form stay side by side, the beach narrower; the errors go under the field and the line «We answer within a day» disappears to make room.
With reduced motion the sea stands still, the ink, the stamp and the signature appear with no springs; sent, the letter and the bottle on the sand fade and the bottle appears already out at sea.
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 15 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.