
Words that melt into each other
Text, Scroll, SVG
NewFree
Scroll inside the preview
An observatory’s eclipse day, in the minimal style: as you scroll, the Moon, only a thin outline, slides across a vermilion Sun and bites it into a thinner and thinner crescent, and the whole page darkens with the light that is left. At totality the Moon settles in the middle, two thin rings draw themselves around it and the page is night; then it moves on and the day comes 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">
<title>The eclipse that dims the page</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- the eclipse day of a made-up observatory, Halo. Scrolling, the Moon, only a thin outline, slides across the Sun
and bites it into a crescent; the more Sun it covers, the darker the whole page gets. At totality the Moon settles
in the middle, a thin vermilion ring draws itself around it and the page is night; then the Moon moves on and
the day comes back. The word of the phase loses weight with the light -->
<main>
<section class="apertura">
<p class="piccolo">Halo Observatory</p>
<h1 class="titolo">For six minutes,<br>the day goes out.</h1>
<p class="piccolo piccolo--tenue">Scroll to bring the Moon across.</p>
</section>
<section class="racconto" data-racconto>
<div class="palco" data-palco>
<header class="testa">
<p class="marchio">Halo</p>
<p class="piccolo piccolo--tenue">Total eclipse, viewing day</p>
</header>
<svg class="cielo" data-cielo aria-hidden="true">
<defs>
<mask id="morso" maskUnits="userSpaceOnUse">
<rect class="morso__tutto" data-morso-tutto fill="#fff"></rect>
<circle data-morso-luna fill="#000"></circle>
</mask>
</defs>
<circle class="sole" data-sole mask="url(#morso)"></circle>
<circle class="corona corona--larga" data-corona-larga></circle>
<circle class="corona" data-corona></circle>
<circle class="luna" data-luna></circle>
<g class="righello" data-righello></g>
</svg>
<div class="fase" aria-live="polite">
<p class="fase__finestra">
<span class="fase__parola" data-fase>Daylight</span>
<span class="fase__parola" data-fase>Partial</span>
<span class="fase__parola" data-fase>Totality</span>
<span class="fase__parola" data-fase>Partial</span>
<span class="fase__parola" data-fase>Daylight</span>
</p>
<p class="lettura">Sun covered <span class="lettura__cifra" data-coperto>0%</span></p>
</div>
</div>
</section>
<section class="apertura apertura--fine">
<h2 class="titolo titolo--medio">Bring a chair.<br>We bring the glasses.</h2>
</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.
29 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 eclipse that dims the page” from Animorio and fit it into my siteThe Sun, the Moon, the rings and the timeline sit in one SVG whose viewBox matches the pixels of the pinned stage (position: sticky, 100lvh tall). The Sun is a solid circle with a mask: a white rectangle as big as the stage and, on top, a black circle that moves with the Moon. Where the black is the Sun does not show, so the crescent comes by itself, right at every position, without drawing it. The visible Moon is only a thin, semi-transparent outline.
The timeline, tied to the scroll with scrub SCRUB, carries a number u from 0 to 1: for four steps the Moon arrives, for TOTALITY steps it stays still in the middle, for four more it leaves. From u come the Moon’s place on its line (tilted by ANGLE degrees), the time and the word of the phase. The change between two words falls where geometry says: first contact is when the distance between the centres equals the sum of the radii.
Every frame the script works out the covered area of the Sun with the formula for the lens between two circles and writes it in the percentage. The darkness is that coverage cubed, like the real sky that barely changes while a sliver of Sun is left: with gsap.utils.interpolate it mixes the warm white and the near black into the --fondo variable, and the text colour turns from black to white between 40 and 62 per cent of darkness. The same number takes weight away from the word of the phase (wght axis) and softens it (Fraunces’ SOFT axis).
Once in the middle, the Moon goes a little over two pixels too far and comes back with elastic.out: a second value of the timeline added to its place. The rings are circles with a stroke-dasharray as long as their circumference, turned by 90 degrees to start from the top: stroke-dashoffset goes from the circumference to zero and draws them, then from zero to minus the circumference and the tail chases the head until they are gone. The second one starts a moment later.
The four times in TIMES sit on the line at their true distance, which is why totality is a short stretch; the vermilion mark walks the hours and the names under the line fade when the time passes over them. All the measures (the Sun’s centre and radius, the line) are worked out again on every ScrollTrigger refresh and on every resize, even halfway through the eclipse.
| Name | File | Default | What it changes |
|---|---|---|---|
SUN | script.js | 0.25 | The radius of the Sun, as a share of the stage height. |
MOON | script.js | 1.035 | How much bigger the Moon looks than the Sun: the bigger it is, the wider the dark edge inside the rings. |
ANGLE | script.js | 20 | The slope of the Moon’s path, in degrees (0 is horizontal). |
TOTALITY | script.js | 2.2 | How long totality lasts, in steps of the story (the Moon takes 4 to arrive). |
TIMES | script.js | ['09:58', '11:24', '11:30', '12:56'] | The four times: first contact, start and end of totality, last contact. |
SCRUB | script.js | 0.6 | How many seconds the drawing takes to catch up with the scroll. |
For the page of an observatory, a planetarium or an outdoor event, for a science article or for any story where the page should go from day to night and back: an opening, a held moment in the middle, a return.
The same, with adapted measures: the Sun sits centred near the top and smaller, the word of the phase below it, the timeline at the bottom with smaller labels. The Moon comes in from off screen.
The Moon does not travel: five still pictures (day, crescent, totality, crescent, day) that take turns with a cross-fade, with the rings already drawn at totality; the page darkens and lightens slowly, the words of the phase cross-fade and the mark on the timeline jumps from one picture to the next.
GSAP 3.15.0, ScrollTrigger (GSAP) 3.15.0, Fraunces (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 7.5 KB; the libraries: GSAP + ScrollTrigger 45 KB.
Tested on 19 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.