
Standing letters with a long shadow
Text, Scroll, 3D
NewFree
Scroll inside the preview
A word just written in fresh ink, heavy and soft, with a shine sliding over it. As you scroll a rocker blotter rolls across it, the baseline gives under its weight and every letter comes out dry: light, sharp, with a tiny breath. At the end the blotter lifts, turns over and shows the letters it took, mirrored and blue. Minimal style.
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>Ink blotted dry</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- a word just written in fresh ink, heavy and soft; as you scroll a rocker blotter rolls across it and every letter
comes out dry, light and sharp. At the end the blotter lifts, turns over and shows the ink it took, mirrored.
The word and the numbers are in the values at the top of script.js -->
<main>
<section class="racconto" data-racconto>
<div class="palco">
<header class="testa">
<p class="marchio">Quire</p>
<p class="sezione">Writing room</p>
</header>
<div class="foglio">
<div class="riga" data-riga>
<!-- the word for screen readers; the script splits it into letters -->
<p class="parola" data-parola aria-label="Sincerely"></p>
<svg class="base" data-base aria-hidden="true" preserveAspectRatio="none">
<path class="base__linea" data-base-linea d="" />
<g data-tacche></g>
</svg>
<div class="tampone" data-tampone aria-hidden="true">
<div class="tampone__giro" data-giro>
<div class="tampone__faccia tampone__faccia--sopra">
<span class="tampone__piastra" data-piastra></span>
<span class="tampone__pomo" data-pomo></span>
</div>
<div class="tampone__faccia tampone__faccia--sotto" data-impronta></div>
</div>
</div>
</div>
</div>
<footer class="piede">
<p class="cenno" data-cenno>Scroll to blot the ink</p>
<p class="stato"><span class="stato__nome">Ink</span> <span class="stato__valore" data-stato>wet</span></p>
</footer>
</div>
</section>
<section class="coda">
<p class="coda__testo">Quire sells writing paper,<br>nibs and rocker blotters.</p>
<p class="piccolo">Letters that dry before you fold them</p>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.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 “Ink blotted dry” from Animorio and fit it into my siteFraunces has three axes: weight (wght), optical size (opsz) and softness (SOFT). Fresh ink is weight 820 and SOFT 100, which rounds the corners like a spreading drop; dry is weight 420 and SOFT 0. A text-shadow with its blur in em makes the halo, and a copy of the letter with a gradient in background-clip: text makes the sliding shine.
At the start and on every resize the script measures each letter wet and dry. As it dries, the letter takes a padding-inline equal to half the width it loses: the word stays the same length and every letter keeps its centre, so the blotter always knows where they are.
A single tween with ScrollTrigger and scrub carries a number from 0 to 1. Up to 0.1 the blotter settles, up to 0.7 it crosses the word, then it lifts and turns. The drying front sits under the blotter, a third of its width in: each letter dries with a 0.3 em wide smoothstep around its centre, and the weight follows a back.out curve, so it goes a little below the dry weight and comes back.
While it runs the blotter has a rotationY swinging a few degrees (the rocking, with perspective on the line), the plate slides the opposite way and the shadow is a CSS variable recomputed every frame. The baseline is an SVG path redrawn with a Gaussian under the centre of the blotter: it sinks where it presses.
The blotter has two faces with backface-visibility: hidden, the bottom one already turned 180 degrees. GSAP turns the container with rotationX; underneath, every letter has its mirrored copy (scaleX(-1)) in a different spot, and its opacity grows with how dry the letter is.
| Name | File | Default | What it changes |
|---|---|---|---|
WORD | script.js | 'Sincerely' | The word written in fresh ink. |
WET | script.js | 820 | The weight of the fresh ink. |
DRY | script.js | 420 | The weight of the dry letters. |
SOFT | script.js | 100 | How soft the wet edges are (the Fraunces SOFT axis, 0 to 100). |
BLEED | script.js | 0.03 | The halo of the wet ink, in em. |
ROCK | script.js | 5 | How much the blotter rocks, in degrees. |
DIP | script.js | 0.035 | How much the baseline gives under the blotter, in em. |
SCRUB | script.js | 0.6 | How many seconds the word takes to catch up with the scroll. |
WRITE | script.js | 1.2 | How long the word takes to be written when the page opens, in seconds. |
For the page of a stationery shop, a calligraphy studio or a letters and invitations service, for the signature at the end of a letter or an about page, for the opening of an article about handwriting. It works with a single word, five to ten letters long.
The same: the word is sized on both the width and the height of the screen, and the blotter always stays inside the page.
No blotter, no movement and no shine: as you scroll the letters dry one after the other in place, losing weight and halo, and «Ink» turns to «dry».
GSAP 3.15.0, ScrollTrigger (GSAP) 3.15.0, Fraunces (font variabile) 5.3.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 7.1 KB; the libraries: GSAP + ScrollTrigger 45 KB.
Tested on 23 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.