
The copy button
Buttons, Text, SVG, Physics
NewFree
A minimal calendar for the dates of a stay: once the arrival is chosen, a thin vermilion line leaves its day and follows the pointer like a rubber band, bending as it runs and straining against a night already taken; choosing the departure pins it down and it trembles like a plucked string, the nights in between darken one after the other and on the left the number of nights widens with the stay.
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 elastic calendar</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the picker: a month to choose the dates of a stay. Once the arrival is chosen a thin elastic line leaves its
day and follows the pointer between the days; choosing the departure pins it down and it settles like a plucked
string, the nights in between darken one after the other and the big number stretches with the stay -->
<div class="cal" data-picker data-autoclic>
<section class="cal__stay" aria-label="Your stay">
<p class="cal__label">Your stay</p>
<div class="cal__count" aria-hidden="true">
<span class="cal__num" data-num><span class="cal__digits" data-digits>0</span></span>
<span class="cal__unit" data-unit>nights</span>
</div>
<dl class="cal__dates">
<div class="cal__date">
<dt>Arrive</dt>
<dd data-from>Add date</dd>
</div>
<div class="cal__date">
<dt>Leave</dt>
<dd data-to>Add date</dd>
</div>
</dl>
<button class="cal__clear" type="button" data-clear disabled><span>Clear dates</span></button>
</section>
<section class="cal__month" aria-label="Choose your dates">
<div class="cal__head">
<button class="cal__nav" type="button" aria-label="Previous month" data-prev><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14.5 6 8.5 12l6 6"/></svg></button>
<h2 class="cal__title" aria-live="polite"><span data-title></span></h2>
<button class="cal__nav" type="button" aria-label="Next month" data-next><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9.5 6l6 6-6 6"/></svg></button>
</div>
<div class="cal__week" aria-hidden="true" data-week></div>
<div class="cal__field" data-field>
<svg class="cal__line" aria-hidden="true" data-svg>
<circle class="cal__pin" r="19" data-pin="a"/>
<circle class="cal__pin" r="19" data-pin="b"/>
<path class="cal__band" d="" data-band/>
</svg>
<div class="cal__grid" data-grid></div>
</div>
</section>
<p class="sr" role="status" aria-live="polite" data-said></p>
</div>
</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.
28 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 elastic calendar” from Animorio and fit it into my siteThe script draws six weeks of cells, so every month is equally tall, and puts each date on a continuous grid: x in days from the left, y in weeks from the top. Dates outside the month have their place too (above or below), so a stay that begins in the month before comes in from the edge. The booked nights (about one in nine) come from a number worked out from the date, so they are always the same.
The line has two ends, each on a spring towards its target: the arrival, and the pointer drawn to the middle of the day under it (FOLLOW, MAGNET) or the departure. The curve is the sum of two ways of vibrating, a half wave and a whole wave across the line: the acceleration of the two ends drives them (LAG), so the middle of the line lags behind when it runs, and the departure gives it a kick (PLUCK, RING). Four small steps per frame, with the step capped at 1/30 of a second, in one requestAnimationFrame that stops when nothing moves.
The line is an SVG path of 29 points written again on every frame, from ring edge to ring edge rather than centre to centre; the longer it is the thinner it gets, because it is stretched. The numbers of the days sit on top, on a little patch of white background: the line vanishes behind every figure and reads as a row of strokes between one day and the next.
Each ring is a circle with stroke-dasharray: GSAP draws it in 0.45 seconds starting from the angle the line comes in at. Each day has a --k variable from 0 to 1 that mixes the grey and the black (color-mix) and takes the weight from 380 to 680 and the width from 96 to 104: once the departure is chosen GSAP takes it to 1 one night after the other (DARKEN), with a one-pixel nudge down and an elastic return.
The number of nights is set in Archivo, whose width axis goes from 62 to 125. On every frame the script counts the nights from where the free end is (a week down is worth seven nights), halfway too, and turns them into width (STRETCH): the number widens and narrows with the line, trembles with its swing and gains weight when the departure is chosen.
A single day of the grid takes the Tab key: the arrows move between the days, Home and End go to the start and the end of the week, Page Up and Page Down change month, Enter chooses. With the keyboard the line follows the focus, with the mouse the pointer; with a finger, pressing a day stretches the line to it at once, and lifting the finger pins it.
| Name | File | Default | What it changes |
|---|---|---|---|
WEEK_START | script.js | 1 | The first day of the week: 1 Monday, 0 Sunday. |
MAX_NIGHTS | script.js | 14 | The longest stay, in nights: beyond it the line strains and goes no further. |
FOLLOW | script.js | 3.6 | How quickly the free end of the line follows the pointer, in hertz. |
MAGNET | script.js | 0.42 | How much the free end is drawn to the middle of the day under the pointer (0 to 1). |
LAG | script.js | 1.5 | How much the line bends when the pointer runs (0 = it stays straight). |
PLUCK | script.js | 11 | How far the line swings when the departure pins it, in pixels. |
RING | script.js | 5.2 | The pitch of the swing, in hertz: higher, a tighter string. |
DARKEN | script.js | [0.34, 0.05] | The nights darkening: the seconds of each, and the gap between one night and the next. |
STRETCH | script.js | [66, 124] | The width of the number of nights, from one night to a long stay (between 62 and 125). |
SLIDE | script.js | 0.55 | The seconds of a change of month. |
For the booking page of a hotel, a holiday house, a B&B or a rental: wherever an arrival and a departure are chosen and the nights matter. The booked nights should come from your own system instead of the made-up ones.
Below 720 pixels the stay goes on top, with the number beside the dates, and the cells narrow to fit the width of the screen. A finger has no pointer: pressing a day stretches the line to it at once, with the same spring and the same bend, and lifting it pins it with the same swing.
No springs, no swings and no waves: the line goes straight from one ring to the other at once, the rings and the nights fade in, the number takes its width at once, the months change with a fade and when the page opens the stay is already in place.
GSAP 3.15.0, Archivo (font variabile, assi wght e wdth) 5.3.0.
Compressed with gzip, the three demo files weigh 13 KB; the libraries: GSAP 28 KB.
Tested on 16 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.