
The gallery that opens
Galleries, Transitions, Images, Physics
NewFree
The stories of a concert hall on the harbour, and every story is a poster pasted on the wall: going on, the new poster comes in from the right and is slapped over the old one, full of air bubbles that a hand pushes out from the top down. The wet paper is darker, shines on its bumps and lets the poster underneath show through; the story’s time is the paste drying. Going back, the poster is torn off the wall.
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, viewport-fit=cover">
<title>The pasted stories</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/sofia-sans@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/sofia-sans-extra-condensed@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the stories of a concert hall, each one a poster pasted on the wall with wheat paste: going on, a new poster
is slapped over the last one, the hand smooths the air out of it and the wet paper lets the old poster show
through while the paste dries, which is the story's time; going back, the top poster is torn off. The posters
are drawn by script.js on the canvas, from STORIES -->
<section class="stories" aria-label="Stories from Ferry House" aria-roledescription="stories" data-stories data-autoclic>
<div class="viewer" data-viewer>
<canvas class="wall" aria-hidden="true" data-wall></canvas>
<p class="sr" data-now></p>
<button class="tap tap--back" type="button" aria-label="Previous story" data-back>
<span class="tap__sign" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M14.5 6.5L9 12l5.5 5.5"/></svg>
</span>
</button>
<button class="tap tap--next" type="button" aria-label="Next story" data-next>
<span class="tap__sign" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M9.5 6.5L15 12l-5.5 5.5"/></svg>
</span>
</button>
<div class="top" data-chrome>
<div class="bars" aria-hidden="true" data-bars></div>
<div class="head">
<span class="head__mark" aria-hidden="true">
<svg viewBox="0 0 32 32">
<circle class="mark__ground" cx="16" cy="16" r="16"/>
<path class="mark__house" d="M9 15.5l7-6 7 6v4.5H9z"/>
<path class="mark__door" d="M14 20v-4h4v4"/>
<path d="M5.5 23.2c1.8 0 1.8-1.4 3.6-1.4s1.8 1.4 3.6 1.4 1.8-1.4 3.6-1.4 1.8 1.4 3.6 1.4 1.8-1.4 3.6-1.4 1.8 1.4 3.6 1.4" class="mark__sea"/>
</svg>
</span>
<span class="head__name">Ferry House</span>
<span class="head__when">3 h</span>
<button class="head__pause" type="button" aria-label="Pause the stories" data-pause>
<svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true"><rect x="6.5" y="5" width="3.6" height="14" rx="1.2"/><rect x="13.9" y="5" width="3.6" height="14" rx="1.2"/></svg>
<svg class="i-play" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5.6c0-.9 1-1.5 1.8-1l9.4 5.9c.7.5.7 1.5 0 2l-9.4 5.9c-.8.5-1.8-.1-1.8-1z"/></svg>
</button>
</div>
</div>
<div class="foot" data-foot>
<form class="reply" data-reply>
<label class="sr" for="story-reply">Reply to Ferry House</label>
<input id="story-reply" type="text" placeholder="Reply to Ferry House" autocomplete="off" enterkeyhint="send" maxlength="140">
</form>
<button class="react" type="button" aria-pressed="false" aria-label="Stick a See you there sticker on this story" data-react>
<svg viewBox="0 0 28 28" aria-hidden="true">
<path class="react__sheet" d="M7 4.5h10.6l6 6V21a2.5 2.5 0 0 1-2.5 2.5H7A2.5 2.5 0 0 1 4.5 21V7A2.5 2.5 0 0 1 7 4.5z"/>
<path class="react__fold" d="M17.6 4.5V8a2.5 2.5 0 0 0 2.5 2.5h3.5"/>
<path class="react__face" d="M10.6 11.4v1.8M15.4 11.4v1.8M10 16.6c1.8 2 4.2 2 6 0"/>
</svg>
</button>
</div>
</div>
<p class="sr" aria-live="polite" aria-atomic="true" 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.
57 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 pasted stories” from Animorio and fit it into my siteEvery poster is drawn only once on a canvas as big as the viewer in real pixels (density at most 2), in its own units of 320 by 568: the ground, the drawing with arcs, curves and Path2D, the words centred and widened to 276 units with measureText. Over it, a tile of grain and fibres multiplied and a soft-light shading from the light’s side. The wall is put together every frame from these canvases, without drawing the posters again.
Going on, the wall as it is gets copied into an under canvas. The new poster comes in from the right held off the wall (scale 1.07, six degrees, its shadow), with x in power2.out for SLAP seconds, while in the second half scale and lift go back to zero with power2.in: it slows down and lands in the same instant. The shadow is blurred only once, with the trick of the shape drawn far away and only its shadow moved onto the canvas, because shadowOffset does not follow the transforms.
The paper is a height map on a grid of 80 by 142 cells: long creases (Gaussians along a segment fading at the ends), lumps of paste (smooth noise) and BUBBLES elliptic air bubbles, which swell with elastic.out on impact. The hand’s front goes down in SMOOTH seconds with power1.inOut, a little wavy and crooked; in front of it it pushes the bubbles, which squashed grow wider more than taller, and a wave of paper; behind it, the creases flatten to 38%. Every frame central differences give the slope: its product with LIGHT makes light and shade (a small grey canvas stretched in overlay) and, where it is wet, the direction halfway between the light and the eye makes the shine.
Every cell has its moment to dry: the paste’s thickness, made of stripes like brush strokes plus a noise, times the distance from the edge. The wetness goes from 1 to 0 with a wide smoothstep while the story’s time runs, and the paper is dry before the bar is full. Wet, the paper multiplies its colour by a darker warm grey and lets the poster underneath through: shrunk to a seventh and stretched back (the paper blurs it), cut with destination-in by a mask as high as SHOW_THROUGH times the square of the wetness, and multiplied. With no time running (pause, a press, the focus) the paste does not dry and nothing is drawn again.
Going back the wall is frozen on a canvas and the poster underneath is worked out again, dry. The tear line runs near the right edge, made of three sine waves and a little noise; from it two Path2D, the piece and what stays. The piece turns up to 24 degrees round its top left corner while it leaves with power2.in in TEAR seconds, with its shadow and the white edge of torn paper; the strip left behind flakes away, dropping a little. At the end the poster revealed is exactly the one worked out for the under canvas: no jump.
The tap zones are two real buttons, Previous story and Next story, whose arrow shows for the mouse and the keys; pausing is a button in the head, and the keyboard’s arrows go on and back. A press longer than HOLD seconds stops the time and hides everything but the poster, without changing story. The time also stops when the keys come into the stories or a reply is being written (the button says Play, and pressed it starts again); an aria-live region says the new story, the sticker and the reply. A click from a script (the library’s preview) goes on, sticks a sticker, goes on, tears, and goes on twice.
| Name | File | Default | What it changes |
|---|---|---|---|
DURATION | script.js | 6 | The seconds each story stays: the paste is dry by then. |
SLAP | script.js | 0.4 | The seconds a new poster takes to come in and be slapped onto the wall. |
SMOOTH | script.js | 0.62 | The seconds the hand takes to smooth the poster from the top down. |
TEAR | script.js | 0.5 | The seconds a poster takes to be torn off, going back. |
HOLD | script.js | 0.22 | The seconds a press must last to stop the time instead of changing story. |
BUBBLES | script.js | 8 | The air bubbles under a poster just pasted. |
SHOW_THROUGH | script.js | 0.22 | How much the poster underneath shows through the wet paper, from 0 to 1. |
SHINE | script.js | 0.8 | How much the wet paper shines on its bumps, from 0 to 1. |
LIGHT | script.js | [-0.55, -0.83] | Where the light comes from, across and down: like this from the top left. |
STORIES | script.js | [['sun', 'MARROW', '& Kite Weather', 'Friday 20 November, doors at 8', '#e4472b', '#f6ecd8', '#f0b53c'], …] | The posters: the drawing (sun, door, ticket, strings, pint), the big words (the bar | starts a new line), the smaller ones, the last line, the ground, the ink and the accent colour. |
For the stories of a venue, a theatre, a festival or a bookshop that puts up posters: gigs, nights, last tickets, news at the bar. Or as a timed gallery for a poster campaign.
The viewer is 320 pixels wide and as tall as a phone, and on short screens it shrinks keeping its proportions. It is tapped on the right and the left and held with a finger; the long press does not open the system’s menu nor select text. The canvas is drawn at a density of 2 at most.
The time starts stopped (the button says Play) and the stories do not go on by themselves. No flight, bubbles, hand or tear: one poster fades to the wall and the other appears, already dry, without their words sitting on each other; the sticker fades in.
GSAP 3.15.0, Sofia Sans (font variabile) 5.3.0, Sofia Sans Extra Condensed (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 18 KB; the libraries: GSAP 28 KB.
Tested on 6 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.