
The needle gauge
Counters, Physics, SVG
NewFree
The questions of a puzzles-by-post subscription, each with a pencil question mark in the margin. Opening one, the question mark straightens up into an exclamation mark in ink: the hook unbends, the dot drops like a weight and pulls the answer down, the questions below ride down with it and it lands with a squash and a little bounce. Closed again, the stroke is reeled back up and curls into a question once more.
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 question-mark accordion</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/schibsted-grotesk@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the questions of a puzzles-by-post subscription, one answer open at a time. In the margin of each question a
pencil question mark; opening it, the question mark straightens into an exclamation mark in ink, its dot drops
like a weight and pulls the answer open, and the questions below ride down on it. The marks are drawn by the
script -->
<div class="faq" data-faq>
<div class="faq__item" data-item>
<h3 class="faq__head"><button class="faq__q" type="button" id="faq-q1" aria-expanded="false" aria-controls="faq-a1"><span class="faq__text">When does a new puzzle arrive?</span></button></h3>
<div class="faq__a" id="faq-a1" role="region" aria-labelledby="faq-q1" hidden><div class="faq__inner"><p>Every other Friday, in a flat envelope that fits any letterbox: one big puzzle for the weekend and three small ones for the week.</p></div></div>
<svg class="faq__mark" aria-hidden="true" focusable="false"><g><path class="faq__stroke"/><rect class="faq__dot"/></g></svg>
</div>
<div class="faq__item" data-item>
<h3 class="faq__head"><button class="faq__q" type="button" id="faq-q2" aria-expanded="false" aria-controls="faq-a2"><span class="faq__text">What if I get stuck?</span></button></h3>
<div class="faq__a" id="faq-a2" role="region" aria-labelledby="faq-q2" hidden><div class="faq__inner"><p>Every puzzle comes with three sealed hints, each a little less subtle than the last. Open them in order, or hold out: the solutions arrive with the next pack.</p></div></div>
<svg class="faq__mark" aria-hidden="true" focusable="false"><g><path class="faq__stroke"/><rect class="faq__dot"/></g></svg>
</div>
<div class="faq__item" data-item>
<h3 class="faq__head"><button class="faq__q" type="button" id="faq-q3" aria-expanded="false" aria-controls="faq-a3"><span class="faq__text">How hard are the puzzles?</span></button></h3>
<div class="faq__a" id="faq-a3" role="region" aria-labelledby="faq-q3" hidden><div class="faq__inner"><p>Each pack starts gentle and ends tough. Most people crack the big one in an evening, alone or round a table with friends.</p></div></div>
<svg class="faq__mark" aria-hidden="true" focusable="false"><g><path class="faq__stroke"/><rect class="faq__dot"/></g></svg>
</div>
<div class="faq__item" data-item>
<h3 class="faq__head"><button class="faq__q" type="button" id="faq-q4" aria-expanded="false" aria-controls="faq-a4"><span class="faq__text">Can I pause my subscription?</span></button></h3>
<div class="faq__a" id="faq-a4" role="region" aria-labelledby="faq-q4" hidden><div class="faq__inner"><p>Yes, for up to three months, from your account page. Paused packs are never charged, and you can start again whenever you like.</p></div></div>
<svg class="faq__mark" aria-hidden="true" focusable="false"><g><path class="faq__stroke"/><rect class="faq__dot"/></g></svg>
</div>
<div class="faq__item" data-item>
<h3 class="faq__head"><button class="faq__q" type="button" id="faq-q5" aria-expanded="false" aria-controls="faq-a5"><span class="faq__text">Can I give it as a gift?</span></button></h3>
<div class="faq__a" id="faq-a5" role="region" aria-labelledby="faq-q5" hidden><div class="faq__inner"><p>Choose three, six or twelve packs and add a note. The first one goes out in a plain envelope, so the surprise holds.</p></div></div>
<svg class="faq__mark" aria-hidden="true" focusable="false"><g><path class="faq__stroke"/><rect class="faq__dot"/></g></svg>
</div>
</div>
</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.
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 “The question-mark accordion” from Animorio and fit it into my siteThe question mark's line, a Bézier curve that goes from the stem into a 202-degree arc of a circle, is resampled into 40 equal steps, and only the direction of each step is kept. It is drawn on every frame starting from the bottom of the stem and going up one step at a time: so the shape is not a fixed drawing but a list of angles to rebuild it from.
To straighten it, each direction moves towards the vertical by multiplying its difference by (1 − p), and the hook shortens from 33.8 to 16 units: at p = 1 the line is straight and reaches the top of the box. p sits on a quick spring (STRAIGHTEN) when opening and on a slower, springier one when closing, which takes the hook a little past its shape. The mark is not a stroke but a filled shape with a width at every point: even along the question mark, a wedge in the exclamation mark (0.62 strokes by the dot, 1.45 at the top), and thinner when it falls fast.
Opening, the dot leaves with a push of 280 pixels a second and speeds up with GRAVITY; when it reaches the answer's height it bounces back with a share of its speed (BOUNCE) and squashes from its bottom, on a spring that makes it square again. The answer's height is exactly its drop: the answer opens from the top line by line, and the questions below, which sit in the page's normal flow, ride down and bounce with it and never overlap. The sums are done in steps of 1/240 of a second, with the frame step capped at 1/30.
The dot lands on the baseline of the answer's last line, not on its edge: the line is measured with a probe of no size, an inline-block span added at the end of the text and taken away at once, and the dot drops in proportion to the answer, so it gets there when the answer is fully open. The sizes are measured again when the width changes and when the fonts arrive.
Closing, the answer goes back to zero on a spring with no bounce (FOLD), the text fades in 0.14 seconds and below half a pixel the answer is hidden again. The questions are buttons inside an h3 with aria-expanded and aria-controls, the answers regions with aria-labelledby; arrows, Home and End go from one question to another, and the focus ring hugs the question. The marks' colour is a color-mix in CSS between pencil, ink and black, driven by two variables the script writes. The loop runs on GSAP's ticker only while something moves; the list has a fixed height, so its top never moves. The question after the open one has data-autoclic for the preview.
| Name | File | Default | What it changes |
|---|---|---|---|
FIRST | script.js | 1 | The question open when the page opens, from 0; −1 for none. |
ONLY_ONE | script.js | true | Opening a question closes the open one; with false many can be open. |
GRAVITY | script.js | 5200 | How hard the dot falls, in pixels a second squared: the higher, the quicker the answer opens. |
BOUNCE | script.js | 0.3 | How high the dot bounces when it lands, as a share of its speed (0 not at all). |
STRAIGHTEN | script.js | 0.22 | The seconds the question mark takes to straighten up (curling back takes nearly twice as long). |
FOLD | script.js | 0.32 | The seconds an answer takes to fold back up. |
TILT | script.js | 12 | How far a question mark leans in under the mouse, in degrees; 0 not at all. |
STROKE | script.js | 4 | The thickness of the marks, in pixels. |
For the frequently asked questions of a service, a subscription, a game or an event, where the site has a lively, slightly witty tone: the exclamation mark in the margin shows from afar which answer is open and how long it is, and you can see the question finding its answer.
The list is at most 560 pixels wide and on a phone narrows to the screen, with a 16 pixel margin each side (358 on a 390 phone). Closed rows are 76 pixels tall on a computer and 64 on a phone, the questions go down to 18 pixels and the marks to 28 pixels tall, in a 48 pixel margin. Taps open and close like clicks, and a finger pressing a question squashes its question mark.
No fall, no bounces and no leaning: the answer opens and closes at once, the question mark becomes an exclamation mark straight away and the answer's text fades in. When the page opens the list fades in with the second question already open; under the mouse the mark only gets darker.
GSAP 3.15.0, Schibsted Grotesk (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 8.7 KB; the libraries: GSAP 28 KB.
Tested on 6 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.