
The card room
3D, Transitions, Images
Free
The page is a printed road map: a click lifts it off the desk and really folds it into six panels, the two side columns like an accordion and then in half. The little packet jumps, turns over in the air and opens again, panel after panel, on the page printed on its back. The next click folds it up and goes back.
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, viewport-fit=cover">
<title>The road map fold</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/standard.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage" data-stage>
<!-- the desk under the page: you only see it while the map is folded up -->
<div class="desk" aria-hidden="true">
<svg class="desk__lines" data-desk viewBox="0 0 1200 800" preserveAspectRatio="xMidYMid slice"></svg>
</div>
<div class="shadow" data-shadow aria-hidden="true"></div>
<!-- the sheet that folds: three columns and two rows of panels, hinged to each other.
The middle column holds the other two; every column holds its top and bottom panel.
The panels are filled with copies of the two pages when a fold starts (page A on the front, page B on the back). -->
<div class="rig" data-rig aria-hidden="true">
<div class="col col--m" data-col="1">
<div class="cell cell--top" data-cell data-c="1" data-r="0"></div>
<div class="cell cell--bot" data-cell data-c="1" data-r="1"></div>
<div class="col col--l" data-col="0">
<div class="cell cell--top" data-cell data-c="0" data-r="0"></div>
<div class="cell cell--bot" data-cell data-c="0" data-r="1"></div>
</div>
<div class="col col--r" data-col="2">
<div class="cell cell--top" data-cell data-c="2" data-r="0"></div>
<div class="cell cell--bot" data-cell data-c="2" data-r="1"></div>
</div>
</div>
</div>
<!-- the two real pages, the ones you read and click while the sheet lies flat -->
<div class="live" data-live>
<!-- page A: the home of a road trip guide -->
<section class="page page--a" data-page="a" aria-label="Byway, slow road trips">
<header class="bar">
<span class="brand">
<svg class="brand__mark" viewBox="0 0 32 32" aria-hidden="true">
<rect x="1" y="1" width="30" height="30" rx="9" class="brand__tile"/>
<path d="M11 30 C11 22 21 21 21 15 C21 9 12 9 13 2" class="brand__road"/>
<path d="M11 30 C11 22 21 21 21 15 C21 9 12 9 13 2" class="brand__dash"/>
</svg>
<span class="brand__name">Byway</span>
</span>
<nav class="links" aria-label="Byway">
<a href="#routes" class="link"><span class="link__plate" aria-hidden="true"></span><span class="link__text">Routes</span></a>
<a href="#stays" class="link"><span class="link__plate" aria-hidden="true"></span><span class="link__text">Stays</span></a>
<a href="#journal" class="link"><span class="link__plate" aria-hidden="true"></span><span class="link__text">Journal</span></a>
</nav>
<span class="bar__tag">Spring routes</span>
</header>
<div class="hero">
<div class="hero__text">
<p class="kicker">Slow road trips on the Northern Coast</p>
<h1 class="title">Take the <em>long way</em> round.</h1>
<p class="lede">Hand-drawn routes along the coast road, with the bakeries, lay-bys and empty beaches worth stopping for.</p>
<button class="cta" type="button" data-go data-autoclic>
<span class="cta__bg" aria-hidden="true"></span>
<span class="cta__label">Open the Coast Road</span>
<span class="cta__map" aria-hidden="true"><i></i><i></i><i></i></span>
</button>
<dl class="facts">
<div class="fact"><dt>Coast road</dt><dd>412 km</dd></div>
<div class="fact"><dt>Stops</dt><dd>7</dd></div>
<div class="fact"><dt>Days</dt><dd>6</dd></div>
</dl>
</div>
<figure class="mapcard" data-card>
<svg class="map" viewBox="0 0 600 500" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Map of the Coast Road from Wren Harbour to Saltmere">
<rect x="-600" y="-400" width="1800" height="1300" class="map__sea"/>
<g class="map__waves">
<path d="M40 70 q10 -6 20 0 t20 0"/><path d="M96 128 q10 -6 20 0 t20 0"/><path d="M30 260 q10 -6 20 0 t20 0"/>
<path d="M60 352 q10 -6 20 0 t20 0"/><path d="M150 40 q10 -6 20 0 t20 0"/><path d="M22 440 q10 -6 20 0 t20 0"/>
</g>
<path d="M54 190 C40 172 66 156 86 166 C108 176 104 202 84 206 C70 210 62 200 54 190Z" class="map__land"/>
<path d="M262 0 C244 46 196 70 204 122 C212 170 158 196 166 248 C174 300 116 326 128 380 C138 424 100 452 108 500 C112 560 90 640 96 900 H1200 V-400 H300 C286 -260 276 -90 262 0Z" class="map__land"/>
<path d="M300 -400 C286 -260 276 -90 262 0 C244 46 196 70 204 122 C212 170 158 196 166 248 C174 300 116 326 128 380 C138 424 100 452 108 500 C112 560 90 640 96 900" class="map__beach"/>
<g class="map__hills">
<ellipse cx="476" cy="150" rx="92" ry="52"/><ellipse cx="482" cy="146" rx="62" ry="34"/><ellipse cx="488" cy="142" rx="32" ry="17"/>
<ellipse cx="452" cy="378" rx="78" ry="40"/><ellipse cx="458" cy="374" rx="46" ry="22"/>
</g>
<path d="M514 236 C530 222 560 226 566 244 C584 248 586 274 568 282 C560 300 528 298 518 284 C498 284 494 252 514 236Z" class="map__wood"/>
<path d="M346 456 C356 442 384 444 390 458 C402 462 400 482 386 484 C376 496 350 494 346 480 C332 478 334 460 346 456Z" class="map__wood"/>
<ellipse cx="384" cy="262" rx="30" ry="15" class="map__lake"/>
<path d="M332 22 C304 52 254 74 262 124 C270 174 214 198 224 250 C234 302 174 328 188 380 C198 420 168 446 186 484" class="map__road" data-road/>
<path d="M332 22 C304 52 254 74 262 124 C270 174 214 198 224 250 C234 302 174 328 188 380 C198 420 168 446 186 484" class="map__dash" data-road-dash/>
<g class="map__scale"><path d="M470 470 H550"/><path d="M470 465 V475 M550 465 V475 M510 467 V473"/><text x="510" y="460">20 km</text></g>
<g class="pin" data-pin data-at="0.04"><g class="pin__body"><path d="M0 0 C-7 -10 -11 -15 -11 -21 A11 11 0 1 1 11 -21 C11 -15 7 -10 0 0Z"/><circle cy="-21" r="4.2"/></g><text x="16" y="-14">Wren Harbour</text></g>
<g class="pin" data-pin data-at="0.35"><g class="pin__body"><path d="M0 0 C-7 -10 -11 -15 -11 -21 A11 11 0 1 1 11 -21 C11 -15 7 -10 0 0Z"/><circle cy="-21" r="4.2"/></g><text x="16" y="-14">Lantern Cliffs</text></g>
<g class="pin" data-pin data-at="0.66"><g class="pin__body"><path d="M0 0 C-7 -10 -11 -15 -11 -21 A11 11 0 1 1 11 -21 C11 -15 7 -10 0 0Z"/><circle cy="-21" r="4.2"/></g><text x="16" y="-14">Gull Bay</text></g>
<g class="pin" data-pin data-at="0.88"><g class="pin__body"><path d="M0 0 C-7 -10 -11 -15 -11 -21 A11 11 0 1 1 11 -21 C11 -15 7 -10 0 0Z"/><circle cy="-21" r="4.2"/></g><text x="16" y="-14">Saltmere</text></g>
<g class="car" data-car>
<g data-car-in>
<rect x="-12" y="-6.5" width="24" height="13" rx="4.5" class="car__body"/>
<rect x="2" y="-5" width="5.5" height="10" rx="1.6" class="car__glass"/>
<rect x="-9" y="-5" width="4" height="10" rx="1.4" class="car__glass"/>
</g>
</g>
</svg>
</figure>
</div>
<div class="creases" aria-hidden="true"><i class="crease crease--v1"></i><i class="crease crease--v2"></i><i class="crease crease--h"></i></div>
</section>
<!-- page B: the guide to one route, printed on the back of the same sheet -->
<section class="page page--b is-off" data-page="b" aria-label="The Coast Road">
<header class="bar">
<span class="brand">
<svg class="brand__mark" viewBox="0 0 32 32" aria-hidden="true">
<rect x="1" y="1" width="30" height="30" rx="9" class="brand__tile"/>
<path d="M11 30 C11 22 21 21 21 15 C21 9 12 9 13 2" class="brand__road"/>
<path d="M11 30 C11 22 21 21 21 15 C21 9 12 9 13 2" class="brand__dash"/>
</svg>
<span class="brand__name">Byway</span>
</span>
<button class="back" type="button" data-back data-autoclic>
<span class="back__bg" aria-hidden="true"></span>
<svg class="back__arrow" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 12 H5 M11 5 L4 12 L11 19"/></svg>
<span class="back__label">All routes</span>
</button>
</header>
<div class="route">
<div class="route__head">
<p class="kicker">Route guide, spring edition</p>
<h2 class="title">The Coast Road</h2>
<p class="lede">Six days from Wren Harbour to Saltmere, never more than a mile from the sea.</p>
<figure class="profile">
<svg class="profile__svg" viewBox="0 0 600 170" preserveAspectRatio="none" aria-hidden="true">
<path class="profile__area" data-area/>
<path class="profile__line" data-line/>
</svg>
<figcaption class="profile__cap">
<span>Wren Harbour</span>
<span class="profile__peak" data-peak>Highest point: Lantern Cliffs, 212 m</span>
<span>Saltmere</span>
</figcaption>
</figure>
</div>
<ol class="legs">
<li class="leg"><span class="leg__day">Monday</span><span class="leg__way">Wren Harbour<svg class="leg__arrow" viewBox="0 0 40 12" aria-hidden="true"><path d="M1 6 H37 M31 1.5 L37 6 L31 10.5"/></svg>Lantern Cliffs</span><span class="leg__km">64 km</span></li>
<li class="leg"><span class="leg__day">Tuesday</span><span class="leg__way">Lantern Cliffs<svg class="leg__arrow" viewBox="0 0 40 12" aria-hidden="true"><path d="M1 6 H37 M31 1.5 L37 6 L31 10.5"/></svg>Gull Bay</span><span class="leg__km">58 km</span></li>
<li class="leg"><span class="leg__day">Wednesday</span><span class="leg__way">Gull Bay<svg class="leg__arrow" viewBox="0 0 40 12" aria-hidden="true"><path d="M1 6 H37 M31 1.5 L37 6 L31 10.5"/></svg>Fennick</span><span class="leg__km">81 km</span></li>
<li class="leg"><span class="leg__day">Thursday</span><span class="leg__way">Fennick<svg class="leg__arrow" viewBox="0 0 40 12" aria-hidden="true"><path d="M1 6 H37 M31 1.5 L37 6 L31 10.5"/></svg>Old Ferry</span><span class="leg__km">72 km</span></li>
<li class="leg"><span class="leg__day">Friday</span><span class="leg__way">Old Ferry<svg class="leg__arrow" viewBox="0 0 40 12" aria-hidden="true"><path d="M1 6 H37 M31 1.5 L37 6 L31 10.5"/></svg>Marram Dunes</span><span class="leg__km">69 km</span></li>
<li class="leg"><span class="leg__day">Saturday</span><span class="leg__way">Marram Dunes<svg class="leg__arrow" viewBox="0 0 40 12" aria-hidden="true"><path d="M1 6 H37 M31 1.5 L37 6 L31 10.5"/></svg>Saltmere</span><span class="leg__km">68 km</span></li>
</ol>
</div>
<div class="creases" aria-hidden="true"><i class="crease crease--v1"></i><i class="crease crease--v2"></i><i class="crease crease--h"></i></div>
</section>
</div>
<p class="sr-only" aria-live="polite" data-say></p>
</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.
78 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 road map fold” from Animorio and fit it into my siteThe sheet is a container with transform-style: preserve-3d. The middle column is the base and holds the other two: the left one has its transform-origin on its right edge, the right one on its left edge, so turning with rotateY closes them right along the crease. In every column the bottom panel has its transform-origin on its top edge and folds with rotateX. Every panel has two faces with backface-visibility: hidden, the front and the back turned by 180 degrees.
The real pages sit above the sheet and work as usual. When the fold starts they are copied (cloneNode): every face holds the whole page, as wide and tall as the screen, moved so that only its own piece shows. The front shows page A, the back page B with the columns swapped (column 0 shows piece 2), because after the turn the sheet is seen from behind. The copies lose data-autoclic and the touches that come later (road, pins, profile).
The folds go to exactly 180 degrees, but every hinge turns around a line set off from the paper by half a thickness: translateZ, the rotation, translateZ back. So every layer lands at its own depth and the panels never touch (no flicker). The order of the layers is that of a real map, and the bottom half folds forwards or backwards depending on the page that is leaving, so both sides of the packet always show that one.
Scale, lean, turn, twist, jump and the three fold angles live in one object (st) that GSAP animates with a timeline: it lifts (power3), closes (power2.inOut), jumps and turns, opens with back.out and lies down. On every update a function writes the transforms and works out the light: the normal of every panel, turned like the panel, against a lamp up on the left, gives the shade or the sheen over its two faces.
The shadow is a separate element on the desk, outside the 3D. Its size is the footprint of the sheet, worked out from the cosines of the angles (a closed column no longer counts) and narrowed by the cosine of the turn, so edge-on it is a line. The higher the sheet lifts or jumps, the more the shadow moves away, blurs and fades. When the new page arrives the sheet goes and the real page, identical, comes back and starts its own touches.
| Name | File | Default | What it changes |
|---|---|---|---|
FOLD | script.js | 0.6 | The seconds of each fold of the map. |
SIZE | script.js | 0.66 | How big the folded map stays in the air (1 = the whole screen); a little bigger on tall screens. |
TILT | script.js | 18 | How many degrees the folded map leans back. |
HOP | script.js | 1 | How high the packet jumps when it turns over (0 = it turns in place). |
BOUNCE | script.js | 1 | How far the panels overshoot and spring back as they open. |
CREASE | script.js | 0.55 | How much the creases show on the open page (0 = none). |
SPEED | script.js | 1 | The speed of the whole transition: 2 is twice as fast, 0.5 half as fast. |
To go from the home to a detail page on a travel site, for road or cycling routes, a tourist office, a publisher of guides and maps; for an archive of maps, an outdoor event with its route, whenever the new page is «the back» of the one before.
The same fold with the sizes adapted: both pages stack in a column and the folded sheet, a thin strip on a tall screen, stays a little bigger in the air. A tap on the button or the map starts it; the hovers (sign plate, opening map, legs) are for the mouse only.
Nothing folds or moves: the page fades in already complete, with the road, the pins and the car in place; on a click the page fades out and the new one fades in, complete as well.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 14 KB; the libraries: GSAP 28 KB.
Tested on 13 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.