
The blinking window
Images, Transitions, SVG
NewFree
A darkroom under the red safelight: the tongs lay a white sheet in the developer, the picture comes up slowly from the shadows to the lights, then it goes into the fixer and ends up pinned to the drying line, where it swings and drips. Three pictures drawn in SVG; a click develops the next 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 darkroom print</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/oswald@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="scena" aria-label="A darkroom: prints come up in the developer and are hung up to dry. Click to develop the next one.">
<!-- the room: the script places every piece for a wide or a tall screen and builds the trays, the pins and the prints -->
<svg class="palco" data-stage xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Lowlight print lab">
<defs>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="var(--glow)" stop-opacity="0.55" />
<stop offset="0.45" stop-color="var(--glow)" stop-opacity="0.16" />
<stop offset="1" stop-color="var(--glow)" stop-opacity="0" />
</radialGradient>
<linearGradient id="bench-shade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="var(--bench-top)" />
<stop offset="0.5" stop-color="var(--bench)" />
<stop offset="1" stop-color="var(--room)" />
</linearGradient>
<linearGradient id="slosh" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="var(--shine)" stop-opacity="0" />
<stop offset="0.5" stop-color="var(--shine)" stop-opacity="0.22" />
<stop offset="1" stop-color="var(--shine)" stop-opacity="0" />
</linearGradient>
<linearGradient id="wet" x1="0" y1="0" x2="1" y2="1">
<stop offset="0.2" stop-color="var(--shine)" stop-opacity="0" />
<stop offset="0.42" stop-color="var(--shine)" stop-opacity="0.5" />
<stop offset="0.5" stop-color="var(--shine)" stop-opacity="0.12" />
<stop offset="0.62" stop-color="var(--shine)" stop-opacity="0.32" />
<stop offset="0.8" stop-color="var(--shine)" stop-opacity="0" />
</linearGradient>
<filter id="soft" x="-30%" y="-30%" width="160%" height="160%"><feGaussianBlur stdDeviation="9" /></filter>
<filter id="soft-small" x="-30%" y="-30%" width="160%" height="160%"><feGaussianBlur stdDeviation="3" /></filter>
<!-- the three pictures, drawn in greys (272 x 172): the developer brings them up from the shadows to the lights -->
<symbol id="pic-lake" viewBox="0 0 272 172">
<linearGradient id="lake-sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#c4c4c4" /><stop offset="1" stop-color="#f4f4f4" /></linearGradient>
<linearGradient id="lake-water" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#d6d6d6" /><stop offset="1" stop-color="#7a7a7a" /></linearGradient>
<rect width="272" height="112" fill="url(#lake-sky)" />
<circle cx="182" cy="72" r="30" fill="#fbfbfb" opacity="0.7" />
<circle cx="182" cy="72" r="15" fill="#ffffff" />
<path d="M0 96 L34 72 L58 84 L102 50 L132 74 L150 66 L192 86 L226 64 L272 82 V112 H0Z" fill="#a2a2a2" />
<path d="M102 50 L112 60 L106 59 L98 66 L93 58Z M226 64 L236 72 L229 71 L221 76Z" fill="#dcdcdc" />
<path d="M0 104 Q50 88 106 102 T200 98 T272 100 V114 H0Z" fill="#646464" />
<rect y="110" width="272" height="62" fill="url(#lake-water)" />
<path d="M150 116 H214 M160 122 H204 M168 128 H196 M174 134 H190 M178 140 H186" stroke="#ffffff" stroke-width="2.4" stroke-linecap="round" />
<path d="M96 136 Q110 142 128 136 L124 132 H100Z M111 132 V116 L123 130Z" fill="#202020" />
<path d="M0 172 V118 L14 120 L30 132 L52 150 L78 172Z" fill="#161616" />
<path d="M18 128 L26 74 L34 128Z M8 132 L14 92 L20 132Z M36 136 L44 98 L52 136Z M22 104 L26 86 L30 104Z" fill="#0c0c0c" />
<path d="M272 172 V112 L252 116 L236 140 L222 172Z" fill="#141414" />
<path d="M246 124 L254 70 L262 124Z M258 120 L264 90 L270 120Z" fill="#0c0c0c" />
</symbol>
<symbol id="pic-still" viewBox="0 0 272 172">
<linearGradient id="still-wall" x1="0" y1="0" x2="1" y2="0.3"><stop offset="0" stop-color="#7a7a7a" /><stop offset="1" stop-color="#2c2c2c" /></linearGradient>
<linearGradient id="still-pear" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#f2f2f2" /><stop offset="0.55" stop-color="#b8b8b8" /><stop offset="1" stop-color="#4a4a4a" /></linearGradient>
<linearGradient id="still-cup" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#ffffff" /><stop offset="0.6" stop-color="#d8d8d8" /><stop offset="1" stop-color="#7c7c7c" /></linearGradient>
<rect width="272" height="124" fill="url(#still-wall)" />
<rect y="120" width="272" height="52" fill="#a8a8a8" />
<rect y="118" width="272" height="4" fill="#e6e6e6" />
<ellipse cx="102" cy="140" rx="36" ry="6" fill="#6a6a6a" />
<ellipse cx="176" cy="142" rx="34" ry="6" fill="#6a6a6a" />
<ellipse cx="236" cy="138" rx="28" ry="5" fill="#6a6a6a" />
<path d="M66 140 V74 Q66 60 74 52 V22 H86 V52 Q94 60 94 74 V140Z" fill="#161616" />
<rect x="72" y="16" width="16" height="8" rx="2" fill="#2e2e2e" />
<path d="M71 70 Q72 60 77 56 V30" stroke="#e4e4e4" stroke-width="3" fill="none" stroke-linecap="round" />
<path d="M70 84 H90 V112 H70Z" fill="#d0d0d0" />
<path d="M150 140 C124 140 116 118 124 104 C130 94 136 92 138 80 C140 68 160 68 162 80 C164 92 170 94 176 104 C184 118 176 140 150 140Z" fill="url(#still-pear)" />
<path d="M150 70 Q151 60 156 54" stroke="#1a1a1a" stroke-width="3" fill="none" stroke-linecap="round" />
<path d="M155 60 Q168 50 176 58 Q166 66 155 60Z" fill="#3a3a3a" />
<ellipse cx="134" cy="112" rx="5" ry="9" fill="#ffffff" opacity="0.7" />
<path d="M210 98 H260 L256 136 Q235 142 214 136Z" fill="url(#still-cup)" />
<ellipse cx="235" cy="98" rx="25" ry="5" fill="#2a2a2a" />
<path d="M258 106 Q272 108 268 120 Q264 128 255 127" stroke="#bdbdbd" stroke-width="5" fill="none" />
</symbol>
<symbol id="pic-light" viewBox="0 0 272 172">
<linearGradient id="light-sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#9c9c9c" /><stop offset="1" stop-color="#e6e6e6" /></linearGradient>
<rect width="272" height="118" fill="url(#light-sky)" />
<path d="M18 36 Q30 22 48 30 Q60 20 76 32 Q88 34 84 44 H20 Q10 42 18 36Z M120 22 Q130 12 144 20 Q156 14 162 26 H118Z" fill="#f6f6f6" />
<path d="M196 64 L40 40 L40 78Z" fill="#ffffff" opacity="0.55" />
<rect y="112" width="272" height="60" fill="#5e5e5e" />
<path d="M0 108 H120 L110 114 H0Z" fill="#8a8a8a" />
<path d="M14 126 Q22 122 30 126 M58 138 Q68 133 78 138 M24 154 Q36 148 48 154 M96 128 Q104 124 112 128 M120 150 Q130 145 140 150 M80 162 Q92 156 104 162" stroke="#b4b4b4" stroke-width="2.4" fill="none" stroke-linecap="round" />
<path d="M150 172 L158 124 L176 104 L196 96 L230 92 L254 100 L272 98 V172Z" fill="#222222" />
<path d="M162 172 L168 140 L184 122 L200 118 L196 140 L188 172Z" fill="#3c3c3c" />
<path d="M200 96 L204 46 H222 L226 96Z" fill="#f2f2f2" />
<path d="M203 84 H223 L224 72 H202Z M204 60 H222 L222.6 50 H203.4Z" fill="#1e1e1e" />
<path d="M218 46 L222 46 L226 96 L221 96Z" fill="#9a9a9a" opacity="0.6" />
<rect x="198" y="42" width="30" height="5" fill="#1a1a1a" />
<rect x="203" y="30" width="20" height="12" fill="#ffffff" />
<path d="M203 30 H223 L213 20Z" fill="#1a1a1a" />
<path d="M60 60 l5 3 l5 -3 M84 52 l4 2.5 l4 -2.5 M72 70 l3 2 l3 -2" stroke="#1a1a1a" stroke-width="1.6" fill="none" stroke-linecap="round" />
</symbol>
</defs>
<rect class="pareti" x="-4000" y="-4000" width="9600" height="9600" data-wall />
<g data-glow-place><ellipse rx="760" ry="620" fill="url(#glow)" /></g>
<!-- the safelight: a hood on a cord, its red glass lit -->
<g data-lamp>
<line x1="0" y1="-900" x2="0" y2="56" class="lampada__filo" />
<path d="M-74 118 L-52 56 H52 L74 118Z" class="lampada__cappa" />
<rect x="-10" y="44" width="20" height="16" rx="3" class="lampada__attacco" />
<path d="M-66 118 H66 L58 136 H-58Z" class="lampada__vetro" />
<path d="M-50 122 H30" class="lampada__riflesso" />
</g>
<!-- the sign of the lab -->
<g data-sign>
<rect x="-150" y="-46" width="300" height="92" rx="6" class="insegna" />
<rect x="-140" y="-36" width="280" height="72" rx="3" class="insegna__bordo" />
<text y="-2" class="insegna__nome" data-lab-name>LOWLIGHT</text>
<text y="24" class="insegna__sotto">PRINT LAB · DOOR STAYS SHUT</text>
</g>
<!-- the drying line and its hook -->
<g data-hook><circle r="9" class="gancio" /><circle r="3.5" class="gancio__foro" /></g>
<path data-wire class="filo" />
<g data-hung></g>
<g data-pins></g>
<!-- the bench -->
<rect x="-4000" width="9600" height="5000" fill="url(#bench-shade)" data-bench />
<rect x="-4000" width="9600" height="6" class="banco__bordo" data-bench-edge />
<!-- the box of printing paper -->
<g data-box>
<rect x="-153" y="-82" width="306" height="176" rx="6" class="scatola__ombra" filter="url(#soft)" />
<rect x="-145" y="-86" width="290" height="164" rx="5" class="scatola" />
<rect x="-133" y="-74" width="266" height="122" rx="3" class="scatola__dentro" />
<rect x="-127" y="-70" width="254" height="112" rx="2" class="scatola__fogli" />
<rect x="-127" y="-70" width="254" height="108" rx="2" class="scatola__foglio" />
<g data-box-slot></g>
<rect x="-145" y="52" width="290" height="26" rx="2" class="scatola__fronte" />
<text y="70" class="etichetta etichetta--scura" data-box-label>LOWLIGHT · FIBRE · GLOSSY</text>
</g>
<g data-trays></g>
<!-- the darkroom timer -->
<g data-timer>
<rect x="-110" y="-62" width="220" height="132" rx="10" class="orologio__ombra" filter="url(#soft)" />
<rect x="-104" y="-66" width="208" height="124" rx="10" class="orologio" />
<rect x="-88" y="-50" width="176" height="74" rx="5" class="orologio__vetro" />
<text x="-76" y="-28" class="orologio__fase" data-timer-label>READY</text>
<text x="78" y="14" class="orologio__cifre" data-timer-digits>0:00</text>
<rect x="-86" y="34" width="40" height="12" rx="6" class="orologio__tasto" />
<rect x="-38" y="34" width="40" height="12" rx="6" class="orologio__tasto" />
</g>
<g data-air></g>
<!-- the bamboo tongs: the tips sit on 0 0, the script opens and closes them -->
<g data-tongs-place>
<g data-tongs>
<line y1="-300" class="pinza__braccio" data-arm-a />
<line y1="-300" class="pinza__braccio" data-arm-b />
<line class="pinza__gomma" data-tip-a />
<line class="pinza__gomma" data-tip-b />
<rect x="-11" y="-286" width="22" height="16" rx="4" class="pinza__elastico" />
</g>
</g>
<g data-fx></g>
</svg>
</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.
37 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 darkroom print” from Animorio and fit it into my siteThe stage is an SVG with a fixed viewBox, 1600 by 1000 on a wide screen and 900 by 1600 on a tall one, and preserveAspectRatio meet; the wall and the bench are much bigger rectangles, so there is never an empty edge. The script picks the room from the window's proportions, places the lamp, the sign, the box, the timer, the trays and the clothespins, and works out how much is really visible beyond the viewBox: the line runs off the screen and the prints, when the line is pulled, leave there.
The three pictures are symbols drawn in greys inside the page. Every sheet has its own filter: a feComponentTransfer with a table of eleven values, then a feColorMatrix that brings the grey between the black of the print and the colour of the paper under the red light. As the development p goes from 0 to 1 the table is rewritten: a grey with darkness D starts to show at (1 − D) × 0.62 and is done 0.38 later, with a soft curve. So the blacks come first and the lights last, as in a real tray.
Every sheet has its origin in the middle of its top edge, where the tongs take it and the clothespin grips it: place, rotation, size and how flat it is (fy) live in an object GSAP animates, and a ticker writes them as a transform. Lying in a tray fy is 0.6, so the sheet looks seen from above; lifted, fy goes back to 1 and the sheet hangs straight from the tongs. While the tongs carry it, the ticker puts their tip on the edge of the sheet, so they never come apart. The sheet changes group at every step (box, air, tray, line) to sit under the liquid or above the bench.
Every tray has its floor, the sheet, a see-through film of liquid on top and a streak of shine that runs from side to side as it rocks, all cut by a clipPath. The rings are ellipses as flat as the sheet that grow and fade; the splashes make a quick arc. The drops start on the bottom edge of the sheet (counting its rotation), swell with back.out, fall with power2.in, stretching, and, if they land in a tray, leave a little ring.
The line is a quadratic curve: its control point drops by the sag, and the sag grows with every print hung and bounces elastically when a clothespin snaps shut. Clothespins and prints read their height and its slope from the line. Every print is one timeline (out of the box, developer, fixer, line) that calls the next after a pause; after the third the line is pulled and it starts again the same. A click skips the pause or sets the timeline to timeScale 3.
| Name | File | Default | What it changes |
|---|---|---|---|
LAB | script.js | 'Lowlight' | The name of the lab, on the sign and on the box of paper. |
DEVELOP | script.js | 3.4 | How many seconds the picture takes to come up in the developer. |
RIPPLES | script.js | 3 | How many rings run across the liquid when a sheet goes in. |
DRIPS | script.js | 1 | How much the hung prints drip (0 = dry). |
SWING | script.js | 1 | How far a print swings when the clothespin snaps shut or you hover over it. |
SPEED | script.js | 1 | The speed of the whole animation: 2 is twice as fast, 0.5 half as fast. |
PAUSE | script.js | 0.6 | How many seconds pass between one print and the next. |
For the page of a photographer, a print lab, a film photography course or an exhibition: an opening that tells the craft, a gallery where the pictures arrive one at a time, a loading screen. Other greyscale pictures can take the place of the drawings.
The same animation in a tall room: the lamp and the sign at the top, the line below them with its three clothespins, the two trays side by side and the box and the timer underneath. A tap develops the next print.
Nothing moves: the sheet fades in in the developer, the picture comes up from the shadows to the lights, then the sheet fades out and fades back in on the line. No tongs, rings, swinging or drops; after the third print the line empties with a fade.
GSAP 3.15.0, Oswald (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 13 KB; the libraries: GSAP 28 KB.
Tested on 10 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.