
The copy button
Buttons, Text, SVG, Physics
NewFree
In the minimal style: the time a concert’s seats are held for, in large figures, with a thin line under it that gets shorter. The minute ticks hang from the line and drop one at a time as the line pulls back past them, while the line, rid of their weight, flicks and ripples like a string; the figures lose weight with the time, from very bold to a hairline, without getting any narrower. In the last thirty seconds the accent, at zero “Hold expired” and “Hold again”.
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 hold on the line</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/full.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the seat hold of a concert checkout: the time left is a thin line that gets shorter, the minute ticks hang
from it and drop off one at a time as the line pulls back past them, and the figures lose weight as the
time runs out. In the last seconds the accent; at zero the hold has expired and can be taken again -->
<section class="hold" aria-label="Seat hold" tabindex="-1" data-hold data-autoclic>
<header class="hold__top">
<span class="hold__swap" data-label>Seats held for</span>
<span class="hold__swap hold__swap--meta" data-meta>2 seats · Row F</span>
</header>
<p class="hold__time" role="timer" aria-live="off" aria-atomic="true">
<span class="hold__digits" aria-hidden="true" data-digits></span>
<span class="sr" data-time-words></span>
</p>
<div class="hold__rule" data-rule>
<svg class="hold__svg" aria-hidden="true" data-svg>
<path class="hold__track" data-track/>
<path class="hold__line" data-line/>
<g class="hold__ticks" data-ticks></g>
</svg>
</div>
<div class="hold__foot">
<span class="hold__swap hold__swap--note" data-note>Finish checkout to keep them.</span>
<button class="hold__again" type="button" hidden data-again>
<span class="hold__fill" aria-hidden="true"></span>
<span class="hold__words">Hold again</span>
</button>
</div>
<p class="sr" aria-live="polite" data-live></p>
</section>
</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.
24 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 hold on the shrinking line” from Animorio and fit it into my siteThe share of the line that is left is (time left / HOLD) to the power of CURVE. With CURVE under 1 the last minutes get more room, the ticks grow closer towards the right as on a slide rule and the end of the line runs faster and faster as the time runs out: at 0.55 the last thirty seconds of ten minutes are still almost a fifth of the line. The ticks sit where the curve puts the minutes, every MARK seconds.
The line is an SVG path redrawn on every frame from 120 points of a string: the wave equation, worked out in small steps (the step stays under the stability limit) with damping. The start is held; the end is where the line ends, and it is free, because the points past the tip copy the last one. When a tick drops the string gets a bell-shaped push upwards where it hung, when a tick lands one downwards: the tip flicks and the wave runs back. Once the string is still the path goes back to a single straight line and nothing is worked out.
Every tick hangs from the point of the string above it and follows it. When the line passes it, it comes off: it falls under gravity, with a small push backwards and a turn, because the line pulling back drags its top, and fades within a hundred pixels or so. Each one has a small fixed difference of its own, so every drop is different but the same from one round to the next. When the line stretches out again the ticks drop onto it from above as soon as it passes under them.
The figures are in Roboto Flex in its display cut (opsz 144) and their weight (wght) follows the line, from WEIGHT[0] to WEIGHT[1]. A lighter typeface is also a narrower one: as soon as the font is in, for every weight the script finds by bisection the width (wdth) that keeps the ten figures as wide as at the full weight, and uses it on every frame. So the time thins out on the spot, from a very narrow black to a wide hairline, and nothing around it moves.
Every figure sits in a window as tall as its ink: the old one drops down and the new one comes down with a back.out. The more often a figure changes, the faster it rolls, so it never lags behind the time; under a seventh of a second it simply changes. The time left is counted from the clock, so a tab left in the background still ends on time. The preview’s click (detail 0) makes the time run PREVIEW times faster, then slower in the last seconds, and when the hold expires it presses Hold again by itself.
| Name | File | Default | What it changes |
|---|---|---|---|
HOLD | script.js | 600 | How many seconds the seats are held for. |
MARK | script.js | 60 | Every how many seconds a tick hangs from the line. |
LAST | script.js | 30 | The last seconds, in the accent colour. |
CURVE | script.js | 0.55 | How the line measures the time: 1 = evenly; under 1 the last minutes get more room and the tip runs faster and faster. |
WEIGHT | script.js | [860, 110] | The weight (wght) of the figures with all the time ahead and at zero. |
SPEED | script.js | 1 | How fast the time goes by: 1 = real time. |
PREVIEW | script.js | [180, 9] | In the library’s preview the time runs faster: how many times, and how many in the last seconds. |
STRING | script.js | [1.6, 3.2] | The line as a string: how fast a ripple runs along it and how soon it calms down. |
ENDED | script.js | ['Hold expired', 'Seats released', 'Your seats went back on sale.'] | The words once the time is up: top left, top right and at the foot. |
For the time a ticket site holds the seats chosen, for a table or room booking waiting for payment, for a cart with the goods set aside, for a code that expires, for an auction about to close.
The component takes the whole width less the margins (up to 360 pixels) and the figures get smaller with the phone while staying the same; the line, the ticks and the string are worked out again for the new size, even halfway through a fall. The button answers a touch as it does the mouse: it fills and its words widen while the finger presses.
Nothing moves: the line still gets shorter, because it is the time, but the ticks it passes fade where they are, the string does not ripple, the figures change without dropping and the words change with a fade. The weight of the figures still goes down. Hold again puts the whole line back, and it fades in with its ticks.
GSAP 3.15.0, Roboto Flex (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 9.8 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.