
Words that melt into each other
Text, Scroll, SVG
NewFree
Scroll inside the preview
A season’s calendar, one race per row. When the list reaches the middle of the screen a car seen from above drives in and stays there while the rows slide under it, and the row passing under it lights up. At the end it drives off, and if you scroll back it comes in from below, turned round.
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 car in the calendar</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/big-shoulders-display@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/barlow@5.3.0/400.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/barlow@5.3.0/600.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="bar">
<a class="brand" href="#">Brightwater Racing</a>
<nav class="bar__nav" aria-label="Sections">
<a href="#">Team</a>
<a href="#">Calendar</a>
<a href="#">Tickets</a>
</nav>
</header>
<section class="intro">
<p class="intro__kicker">Car 11 · Coastal GT Cup</p>
<h1 class="intro__title">Nine weekends, one car, the whole coast.</h1>
<p class="intro__hint">Scroll down the calendar, then back up.</p>
</section>
<!-- the calendar: the car waits above the screen, drives in when the list reaches the middle and leaves at its end -->
<section class="calendar" data-calendar>
<div class="calendar__sticky" aria-hidden="true">
<div class="drift" data-drift>
<svg class="car" data-car viewBox="0 0 200 420">
<defs>
<radialGradient id="shadow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0.55" stop-color="#000" stop-opacity="0.55"/>
<stop offset="1" stop-color="#000" stop-opacity="0"/>
</radialGradient>
<linearGradient id="paint" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#d9dde4"/>
<stop offset="0.18" stop-color="#ffffff"/>
<stop offset="0.82" stop-color="#ffffff"/>
<stop offset="1" stop-color="#d9dde4"/>
</linearGradient>
<linearGradient id="glass" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2a3140"/>
<stop offset="1" stop-color="#0e1118"/>
</linearGradient>
<clipPath id="body"><path d="M36 28C60 16 140 16 164 28C176 34 182 48 184 66L188 118C190 140 186 160 184 176L182 246C184 268 190 290 188 316L184 366C182 392 166 410 136 414C112 417 88 417 64 414C34 410 18 392 16 366L12 316C10 290 16 268 18 246L16 176C14 160 10 140 12 118L16 66C18 48 24 34 36 28Z"/></clipPath>
</defs>
<ellipse cx="100" cy="216" rx="112" ry="222" fill="url(#shadow)"/>
<!-- the tyres, peeking out under the arches -->
<g fill="#151820">
<rect x="2" y="64" width="26" height="70" rx="7"/><rect x="172" y="64" width="26" height="70" rx="7"/>
<rect x="2" y="290" width="26" height="70" rx="7"/><rect x="172" y="290" width="26" height="70" rx="7"/>
</g>
<!-- the body and its livery -->
<path d="M36 28C60 16 140 16 164 28C176 34 182 48 184 66L188 118C190 140 186 160 184 176L182 246C184 268 190 290 188 316L184 366C182 392 166 410 136 414C112 417 88 417 64 414C34 410 18 392 16 366L12 316C10 290 16 268 18 246L16 176C14 160 10 140 12 118L16 66C18 48 24 34 36 28Z" fill="url(#paint)"/>
<g clip-path="url(#body)">
<rect x="70" y="0" width="22" height="420" fill="#b6ff3b"/>
<rect x="108" y="0" width="22" height="420" fill="#b6ff3b"/>
<rect x="94" y="0" width="12" height="420" fill="#0b0e14"/>
<path d="M0 196H200V214H0Z" fill="#0b0e14" opacity="0.08"/>
</g>
<!-- the rear window, the roof with its number, the windscreen -->
<path d="M50 106C70 98 130 98 150 106L146 146C120 140 80 140 54 146Z" fill="url(#glass)"/>
<circle cx="100" cy="192" r="30" fill="#ffffff" stroke="#0b0e14" stroke-width="5"/>
<text x="100" y="206" text-anchor="middle" font-family="'Big Shoulders Display Variable', Impact, sans-serif" font-weight="900" font-size="38" fill="#0b0e14">11</text>
<path d="M46 262C72 248 128 248 154 262L162 304C126 294 74 294 38 304Z" fill="url(#glass)"/>
<path d="M60 266C80 258 104 256 122 258" fill="none" stroke="#fff" stroke-opacity="0.35" stroke-width="4" stroke-linecap="round"/>
<!-- mirrors, headlights, the rear wing -->
<path d="M18 252L4 246L4 262L18 266Z" fill="#0b0e14"/><path d="M182 252L196 246L196 262L182 266Z" fill="#0b0e14"/>
<path d="M34 384C42 396 54 404 68 406L64 394C52 392 42 388 34 384Z" fill="#fff6c9"/>
<path d="M166 384C158 396 146 404 132 406L136 394C148 392 158 388 166 384Z" fill="#fff6c9"/>
<rect x="0" y="4" width="200" height="22" rx="4" fill="#0b0e14"/>
<rect x="0" y="2" width="10" height="30" rx="3" fill="#0b0e14"/><rect x="190" y="2" width="10" height="30" rx="3" fill="#0b0e14"/>
</svg>
</div>
</div>
<h2 class="calendar__title">Calendar 2027</h2>
<ol class="rounds" data-rounds>
<li class="round" data-round><span class="round__no">Round 1</span><b class="round__name">Harbour Ring</b><span class="round__date">14–16.03.2027</span><i class="round__line" data-line></i></li>
<li class="round" data-round><span class="round__no">Round 2</span><b class="round__name">Saltmarsh Park</b><span class="round__date">04–06.04.2027</span><i class="round__line" data-line></i></li>
<li class="round" data-round><span class="round__no">Round 3</span><b class="round__name">Ridgeback Circuit</b><span class="round__date">25–27.04.2027</span><i class="round__line" data-line></i></li>
<li class="round" data-round><span class="round__no">Round 4</span><b class="round__name">Lantern Bay Streets</b><span class="round__date">16–18.05.2027</span><i class="round__line" data-line></i></li>
<li class="round" data-round><span class="round__no">Round 5</span><b class="round__name">Pine Hollow Raceway</b><span class="round__date">06–08.06.2027</span><i class="round__line" data-line></i></li>
<li class="round" data-round><span class="round__no">Round 6</span><b class="round__name">Kestrel Downs</b><span class="round__date">27–29.06.2027</span><i class="round__line" data-line></i></li>
<li class="round" data-round><span class="round__no">Round 7</span><b class="round__name">Copperfield Speedway</b><span class="round__date">18–20.07.2027</span><i class="round__line" data-line></i></li>
<li class="round" data-round><span class="round__no">Round 8</span><b class="round__name">Blue Mesa International</b><span class="round__date">22–24.08.2027</span><i class="round__line" data-line></i></li>
<li class="round" data-round><span class="round__no">Finale</span><b class="round__name">Winter Hill</b><span class="round__date">03–05.10.2027</span><i class="round__line" data-line></i></li>
</ol>
</section>
<section class="outro">
<h2 class="outro__title">See you at the first corner.</h2>
<a class="outro__link" href="#">Get weekend passes</a>
</section>
<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.
48 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 car in the calendar” from Animorio and fit it into my siteThe section’s first child is a sticky frame as tall as the screen, with a negative bottom margin of the same height, so it does not push the list down. The car sits in the middle of the frame. The section has overflow: clip, which cuts the car at the section’s edges without breaking the sticky.
Two ScrollTriggers with callbacks: when the top of the section crosses the line at LINE% of the screen the car drives in (y to 0, IN seconds, expo.out); when the end of the list crosses the same line it drives off downwards (y to one screen, OUT seconds, expo.in) and only when it is gone does it turn 180 degrees. Scrolling back the opposite happens: it comes in from below nose up, and leaves at the top turning back to 0 degrees.
Every row has its own ScrollTrigger, from when its top crosses the line to when its bottom does. Entering, text and line take the accent colour in ON seconds with power2.out; leaving, they go back to their colour in OFF seconds with power2.inOut.
A tween tied to the scroll moves the frame from -DRIFT% to DRIFT% while the section crosses the screen. On every refresh a function looks at where the section and the end of the list are and puts the car straight where it belongs, above, in or below, turned the right way.
| Name | File | Default | What it changes |
|---|---|---|---|
IN | script.js | 0.8 | The seconds the car takes to drive in. |
OUT | script.js | 0.8 | The seconds it takes to drive off. |
DRIFT | script.js | 12 | How far the car drifts while the list goes by, as a share of its frame (%). |
ON | script.js | 0.2 | The seconds a row takes to light up. |
OFF | script.js | 0.6 | The seconds a row takes to go back. |
LINE | script.js | 50 | Where on the screen the rows light up and the car comes and goes, from the top (%). |
For a calendar, a programme, a running order, the stages of a journey: any list where something moves forward. In place of the car any thing seen from above will do, a boat, a bike, a train.
The car is a third of the screen wide and the rows go on two levels: the round above, name and dates below. The finger scrolls like the wheel and the car comes and goes the same way.
The car does not drive: it fades out and fades back in where it belongs, and the frame does not drift. The rows still light up.
GSAP 3.15.0, Big Shoulders Display (font variabile) 5.3.0, Barlow 5.3.0.
Compressed with gzip, the three demo files weigh 5.4 KB; the libraries: GSAP + ScrollTrigger 45 KB.
Tested on 2 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.