
The three-line menu
Menus, Transitions, Physics, Text
NewFree
Generating a PDF invoice shown as setting metal type by hand: press Generate invoice and the lead sorts, their letters raised and back to front, drop one after another into the steel composing stick, are tapped tight and every full line slides down into the brass galley. Then the roller inks the forme, the press comes down with a thump and the sheet lifts and turns over: the invoice printed the right way round, in real-looking ink. Every other run a wrong sort is picked, jumps out, and Retry sets the right 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, viewport-fit=cover">
<title>The composing stick loader</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/instrument-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the composing stick loader: an invoice being generated, shown as a page of metal type being set by hand. Pressed,
the sorts drop one after the other into the steel composing stick, their letters raised and back to front; a
full line is tightened with a tap and slides down into the brass galley. When the forme is complete a roller
inks it, the platen comes down with the sheet and lifts, and the sheet turns over with the invoice printed the
right way round. The metal is drawn in HTML and CSS by script.js, the print is painted on a canvas -->
<section class="press" aria-label="Invoice generation" data-press>
<div class="press__scene" role="progressbar" aria-label="Invoice generation" aria-valuemin="0" aria-valuemax="100"
aria-valuenow="0" aria-valuetext="Ready to generate" data-scene>
<div class="press__fit" data-fit>
<div class="press__box" aria-hidden="true" data-box>
<div class="galley">
<div class="galley__floor"></div>
<div class="galley__rim galley__rim--top"></div>
<div class="galley__rim galley__rim--side"></div>
<div class="galley__rim galley__rim--foot"></div>
</div>
<div class="stick">
<div class="stick__back"></div>
<div class="stick__bed"></div>
<div class="stick__clamp"></div>
<div class="stick__knee"></div>
</div>
<div class="press__type" data-type></div>
<div class="stick__lip"></div>
<div class="roller" data-roller><div class="roller__drum" data-drum></div></div>
<div class="sheet__shadow" data-sheet-shadow></div>
<div class="sheet" data-sheet>
<canvas class="sheet__face sheet__face--back" data-back></canvas>
<canvas class="sheet__face sheet__face--front" data-front></canvas>
</div>
<div class="platen" data-platen><div class="platen__ribs"></div></div>
<div class="press__fly" data-fly></div>
</div>
</div>
</div>
<p class="press__status" aria-hidden="true">
<svg class="press__tick" viewBox="0 0 24 24" data-tick><path d="M5.5 12.6l4.2 4.2 8.8-9.4" pathLength="1"/></svg>
<svg class="press__warn" viewBox="0 0 24 24" data-warn><path d="M12 7.5v6M12 16.6v.1" pathLength="1"/><path d="M10.3 3.9L2.6 17.6a2 2 0 0 0 1.7 3h15.4a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
<span class="press__swap" data-status>Ready to generate</span>
<span class="press__pct" data-pct></span>
</p>
<div class="press__buttons">
<button class="pill is-quiet" type="button" hidden data-again><span class="pill__label">Generate again</span></button>
<button class="pill" type="button" data-go data-autoclic><span class="pill__label" data-label>Generate invoice</span></button>
</div>
<p class="sr" role="status" 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.
47 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 composing stick loader” from Animorio and fit it into my siteEvery sort is exactly as wide as its letter’s advance, measured with measureText on a canvas in the variable font Fraunces, at its line’s weight (720 for the heading, 470 for the client…). The line is set from the knee leftwards in reading order, so the forme is the mirror of the page; if a line does not fit the measure, its size goes down until it does. What is left up to the clamp is filled with quads, lower blocks with no letter.
Every sort is a div with two faces: the face on top, 22 pixels high, with a gradient tilted to the light and a pale edge top left, and the body in front, darker, with the nick drawn by two gradient lines and a shadow. The letter sits on the face with scaleX(-1), raised by three text-shadows (light top left, shadow bottom right, a soft shadow). Every sort has a brightness of its own, between 0.9 and 1.08, like worn type; the glint of the tap is a soft-light ::after moved by a CSS variable that GSAP animates.
One GSAP timeline holds everything: the drops (each turning straight with power2.in and ending a hair loose), the line’s jolt with elastic, the quads, the tap, the line’s trip into the galley with a bounce, the roller, the platen, the sheet. The percentage comes from callbacks placed where each sort lands. In the run that goes wrong the wrong sort goes into the timeline before the right one, followed by an addPause: the timeline stops by itself, and Retry plays it on.
The roller travels over the galley with power1.inOut, and the moment its middle passes over each line is found by inverting the curve: there the line gets the class that turns its letters black. The platen comes down from bigger and higher (closer to the eye) with power3.in, and as it lands the whole scene drops two pixels and springs back. The sheet has two faces in CSS 3D with backface-visibility: turning it 180 degrees about the vertical axis undoes the mirror again, so the print reads the right way; the shadow under it narrows and widens as it turns.
The page is painted once per job, from the same sort widths mirrored around the middle: the paper (grain pixel by pixel on a canvas of its own, a few fibres, a shade at the edges), the bite of the impression (the letter in light shifted down right and in dark up left), then the ink on a canvas of its own, with a different opacity per letter, the squeezed rim from a thin stroke and thousands of specks taken out with destination-out, laid on the paper with multiply. The back is the same paper with the letters back to front, barely visible.
| Name | File | Default | What it changes |
|---|---|---|---|
SECONDS | script.js | 6 | How long setting the lines takes, in seconds; inking and printing add about two seconds (a real generation follows the server’s work). |
LINES | script.js | ['INVOICE INV-0412', 'Marlow & Finch', 'Due 30 October', 'Total €1,840.00'] | The invoice’s lines, four at most: a line too long for the stick gets smaller by itself. |
WEIGHTS | script.js | [720, 470, 420, 640] | The type weight of each line, from 100 to 900: the font is variable, so every weight is real, in the print too. |
FAIL_EVERY | script.js | 2 | In the demo, one generation in how many goes wrong (0 never). |
FAIL_LINE | script.js | 3 | Which line the wrong sort drops into. |
FAIL_SORT | script.js | ['b', 'd'] | The right sort and the wrong one that drops in its place; if the line has no right one, a letter half way along goes wrong. |
LABELS | script.js | { busy: 'Generating…', done: 'Download PDF', saved: 'Downloaded', retry: 'Retry' } | The button’s words while it works, at the end, after the download and after the error; the one at rest, Generate invoice, is changed in index.html. |
WORDS | script.js | { set: 'Laying out INV-0412', render: 'Rendering PDF', done: 'Invoice ready', fail: 'Glyph mismatch on line' } | The line under the drawing while it sets (with the percentage), while it prints, at the end and after the error (followed by the line’s number); the one at rest, Ready to generate, is changed in index.html. |
For a wait of a few seconds that ends with a document: generating an invoice, a quote, a receipt or a contract as a PDF, exporting a laid-out report, preparing a print file. It suits invoicing tools for freelancers and studios, billing or publishing apps, where typography is part of the product. For a wait of a moment it is too long.
The drawing is 360 pixels wide and scales to the width it gets: at 390 it fits whole with 16 pixels on each side, and the letters stay sharp because they are real text; the print canvas paints at least at twice the density. There is room above for the falling sorts. The pills are 44 high and squash and spring back under the finger; the sort waiting above the knee shows with the mouse and the keyboard.
Nothing drops and nothing moves: every line fades in already in its place in the galley, one after the other, the letters turn black line by line with no roller, and at the end the printed sheet fades in already the right way round, with no press and no turn. The wrong sort turns red and fades away. The percentage, the words and the tick update with fades.
GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 15 KB; the libraries: GSAP 28 KB.
Tested on 8 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.