
The star rating
Ratings, SVG, Hover
NewFree
A podcast player in the minimal style: the waveform is a row of thin vertical lines, and the lines already heard take the accent. Passing over with the pointer, the nearby lines grow taller and move apart as under a lens, each on its own spring, while the time of that point, in tabular figures, follows the pointer with the chapter's name. Dragging seeks, play turns into pause and the figures of the time roll one by one.
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 player with a wave of lines</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the player with a wave of lines: a podcast player whose waveform is a row of hairlines drawn on a canvas.
Under the pointer the lines spread and grow as under a lens, each one on its own spring, and the time of the
point rides above them in tabular figures. No sound: the time simply runs -->
<section class="player" aria-label="Podcast player" data-player data-autoclic>
<header class="head">
<p class="head__show">Slow Signal</p>
<p class="head__meta">Oct 14 · 48 min</p>
</header>
<h2 class="title" data-title>The quiet part of the work</h2>
<p class="guest">with Tamsin Okoro</p>
<div class="scope" data-scope>
<canvas class="scope__lines" aria-hidden="true" data-canvas></canvas>
<div class="readout" aria-hidden="true" data-readout>
<span class="readout__time" data-readout-time>0:00</span>
<span class="readout__chapter" data-readout-chapter></span>
</div>
<div class="wave" role="slider" tabindex="0" aria-label="Position in the episode" aria-valuemin="0" data-wave></div>
</div>
<div class="controls">
<button class="play" type="button" aria-label="Play" aria-pressed="false" data-play>
<svg viewBox="0 0 24 24" aria-hidden="true"><polygon data-a/><polygon data-b/></svg>
</button>
<div class="now">
<p class="now__clock" aria-hidden="true"><span class="digits" data-now>0:00</span><span class="now__of">/</span><span data-total>0:00</span></p>
<p class="now__chapter" data-chapter></p>
</div>
<button class="speed" type="button" aria-label="Playback speed: 1 times" data-speed><span class="speed__in" data-speed-text>1×</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.
39 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 player with a wave of lines” from Animorio and fit it into my siteThe episode is a row of 2400 loudness values, written once from a fixed seed: sentences of different lengths with a curve inside, almost silent breaths between them, a slow swell that makes some stretches livelier, the theme at the start and a moment of silence with a short sting at every chapter. The number of lines comes from the width (one every PITCH pixels) and each line mixes the peak and the average of its stretch, with some contrast: at any size the wave stays the same episode.
Each line has a weight and the strip is shared out in proportion to the weights. The weight is 1, plus a narrow Gaussian round the pointer, minus a wide one that gives back the same area: the lines under the lens take room and the ones in a ring round it give it up, as on the rim of a real lens, while the rest of the strip and its two ends do not move. Each side of the centre pays for its own half, so the line under the pointer is the one that was there before the lens opened and the time read is the right one; near an end, where the ring has few lines to give, that side of the lens grows less, so no weight falls below 0.2 and no line passes another.
The lens gives each line a place and a height (1 + MAGNIFY times the Gaussian); each line chases them on a damped spring (STIFFNESS, and BOUNCE taking damping away), integrated in steps of 1/120 of a second with the frame's step held to 1/30. The lens's strength comes and goes on a spring with no bounce. The canvas draws the lines in a colour picked from two ramps made beforehand (not heard towards light, heard towards the ink) and a width that grows by half a pixel under the lens; GSAP's ticker comes off when nothing moves.
The time and the chapter above the strip follow the pointer on a softer spring and stay inside the card; the weight of the type goes from 300 to 530 with the lens's strength and the width (font-stretch, Roboto Flex's wdth axis) drops with the spring's speed, so the figure narrows when it runs. The figures are tabular: the number does not dance while it changes. The width of the writing is measured only when the chapter changes.
The time shown is the real time plus a lag that a tween brings to zero in SEEK seconds: a click moves the time at once and the accent runs after it, and dragging (with pointer capture) the time follows the hand while the lag melts away. Play and pause are two polygons whose points slide from the two halves of the triangle to the two bars, with back.out for one pixel too far. The time below is made of little windows, one per figure: only the figure that changes changes, the old one goes up and the new one comes from below. The speed narrows down to 25% of the wdth axis, changes and opens again.
The strip is a role slider: arrows of 10 seconds, pages of a minute, Home and End, Space plays and pauses; with the keys the lens rests on the line playing. With a finger the lens opens while the finger touches and stays a moment after, so the time can be read. A click from a script on the card (the library's preview) runs one tour on a GSAP timeline: play, a pointer of its own passing the lens over the wave, pressing and dragging, passing again, leaving, the speed to 1.5×, pause and back to 14:05.
| Name | File | Default | What it changes |
|---|---|---|---|
LENGTH | script.js | '48:30' | How long the episode lasts (minutes:seconds, or hours:minutes:seconds). |
RESUME | script.js | '14:05' | Where the listening had stopped: the player opens there. |
CHAPTERS | script.js | [['0:00', 'Cold open'], ['6:20', 'Learning to wait'], ['19:45', 'Tools that disappear'], ['34:10', 'The last ten percent']] | The chapters: where each one starts and its name. Each has its tick under the wave and a short break in the voice. |
SPEEDS | script.js | [1, 1.5, 2] | The speeds the speed button goes through. |
PITCH | script.js | 3.4 | The pixels from one line to the next: fewer pixels, more lines. |
MAGNIFY | script.js | 0.85 | How much taller the lines under the lens get (0 = not at all). |
SPREAD | script.js | 1.6 | How far the lens pushes apart the lines under it (0 = not at all). |
REACH | script.js | 30 | The radius of the lens, in pixels. |
STIFFNESS | script.js | 320 | The springs of the lines: higher is quicker. |
BOUNCE | script.js | 0.28 | How far past their place the lines go before they settle (0 = not at all). |
SEEK | script.js | 0.6 | The seconds the accent takes to run to a new point. |
For a podcast's site, an episode's page, an audio interview, the recording of a talk, an audiobook or an app's voice note: a sober player whose wave reads like an index and where the right point is found at a glance.
The card is at most 380 pixels wide (at 390 it is 358 wide and the lines are counted again from the width). A finger touching the strip opens the lens and drags it without scrolling the page (touch-action none only on the strip); lifting it the lens stays 0.9 seconds, so the time can be read. The play button is 54 pixels.
The lines do not move or grow: the lens only lights them, with a fade. The time above the strip fades in and sits under the pointer with no spring; the accent jumps straight to the new point, the figures and the chapter change without rolling, the play icon changes at once and the card fades in, already at 14:05.
GSAP 3.15.0, Roboto Flex (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 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.