
The needle gauge
Counters, Physics, SVG
NewFree
In the minimal style: noughts and crosses against the barista, to pass the time for a café’s customers while they wait. The grid is four thin hairlines that draw themselves on arrival and are strings: when an X or an O lands next to them they give a pixel or two and settle. The X and O are written stroke by stroke, the pen going a hair too far and coming back; the barista thinks, glances at a square it does not take and then writes. The winning row is drawn in vermilion over its three squares and plucks the strings it crosses, the others fade; Play again rewinds the game from the last move.
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>Noughts and crosses in pen</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- a game of noughts and crosses against the barista, for a café's customers while the coffee brews. The four
grid lines are thin strings drawn by the script: they draw themselves on arrival and give a little when a
mark lands next to them. X and O are written stroke by stroke, the barista thinks for a moment before its
move, the winning row is drawn in vermilion over its three squares and the rest fade. Play again rewinds
the game, the last move first. The nine squares are real buttons: arrows move between them, Enter plays -->
<section class="game" aria-labelledby="ttt-title" data-game data-autoclic>
<h2 class="game__title" id="ttt-title" data-title>Beat the barista</h2>
<div class="board" data-board>
<svg class="board__ink" viewBox="0 0 300 300" aria-hidden="true" data-ink>
<g class="board__grid" data-grid></g>
<g class="board__marks" data-marks></g>
<path class="board__ghost" data-ghost-x />
<path class="board__ghost" data-ghost-o />
<path class="board__win" data-win />
</svg>
<div class="cells" role="group" aria-label="Board, three squares by three" data-cells>
<button class="cell" type="button" tabindex="-1" data-cell aria-label="Top left, empty"></button>
<button class="cell" type="button" tabindex="-1" data-cell aria-label="Top middle, empty"></button>
<button class="cell" type="button" tabindex="-1" data-cell aria-label="Top right, empty"></button>
<button class="cell" type="button" tabindex="-1" data-cell aria-label="Middle left, empty"></button>
<button class="cell" type="button" tabindex="0" data-cell aria-label="Centre, empty"></button>
<button class="cell" type="button" tabindex="-1" data-cell aria-label="Middle right, empty"></button>
<button class="cell" type="button" tabindex="-1" data-cell aria-label="Bottom left, empty"></button>
<button class="cell" type="button" tabindex="-1" data-cell aria-label="Bottom middle, empty"></button>
<button class="cell" type="button" tabindex="-1" data-cell aria-label="Bottom right, empty"></button>
</div>
</div>
<p class="status" aria-live="polite" data-status>Pick a square. You’re X.</p>
<div class="foot">
<p class="score" data-score>You <b data-n="X">0</b><span class="score__sep">·</span>Barista <b data-n="O">0</b><span class="score__sep">·</span>Draws <b data-n="D">0</b></p>
<button class="again" type="button" data-again><span class="again__bg" aria-hidden="true"></span><span class="again__label">Play again</span></button>
</div>
</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.
25 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 “Noughts and crosses in pen” from Animorio and fit it into my siteThe four hairlines are SVG paths in a 300 by 300 viewBox (a square is 100), with vector-effect non-scaling-stroke so they stay one and a half pixels at any size. Each one has how much of it is drawn (t) and a damped spring (STRING: stiffness and damping) that bends it at the point where it was plucked: every frame it becomes a quadratic curve with its control point there, twice as far as the bend.
The springs are integrated on GSAP’s ticker in four sub-steps, with the step capped at 1/30 of a second; the tweens only move numbers (how much of a stroke is written, the opacity) and mark the frame as dirty. When the strings are still and no tween has moved anything, the loop takes itself off the ticker.
An X is two strokes, a number from 0 to 1 each, the second starting before the first has settled; an O is an arc rebuilt every frame as a polyline, 364 degrees anticlockwise. Each number goes up to 1 + OVERSHOOT and comes back to 1 with a sine.out: the pen goes a hair past the end and stops. When the move is written, the strings around the square get a push outwards (PLUCK), full for those beside it and smaller for the others.
It plays full minimax (winning sooner is worth more), with a chance SLIP of picking a worse move, or nobody would ever win. It waits a random time within THINK and meanwhile draws and takes back half a pale O in a square it will not take; when the move is forced it does not look around and is quicker. The whole turn is a GSAP timeline, so it stops with the workbench like everything else.
The vermilion line goes from the middle of a square at one end of the row to the middle of the other, stretched by 38 units each way; before drawing it, where it crosses each string is worked out, and as the stroke moves on each string is plucked when the pen goes over it. Play again brings the faded moves back to full, takes back the line and erases the moves in reverse order, each stroke backwards, with a gap that shortens by 14% at every move.
The nine squares are real buttons in a group, with a single tabindex 0 that moves with the arrows, Home and End; each square says its name and what is in it (Top left, X) and aria-disabled when it cannot be played, the status line is aria-live. A click from a script on the component (the library’s preview, detail 0) plays a whole game written in advance, with a fork, the win, the pause and the rewind; a real hand interrupts it and the game goes on from there.
| Name | File | Default | What it changes |
|---|---|---|---|
SLIP | script.js | 0.2 | How often the barista misses its best move: 0 never loses, 1 plays at random. |
THINK | script.js | [0.55, 0.95] | The barista’s pause before a move, at random between these two, in seconds. |
DRAW | script.js | 0.3 | The seconds one stroke takes to write (an O takes one and a half times as long). |
OVERSHOOT | script.js | 0.04 | How far the pen goes past the end of the stroke before coming back (0.04 = 4%). |
PLUCK | script.js | 1.8 | How far the grid lines give, in pixels, when a move lands next to them. |
STRING | script.js | [1700, 0.11] | The grid lines’ spring: the stiffness (higher quivers faster) and the damping (lower quivers for longer). |
FADE | script.js | 0.16 | The opacity of the moves outside the winning row. |
For the site or digital menu of a café, a bar or a pastry shop, the waiting page of an order or a table, a venue’s screen, a 404 or maintenance page: anywhere someone waits a few minutes and a small, careful game keeps them company.
The grid is at most 300 pixels wide and fits the screen with 32 pixels on each side; the squares are almost a third of the grid, comfortable under a finger. Pressing a free square shows the pale X, like hovering with the mouse, and letting go writes it. Under 360 pixels the status line and the score condense on the font’s width axis to stay on one line.
No running strokes and no quivering strings: the grid and the title appear with a fade, the X and O light up already whole, the vermilion line appears over the three squares and the others fade, and Play again fades everything out together. The game, the barista and the keyboard stay the same.
GSAP 3.15.0, Archivo (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 11 KB; the libraries: GSAP 28 KB.
Tested on 16 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.