
Words that melt into each other
Text, Scroll, SVG
NewFree
Scroll inside the preview
The page of an invented postal service: at the top a sheet of airmail paper folds itself, in 3D, into a paper plane. As you scroll the plane flies down the page on a curved route, banks in the turns, brushes past the text blocks that step aside on a spring, loops the loop and leaves a dashed red and blue trail; at the bottom it glides into a mailbox that shuts and raises its flag.
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>L'aeroplanino di carta</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="barra">
<a class="marchio" href="#" aria-label="Paperwing Post, home">
<svg class="marchio__segno" viewBox="0 0 40 40" aria-hidden="true">
<rect class="marchio__bordo" x="3" y="3" width="34" height="34" rx="3" />
<path class="marchio__ala" d="M9 21L31 11L22 30L19 23Z" />
<path class="marchio__piega" d="M19 23L31 11" />
</svg>
<span>Paperwing Post</span>
</a>
<nav class="voci" aria-label="Main">
<a class="voce" href="#rotta">Rates</a>
<a class="voce" href="#giro">Express</a>
<a class="voce" href="#arrivo">Track</a>
</nav>
</header>
<main>
<!-- the opening: the sheet of paper folds itself into a plane -->
<section class="apertura" data-apertura>
<div class="apertura__testo">
<p class="occhiello" data-entra>Airmail without the airport</p>
<h1 class="titolo">
<span class="riga"><span data-riga>Letters that</span></span>
<span class="riga"><span data-riga><em>fly themselves.</em></span></span>
</h1>
<p class="sotto" data-entra>Write it, fold it, let it go. Paperwing Post handles the wind, the weather and the last ten metres to the door.</p>
<div class="azioni" data-entra>
<a class="pillola" href="#arrivo"><span class="pillola__fondo"></span><span class="pillola__testo">Send a letter</span></a>
<p class="cenno" data-cenno><span>Scroll to launch</span>
<svg viewBox="0 0 24 32" aria-hidden="true"><path d="M12 4V27M5 20L12 27L19 20" /></svg>
</p>
</div>
</div>
<div class="apertura__foglio" data-posto-foglio aria-hidden="true"></div>
</section>
<!-- the route: the plane brushes past these blocks and they step aside -->
<section class="rotta" id="rotta" data-rotta>
<article class="blocco blocco--sx" data-blocco>
<div class="blocco__corpo" data-corpo>
<svg class="timbro" viewBox="0 0 80 80" aria-hidden="true" data-timbro>
<circle cx="40" cy="40" r="30" /><circle cx="40" cy="40" r="23" />
<text x="40" y="44">FOLDED</text>
</svg>
<h2 data-entra-blocco>Folded by hand</h2>
<p data-entra-blocco>Every letter leaves the counter as a paper plane: two creases, a sharp nose and a last look against the light.</p>
</div>
</article>
<article class="blocco blocco--dx" data-blocco>
<div class="blocco__corpo" data-corpo>
<svg class="timbro" viewBox="0 0 80 80" aria-hidden="true" data-timbro>
<circle cx="40" cy="40" r="30" /><circle cx="40" cy="40" r="23" />
<text x="40" y="44">ROUTED</text>
</svg>
<h2 data-entra-blocco>Routed by the wind</h2>
<p data-entra-blocco>Our planes read the breeze, not the map. Expect a few curves on the way: they still arrive before the bus.</p>
</div>
</article>
<article class="blocco blocco--sx" data-blocco>
<div class="blocco__corpo" data-corpo>
<svg class="timbro" viewBox="0 0 80 80" aria-hidden="true" data-timbro>
<circle cx="40" cy="40" r="30" /><circle cx="40" cy="40" r="23" />
<text x="40" y="44">WAXED</text>
</svg>
<h2 data-entra-blocco>Weatherproof wings</h2>
<p data-entra-blocco>Waxed paper, a rounded nose and a tail that forgives. Rain slows us down, but only a little.</p>
</div>
</article>
<article class="blocco blocco--dx" data-blocco>
<div class="blocco__corpo" data-corpo>
<svg class="timbro" viewBox="0 0 80 80" aria-hidden="true" data-timbro>
<circle cx="40" cy="40" r="30" /><circle cx="40" cy="40" r="23" />
<text x="40" y="44">TRACKED</text>
</svg>
<h2 data-entra-blocco>Tracked to the door</h2>
<p data-entra-blocco>Look up. If it is folded, striped and flying low over your street, it is probably yours.</p>
</div>
</article>
</section>
<!-- the express service: the plane loops the loop here -->
<section class="giro" id="giro" data-giro>
<div class="giro__testo" data-blocco>
<div class="blocco__corpo" data-corpo>
<p class="occhiello" data-entra-blocco>Express</p>
<h2 data-entra-blocco>One loop the loop, <em>on the house.</em></h2>
<p data-entra-blocco>Express letters take the scenic route for a second. Nobody has ever asked for a refund.</p>
</div>
</div>
<div class="giro__spazio" data-giro-spazio aria-hidden="true"></div>
</section>
<!-- the landing: a glide into the mailbox, the door shuts and the flag goes up -->
<section class="arrivo" id="arrivo" data-arrivo>
<div class="arrivo__testo" data-blocco>
<div class="blocco__corpo" data-corpo>
<p class="occhiello" data-entra-blocco>Last ten metres</p>
<h2 data-entra-blocco>Straight into <em>the box.</em></h2>
<p data-entra-blocco>No knocking, no missed deliveries. The plane glides in, the door shuts and the flag goes up.</p>
<svg class="bollo" data-bollo viewBox="0 0 150 80" aria-hidden="true">
<rect x="4" y="8" width="142" height="64" rx="8" />
<rect x="11" y="15" width="128" height="50" rx="5" />
<text x="75" y="48">DELIVERED</text>
</svg>
</div>
</div>
<div class="arrivo__cassetta" data-posto-cassetta aria-hidden="true"></div>
</section>
<footer class="piede">
<p><strong>Paperwing Post</strong> Letters by air, folded by hand.</p>
</footer>
</main>
<!-- the sky over the whole page: the trail, the plane and the mailbox are drawn here by the script -->
<svg class="cielo" data-cielo aria-hidden="true">
<defs>
<pattern id="ap-righe" width="22" height="22" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<rect width="22" height="22" class="righe__carta" />
<rect width="6" height="22" class="righe__rosso" />
<rect x="11" width="6" height="22" class="righe__blu" />
</pattern>
<!-- the letter printed on the front of the sheet, in sheet units: 200 wide, 300 tall -->
<g id="ap-lettera">
<path class="lettera__bordo" fill-rule="evenodd" d="M-100 0H100V300H-100ZM-91 9V291H91V9Z" />
<rect class="lettera__etichetta" x="-80" y="24" width="62" height="20" rx="2" />
<text class="lettera__via" x="-49" y="38">BY AIR MAIL</text>
<g class="lettera__francobollo">
<rect x="44" y="20" width="38" height="46" />
<rect class="francobollo__dentro" x="49" y="25" width="28" height="36" />
<path class="francobollo__ala" d="M53 49L73 39L66 56L63 50Z" />
</g>
<text class="lettera__saluto" data-saluto x="-76" y="132">Dear Ada,</text>
<path class="lettera__righe" d="M-76 156c22-4 44 3 66-1s40 2 62-1M-76 176c26-3 50 3 76 0s34 2 52-1M-76 196c20-3 40 2 60 0s46 3 70-1M-76 216c30-3 58 2 86-1M-76 236c22-2 46 3 70-1s30 2 40 0" />
<text class="lettera__firma" x="70" y="272">June</text>
</g>
<!-- the back of the sheet: only the striped border -->
<g id="ap-retro"><path class="lettera__bordo" fill-rule="evenodd" d="M-100 0H100V300H-100ZM-91 9V291H91V9Z" /></g>
<clipPath id="ap-dentro" data-clip-dentro><rect data-clip-fuori x="0" y="0" width="0" height="0" /><path data-clip-bocca d="" /></clipPath>
<mask id="ap-maschera" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100" data-maschera>
<path data-scia-maschera class="scia__maschera" d="" />
</mask>
</defs>
<g mask="url(#ap-maschera)" data-scia>
<path class="scia scia--rosso" data-scia-rosso d="" />
<path class="scia scia--blu" data-scia-blu d="" />
</g>
<g data-cassetta-dietro></g>
<g data-aereo-fuori><g data-aereo></g></g>
<g data-cassetta-davanti></g>
</svg>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.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.
52 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 paper plane” from Animorio and fit it into my siteThe sheet is 200 units wide and 300 tall, with the nose at the top. The script folds half a sheet on paper: the corners to the centre, the new edges to the centre, and at every fold it cuts the pieces along the crease line and flips the ones that move, remembering the way back to the flat sheet and the list of creases that move them. Then the wing crease cuts everything one last time. Out come sixteen faces (eight per side), each drawn in the coordinates of the flat sheet.
Every crease is a rotation around a line of the flat sheet (Rodrigues’ formula): corners, second corners, halves and wings. The position of a face is the product of the rotations of its chain, from the outermost to the innermost. The plane is then turned towards the screen: from the upright sheet facing you to the flight orientation with a spherical interpolation (quaternions), and in flight with the heading, the roll and a little yaw.
Seen without perspective a flat face stays a parallelogram, so a transform matrix(a b c d e f) on an SVG group with the polygon in sheet coordinates is enough. Inside is the letter (a use cut by the face’s clipPath) when the front shows, just the striped border when the back shows, and a dark polygon whose opacity depends on how the face catches the light. The drawing order: far panels first, then on each panel the stack of paper from the hidden side to the visible one.
The route is a smooth curve (Catmull-Rom turned into Béziers) running beside every block, measured from the page, then a loop the loop and the glide into the mailbox. The script samples it every 3 pixels and for each point works out the scroll that brings it to 60% of the screen; in the loop the scroll moves evenly, so on the screen the loop is round. A single tween with ScrollTrigger and scrub carries the scroll value, and a binary search finds the point.
The trail is two dashed paths (red and blue, offset) seen through a mask with a single dash as long as the whole route: the stroke-dashoffset uncovers it up to the tail. Every frame the blocks measure their distance from the plane and move with gsap.quickTo and elastic.out. On arrival a timeline shuts the door (scaleY from -0.52 to 1 with elastic.out), jolts the mailbox, raises the flag with back.out and drops the stamp; scrolling back plays it in reverse.
| Name | File | Default | What it changes |
|---|---|---|---|
FOLD_SPEED | script.js | 1 | How quickly the sheet folds itself: 0.5 slow, 2 quick. |
PLANE_SIZE | script.js | 1.2 | The size of the plane in flight. |
LOOP_SIZE | script.js | 1 | The size of the loop the loop. |
DODGE | script.js | 56 | How many pixels the blocks step aside when the plane brushes past. |
BANK | script.js | 0.6 | How much the plane leans into the turns: 0 keeps it level. |
DASH | script.js | 9 | The length of every dash of the trail, in pixels. |
SCRUB | script.js | 0.9 | How many seconds the plane takes to catch up with the scroll: 0 follows it at once. |
--rosso | style.css | #d2412f | The airmail red: the stripes of the sheet, the trail, the flag and the stamps. |
For a page that walks the reader from one point to another: a shipping or delivery service, a newsletter, an invitation, a contact form that «takes off», a messaging app, a stationery shop, a «how it works» page that ends with the delivery.
The same animation with adapted sizes: the sheet folds under the title, the blocks are wider and the plane, smaller, flies in the free strip beside them; the loop is smaller and the mailbox sits above the closing text, so the trail never crosses the words. The blocks step aside a little less.
No folding, no flight and no smooth scrolling: the plane is already folded at the top and fades in, the trail is already drawn all the way to the mailbox, the mailbox is shut with its flag up and the «Delivered» stamp is already in place. The texts only fade in and the blocks stay still.
GSAP 3.15.0, Lenis 1.3.26, Fraunces (font variabile) 5.3.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 17 KB; the libraries: GSAP + ScrollTrigger 45 KB, Lenis 5.3 KB.
Tested on 11 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.