
Stacking certificates
Hover, Images
NewFree
Each render of a CGI studio sits under a flat field of colour, with a black dot wherever the picture is dark: at rest it looks like a halftone print. With the mouse over it the dots shrink one by one, each after its own wait, the colour fades and the full-colour render shows through; move away and the halftone comes back.
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 halftone that melts away</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/space-grotesk@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/jetbrains-mono@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- the work page of a small CGI studio: every render is hidden under a flat field of colour, with black dots where
the picture is dark; over a card the dots shrink one by one and the field fades, and the render comes through -->
<main class="pagina">
<header class="testa">
<p class="testa__marchio">Lumen Club</p>
<h1 class="testa__titolo">Selected renders</h1>
<p class="testa__nota">CGI for brands, 2026</p>
</header>
<ul class="griglia">
<li>
<a class="carta" href="#" data-carta data-scena="sfera">
<span class="carta__quadro">
<canvas class="carta__render" data-render aria-hidden="true"></canvas>
<canvas class="carta__retino" data-retino aria-hidden="true"></canvas>
</span>
<span class="carta__nome">Orbit</span>
<span class="carta__dove">a perfume launch</span>
</a>
</li>
<li>
<a class="carta" href="#" data-carta data-scena="anello">
<span class="carta__quadro">
<canvas class="carta__render" data-render aria-hidden="true"></canvas>
<canvas class="carta__retino" data-retino aria-hidden="true"></canvas>
</span>
<span class="carta__nome">Halo</span>
<span class="carta__dove">a headphone brand</span>
</a>
</li>
<li>
<a class="carta" href="#" data-carta data-scena="pila">
<span class="carta__quadro">
<canvas class="carta__render" data-render aria-hidden="true"></canvas>
<canvas class="carta__retino" data-retino aria-hidden="true"></canvas>
</span>
<span class="carta__nome">Stack</span>
<span class="carta__dove">a furniture fair</span>
</a>
</li>
<li>
<a class="carta" href="#" data-carta data-scena="arco">
<span class="carta__quadro">
<canvas class="carta__render" data-render aria-hidden="true"></canvas>
<canvas class="carta__retino" data-retino aria-hidden="true"></canvas>
</span>
<span class="carta__nome">Arch</span>
<span class="carta__dove">a music festival</span>
</a>
</li>
</ul>
</main>
<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.
21 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 halftone that melts away” from Animorio and fit it into my siteEach card has a canvas with the render, painted by the script with gradients: a dark room and an object lit from the upper left. What matters is that the shadows are really dark, below DARK: that is where the dots go, and the shape can be read in the halftone too.
The render is read in small, at most SAMPLE pixels wide. The grid has cells as big as the card’s short side divided by CELL[0], never less than CELL[1] pixels. Each cell looks at the brightness of its centre (0.299 red, 0.587 green, 0.114 blue) and, if it is below DARK, gets a dot.
Each dot has a wait between 0 and 1, taken from a hash of its row and column: it looks random, but it is always the same, so resizing the window does not make the pattern flicker and the dots come back in the order they left.
A number p goes from 0 to 1 in DURATION milliseconds, in a straight line. The field is drawn with an opacity of 1 minus p; each dot has a radius of half a cell times 1 minus (p minus its wait) divided by (1 minus the wait), so it stays full until p passes its wait and then shrinks. All the circles share one path and are filled at once.
The card to uncover is chosen by the script by looking at which element is under the mouse on every move, so it also works with the preview’s autopilot; with the keyboard the card with the focus uncovers, with a finger the one that was tapped.
| Name | File | Default | What it changes |
|---|---|---|---|
CELL | script.js | [150, 3] | The grid: the card’s short side divided by the first number, never less than the second in pixels (as on the site). |
DARK | script.js | 0.26 | How dark a spot must be to get its dot, from 0 (black) to 1 (white); 0.26 on the site. |
DURATION | script.js | 520 | The milliseconds for the dots to melt and come back (240 on the site). |
SAMPLE | script.js | 1024 | The largest width the render is read at to place the dots. |
For a studio’s work page, a product gallery or an archive of covers: at rest the page has a single graphic look, and the real picture is earned by passing over it.
The same, with the cards one under the other: one tap melts the halftone and another puts it back.
The dots do not shrink: the whole halftone fades away and comes back the same way.
Space Grotesk (font variabile) 5.3.0, JetBrains Mono (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 6.2 KB, and no libraries are needed.
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.