
Standing letters with a long shadow
Text, Scroll, 3D
NewFree
Scroll inside the preview
A heading in two halves fills the screen; as you scroll the halves shrink and move apart, and the paragraph opens between them. The headings fit the width with CSS alone.
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 heading that opens</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/big-shoulders@5.3.0/standard.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@5.3.0/standard.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="top">
<p class="brand">Lanterna Philharmonic</p>
<p class="top__line">Season notes</p>
</header>
<main>
<!-- every split section is three screens tall; its stage stays on screen while the two halves open -->
<section class="split split--night" data-split>
<h2 class="visually-hidden">First season</h2>
<div class="split__stage">
<div class="split__grid">
<div class="split__half split__half--top" aria-hidden="true">
<p class="fit split__title" data-title>
<span class="fit__box"><span class="fit__display">First</span></span>
<span class="fit__ref">First</span>
</p>
</div>
<p class="split__text" data-text>Thirty-two concerts, eleven premieres and a new hall by the water. This is the first season of the Lanterna Philharmonic, and it starts with every door open.</p>
<div class="split__half split__half--bottom" aria-hidden="true">
<p class="fit split__title" data-title>
<span class="fit__box"><span class="fit__display">Season</span></span>
<span class="fit__ref">Season</span>
</p>
</div>
</div>
<p class="split__hint" data-hint>Scroll</p>
</div>
</section>
<section class="split split--amber" data-split>
<h2 class="visually-hidden">One hall, every street</h2>
<div class="split__stage">
<div class="split__grid">
<div class="split__half split__half--top" aria-hidden="true">
<p class="fit split__title" data-title>
<span class="fit__box"><span class="fit__display">One hall</span></span>
<span class="fit__ref">One hall</span>
</p>
</div>
<p class="split__text" data-text>On Sundays the orchestra leaves the stage: string quartets in the covered market, brass on the morning ferries, the whole symphony in the old shipyard.</p>
<div class="split__half split__half--bottom" aria-hidden="true">
<p class="fit split__title" data-title>
<span class="fit__box"><span class="fit__display">Every street</span></span>
<span class="fit__ref">Every street</span>
</p>
</div>
</div>
</div>
</section>
<section class="split split--sky" data-split>
<h2 class="visually-hidden">The city in tune</h2>
<div class="split__stage">
<div class="split__grid">
<div class="split__half split__half--top" aria-hidden="true">
<p class="fit split__title" data-title>
<span class="fit__box"><span class="fit__display">The city</span></span>
<span class="fit__ref">The city</span>
</p>
</div>
<p class="split__text" data-text>Rehearsals are open to anyone holding a ticket stub, and every school in the district gets a row of seats at the front.</p>
<div class="split__half split__half--bottom" aria-hidden="true">
<p class="fit split__title" data-title>
<span class="fit__box"><span class="fit__display">In tune</span></span>
<span class="fit__ref">In tune</span>
</p>
</div>
</div>
</div>
</section>
<section class="outro">
<p class="outro__text">The season opens on the first Friday of October.</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.
32 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 heading that opens” from Animorio and fit it into my siteEach chapter is three screens tall and its stage, one screen tall, stays put with position: sticky. The stage is a grid of two equal rows: the top half rests its heading at the bottom, the bottom half at the top, so the two lines touch on the middle line. The paragraph sits in the centre, under the headings.
Next to the heading there is a hidden copy of it at 16 px, which takes its own width; a box takes the rest of the line. A property registered as a length turns 100cqi into pixels where it is written: in the box it is the whole line, in the heading it is the box, that is the line minus the text. Line divided by text says how much to enlarge, and tan(atan2(a, b)) = a / b is the way CSS has to divide one length by another. The heading never gets taller than its half (100cqh).
The script measures the paragraph’s real height, once the fonts are in, and moves each half away by half that height plus 54 px. The halves go down to scale 0.82, or less if needed to stay within a quarter of the screen each; the top one shrinks towards its lower edge (origin at 75%), the bottom one towards the top (25%).
One timeline tied to the chapter’s scroll, from the start of the stage to its end, with half a second of lag to smooth the wheel: the halves move from the start, the paragraph opens from a tenth, with clip-path from inset(50% 0 50% 0) to inset(0). If the window’s width changes, everything is measured again after 200 ms of quiet; the height does not count, because on phones it changes with every bar that slides away.
| Name | File | Default | What it changes |
|---|---|---|---|
SHRINK, HEIGHT | script.js | 0.82, 0.25 | The largest scale of the halves once open, and the most of the screen each one may take. |
GAP | script.js | 54 | The pixels between the paragraph and each half, once open. |
TEXT_FROM | script.js | 0.1 | When the paragraph starts to open, in the chapter’s scroll. |
SCRUB | script.js | 0.5 | The seconds the halves take to catch up with the scroll. |
--fit-max | style.css | 100cqh | The headings’ limit: the height of their half. Raise it for headings that spill off the stage. |
To open a chapter with a poster heading and a few lines under it: a season’s page, an annual report, a story in chapters, a product launch.
The same: the headings fill the screen’s width, the paragraph is centred, at 18 px, and takes the whole line minus 32 px margins.
Each chapter is one screen tall and already open: small headings above and below, the paragraph in between.
GSAP 3.15.0, ScrollTrigger (GSAP) 3.15.0, Big Shoulders (font variabile) 5.3.0, Newsreader (font variabile) 5.3.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 4.4 KB; the libraries: GSAP + ScrollTrigger 45 KB.
Tested on 29 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.