
Standing letters with a long shadow
Text, Scroll, 3D
NewFree
Scroll inside the preview
Five folders that pile up as you scroll: each one stops at the same height, and the tabs, each one place further along, stay in a row like the dividers of a file drawer. Click a tab to go back to its folder.
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 tabbed folders</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/anybody@5.3.0/wdth.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist-mono@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="top">
<p class="brand">Offcut</p>
<p class="top__line">Selected jobs</p>
</header>
<main>
<section class="intro">
<h1 class="intro__title">Five jobs,<br>filed.</h1>
<p class="intro__text">Every job at Offcut lives in a folder. Scroll, and each one slides over the one before; the tabs stay in a row, so you can always pull a folder back out.</p>
</section>
<!-- every folder sticks at the same height; its tab sits one place further along, so the row fills up -->
<ul class="drawer" data-drawer style="--count: 5">
<li class="folder" data-folder style="--n: 0; --tint: #f7f5ee; --on: #0e1440">
<div class="folder__row"><button class="folder__tab" type="button" data-tab aria-label="Go to Ferry timetable"><span>Ferry</span></button></div>
<article class="folder__body">
<div class="folder__inside">
<div class="folder__text">
<p class="folder__date">March 2026</p>
<h2 class="folder__title">Ferry timetable</h2>
<p class="folder__line">Forty crossings a day, set so you can read them from the pier.</p>
<ul class="folder__tags"><li>Timetable</li><li>Wayfinding</li></ul>
</div>
<figure class="folder__art" aria-hidden="true">
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
<rect width="400" height="300" fill="#2343e6"/>
<circle cx="306" cy="80" r="38" fill="#ffd21f"/>
<rect x="212" y="112" width="24" height="36" fill="#0e1440"/>
<rect x="212" y="120" width="24" height="8" fill="#ffd21f"/>
<rect x="132" y="146" width="126" height="34" fill="#ff5b4a"/>
<g fill="#f7f5ee"><rect x="146" y="156" width="16" height="13"/><rect x="172" y="156" width="16" height="13"/><rect x="198" y="156" width="16" height="13"/><rect x="224" y="156" width="16" height="13"/></g>
<path d="M84 180 H312 L284 222 H112 Z" fill="#f7f5ee"/>
<path d="M0 228 q25 -16 50 0 t50 0 t50 0 t50 0 t50 0 t50 0 t50 0 t50 0 V300 H0 Z" fill="#1a31b4"/>
<path d="M-25 256 q25 -16 50 0 t50 0 t50 0 t50 0 t50 0 t50 0 t50 0 t50 0 t50 0 V300 H-25 Z" fill="#0e1440"/>
</svg>
<i></i><i></i><i></i><i></i>
</figure>
</div>
</article>
</li>
<li class="folder" data-folder style="--n: 1; --tint: #7df0c4; --on: #0e1440">
<div class="folder__row"><button class="folder__tab" type="button" data-tab aria-label="Go to Seed packets"><span>Seeds</span></button></div>
<article class="folder__body">
<div class="folder__inside">
<div class="folder__text">
<p class="folder__date">January 2026</p>
<h2 class="folder__title">Seed packets</h2>
<p class="folder__line">A packet for every seed in the neighbourhood library, printed in two inks.</p>
<ul class="folder__tags"><li>Packaging</li><li>Riso</li></ul>
</div>
<figure class="folder__art" aria-hidden="true">
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
<rect width="400" height="300" fill="#f7f5ee"/>
<g transform="rotate(-11 122 156)">
<rect x="70" y="66" width="104" height="172" rx="6" fill="#0e1440"/>
<g transform="translate(122 150)" fill="#7df0c4"><rect x="-3" y="-6" width="6" height="52"/><path d="M0 6 C-30 2 -36 -30 -36 -30 C-8 -32 0 -14 0 6 Z"/><path d="M0 -6 C26 -10 32 -40 32 -40 C8 -40 0 -24 0 -6 Z"/></g>
<rect x="86" y="212" width="72" height="8" fill="#f7f5ee"/>
</g>
<g transform="rotate(3 200 150)">
<rect x="148" y="52" width="104" height="184" rx="6" fill="#ff5b4a"/>
<g transform="translate(200 146)" fill="#0e1440"><rect x="-3" y="-6" width="6" height="56"/><path d="M0 8 C-30 4 -36 -28 -36 -28 C-8 -30 0 -12 0 8 Z"/><path d="M0 -4 C26 -8 32 -38 32 -38 C8 -38 0 -22 0 -4 Z"/></g>
<rect x="164" y="210" width="72" height="8" fill="#f7f5ee"/>
</g>
<g transform="rotate(15 282 160)">
<rect x="230" y="70" width="104" height="172" rx="6" fill="#ffd21f"/>
<g transform="translate(282 154)" fill="#0e1440"><rect x="-3" y="-6" width="6" height="52"/><path d="M0 6 C-30 2 -36 -30 -36 -30 C-8 -32 0 -14 0 6 Z"/><path d="M0 -6 C26 -10 32 -40 32 -40 C8 -40 0 -24 0 -6 Z"/></g>
<rect x="246" y="216" width="72" height="8" fill="#0e1440"/>
</g>
</svg>
<i></i><i></i><i></i><i></i>
</figure>
</div>
</article>
</li>
<li class="folder" data-folder style="--n: 2; --tint: #0e1440; --on: #f7f5ee">
<div class="folder__row"><button class="folder__tab" type="button" data-tab aria-label="Go to Night market"><span>Market</span></button></div>
<article class="folder__body">
<div class="folder__inside">
<div class="folder__text">
<p class="folder__date">October 2025</p>
<h2 class="folder__title">Night market</h2>
<p class="folder__line">Posters that glow under the lamps: one for each of the six nights.</p>
<ul class="folder__tags"><li>Posters</li><li>Screen print</li></ul>
</div>
<figure class="folder__art" aria-hidden="true">
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
<rect width="400" height="300" fill="#1a31b4"/>
<path d="M318 34 a46 46 0 1 0 34 78 a36 36 0 1 1 -34 -78 Z" fill="#ffd21f"/>
<path d="M0 92 Q200 150 400 80" fill="none" stroke="#f7f5ee" stroke-width="2.5"/>
<g fill="#ffd21f"><rect x="52" y="106" width="16" height="24" rx="5"/><rect x="122" y="120" width="16" height="24" rx="5"/><rect x="192" y="125" width="16" height="24" rx="5"/><rect x="262" y="118" width="16" height="24" rx="5"/></g>
<g>
<rect x="46" y="200" width="7" height="72" fill="#f7f5ee"/><rect x="166" y="200" width="7" height="72" fill="#f7f5ee"/>
<path d="M30 168 H190 L176 204 H44 Z" fill="#ff5b4a"/>
<g fill="#f7f5ee"><path d="M62 168 H94 L90 204 H64 Z"/><path d="M126 168 H158 L156 204 H130 Z"/></g>
<rect x="40" y="244" width="140" height="30" fill="#f7f5ee"/>
</g>
<g>
<rect x="232" y="212" width="7" height="60" fill="#f7f5ee"/><rect x="348" y="212" width="7" height="60" fill="#f7f5ee"/>
<path d="M216 182 H372 L358 216 H230 Z" fill="#7df0c4"/>
<g fill="#0e1440"><path d="M248 182 H278 L274 216 H250 Z"/><path d="M310 182 H340 L338 216 H314 Z"/></g>
<rect x="226" y="250" width="136" height="24" fill="#f7f5ee"/>
</g>
</svg>
<i></i><i></i><i></i><i></i>
</figure>
</div>
</article>
</li>
<li class="folder" data-folder style="--n: 3; --tint: #ffd21f; --on: #0e1440">
<div class="folder__row"><button class="folder__tab" type="button" data-tab aria-label="Go to Paper zoo"><span>Zoo</span></button></div>
<article class="folder__body">
<div class="folder__inside">
<div class="folder__text">
<p class="folder__date">June 2025</p>
<h2 class="folder__title">Paper zoo</h2>
<p class="folder__line">A book to cut up: eleven animals, and not a drop of glue.</p>
<ul class="folder__tags"><li>Book</li><li>Die cut</li></ul>
</div>
<figure class="folder__art" aria-hidden="true">
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
<rect width="400" height="300" fill="#f7f5ee"/>
<path d="M70 44 H330 V262 H70 Z" fill="none" stroke="#0e1440" stroke-width="2.5" stroke-dasharray="9 8"/>
<path d="M112 74 L200 158 L112 196 Z" fill="#ff5b4a"/>
<path d="M288 74 L200 158 L288 196 Z" fill="#ff5b4a"/>
<path d="M112 196 L200 158 L288 196 L200 248 Z" fill="#e8402f"/>
<path d="M150 204 L200 182 L250 204 L200 248 Z" fill="#f7f5ee" stroke="#0e1440" stroke-width="0"/>
<path d="M186 232 H214 L200 248 Z" fill="#0e1440"/>
<path d="M150 150 l18 10 l-18 8 Z" fill="#0e1440"/>
<path d="M250 150 l-18 10 l18 8 Z" fill="#0e1440"/>
</svg>
<i></i><i></i><i></i><i></i>
</figure>
</div>
</article>
</li>
<li class="folder" data-folder style="--n: 4; --tint: #ff5b4a; --on: #0e1440">
<div class="folder__row"><button class="folder__tab" type="button" data-tab aria-label="Go to Open Tuesday"><span>Tuesday</span></button></div>
<article class="folder__body">
<div class="folder__inside">
<div class="folder__text">
<p class="folder__date">April 2025</p>
<h2 class="folder__title">Open Tuesday</h2>
<p class="folder__line">Signs for a repair café that opens one evening a week.</p>
<ul class="folder__tags"><li>Signage</li><li>Stencil</li></ul>
</div>
<figure class="folder__art" aria-hidden="true">
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
<rect width="400" height="300" fill="#0e1440"/>
<path d="M136 92 q-14 -16 0 -32 M170 92 q-14 -16 0 -32 M204 92 q-14 -16 0 -32" fill="none" stroke="#f7f5ee" stroke-width="7" stroke-linecap="round"/>
<path d="M226 144 h20 a28 28 0 0 1 0 56 h-20" fill="none" stroke="#f7f5ee" stroke-width="15"/>
<path d="M104 116 H236 V206 a30 30 0 0 1 -30 30 H134 a30 30 0 0 1 -30 -30 Z" fill="#f7f5ee"/>
<g transform="rotate(34 300 176)">
<rect x="289" y="120" width="22" height="132" rx="7" fill="#ffd21f"/>
<circle cx="300" cy="118" r="28" fill="#ffd21f"/>
<rect x="290" y="82" width="20" height="38" fill="#0e1440"/>
</g>
</svg>
<i></i><i></i><i></i><i></i>
</figure>
</div>
</article>
</li>
</ul>
<section class="outro">
<p class="outro__text">Next job: yours.</p>
</section>
</main>
<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="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.
62 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 tabbed folders” from Animorio and fit it into my siteThe folders are the items of a list, one after the other, each with position: sticky and the same top. When one reaches the line it stops; the next one, which comes later in the code and therefore sits above, keeps rising and covers it. Between one and the next there is a 14vh margin: the stretch of scroll in which the whole folder is in view.
Each folder is a row with the tab and, under it, the body. The tab is a fifth of the folder wide and has a left margin of as many fifths as --n says: 0 for the first, 4 for the last. The rest of the row is empty, and the tabs of the folders underneath show through it. The slanted side is a triangle cut with clip-path.
A sticky element has to stay inside the content of its container, margin included. That is why the list ends with an empty stretch, an ::after 36vh tall: until that stretch has gone by, all the folders stay where they are, and then they leave together.
One number tied to the scroll with ScrollTrigger: how many folders have landed on the first one, fractions included, from 0 to 4. From it, for each folder, how many are on top of it: it rises 7 px for each (the --rise variable), and while the first of them slides over it its content goes down to 15% opacity (--fade). The scrub lags by 0.4 seconds.
When the number passes a whole, the folder that has landed is the one in front: its tab jumps 9 px and falls back on an elastic, the picture’s four handles grow one after the other, and the tab’s label lights up. A click on a tab works out where its folder stops (the first folder’s point, plus as many distances as there are folders before it) and takes the page there, with the browser’s smooth scroll.
| Name | File | Default | What it changes |
|---|---|---|---|
--count, --n | index.html | 5, 0-4 | How many folders there are and each one’s place in the row: change them together when you add or remove one. |
--tint, --on | index.html | #f7f5ee, #0e1440 | Each folder’s colour and the colour of its text. |
--top, --tab, --slant | style.css | 58px, 46px, 22px | Where the folders stop, how tall the tabs are and how wide their slanted side is. |
LIFT | script.js | 7 | The pixels a folder rises for every folder that lands on it. |
FADE | script.js | 0.85 | How much of its content a covered folder loses: 1 makes it vanish altogether. |
SCRUB | script.js | 0.4 | The seconds the pile takes to catch up with the scroll. |
.folder margin, .drawer::after | style.css | 14vh, 36vh | The scroll between one folder and the next, and how long the pile holds at the end. |
For a short list of jobs, case studies, services or chapters, three to six of them, where it matters to see at once how many there are and to be able to go back: a studio’s portfolio, the index of a report, the stages of a journey.
The same, with narrower tabs and 11 px labels; inside the folder the picture goes under the text and takes the height that is left.
The folders pile up all the same, because it is the page that scrolls, but they do not rise, the content does not fade and the tabs do not jump; a click on a tab goes to the folder without animated scrolling.
GSAP 3.15.0, ScrollTrigger (GSAP) 3.15.0, Anybody (font variabile) 5.3.0, Geist Mono (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 6.3 KB; the libraries: GSAP + ScrollTrigger 45 KB.
Tested on 30 September 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.