Skip to content
animorio
IT
All components

One-sentence stories

Stories, Text, TransitionsGSAP, CSS onlyAdvancedCompressed weight: Demo 12 KBGSAP 28 KBFreeDownload the zip

The stories of a small museum of drawing made of type alone, in the minimal style: every story is one big sentence. Its words come in one after the other while the font’s axes open up, and the story reads itself in its own time, letter by letter, while the thin bar at the top fills: the word in the accent colour takes its colour and its slant only when it is read. Tap on the right to go on, on the left to go back, hold to pause.

The code

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>One-sentence stories</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/full.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the stories of a small museum of drawing, Velum, made of type alone: every story is one sentence. Its words
         come in one after the other while the font's width and weight open up, and the story reads itself in its own
         time, letter by letter, while the thin bar at the top fills: the word in the accent colour takes its colour
         and its slant only when it is read. A tap on the right goes on, on the left back; holding stops the time.
         The sentences are written by script.js, from STORIES -->
    <section class="stories" aria-label="Stories from Velum" aria-roledescription="stories" data-stories data-autoclic>
      <div class="viewer" data-viewer>
        <div class="bars" aria-hidden="true" data-bars></div>

        <div class="head" data-head>
          <span class="head__name">Velum</span>
          <span class="head__when" aria-hidden="true">
            <span class="head__ago" data-ago></span>
            <span class="head__paused" data-paused-word>Paused</span>
          </span>
          <button class="head__pause" type="button" aria-label="Pause the stories" data-pause>
            <svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true"><path d="M8.5 5.5v13M15.5 5.5v13"/></svg>
            <svg class="i-play" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5.8v12.4l10-6.2z"/></svg>
          </button>
        </div>

        <div class="text" aria-hidden="true" data-text>
          <div class="measure" data-measure></div>
        </div>
        <p class="sr" data-now></p>

        <button class="tap tap--back" type="button" aria-label="Previous story" data-back>
          <svg class="tap__sign" viewBox="0 0 24 24" aria-hidden="true"><path d="M14.5 5.5L8 12l6.5 6.5"/></svg>
        </button>
        <button class="tap tap--next" type="button" aria-label="Next story" data-next>
          <svg class="tap__sign" viewBox="0 0 24 24" aria-hidden="true"><path d="M9.5 5.5L16 12l-6.5 6.5"/></svg>
        </button>

        <div class="foot" data-foot>
          <span class="foot__rule" aria-hidden="true" data-rule></span>
          <p class="label" aria-hidden="true" data-label>
            <span class="label__what" data-what></span>
            <span class="label__when" data-when></span>
          </p>
          <button class="again" type="button" hidden data-again>
            <span class="again__word">Watch again</span>
            <svg class="again__sign" viewBox="0 0 24 24" aria-hidden="true"><path d="M18.4 9.2A7 7 0 1 0 19 13.6"/><path d="M19.2 4.8v4.6h-4.6"/></svg>
          </button>
        </div>
      </div>
      <p class="sr" aria-live="polite" aria-atomic="true" 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>

Playground

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.

37 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “One-sentence stories” from Animorio and fit it into my site

What it does

On a black background a story viewer in a phone’s proportions, almost black and edged with a fine line: at the top four very thin bars, Velum, 2 h and the pause button; at the bottom a hairline and under it a label in two halves, like a museum’s (North wing, Saturday, 10 am). In the middle, standing on the hairline, a single sentence in narrow Roboto Flex, as big as the viewer allows: The north wing reopens on Saturday. Its words come one after the other from below, narrow and light, and as they rise they widen and take weight; the weight goes a hair too far and settles, the line rises a pixel and comes back. The sentence comes in almost unlit and, while the first bar fills, reads itself: a soft head runs through the letters and each one takes its ink and its grade (fuller, without getting wider), with a little knock on the letter under the head. The word between asterisks, reopens, is a little heavier and takes the vermilion and the slant only when the head goes over it, letter by letter, with the comma or the full stop leaning with it. The sentence is read in the first four fifths of the time, so it stays whole for a moment before the next story. Going on, the old words leave from the first to the last, rising, narrowing and losing weight, and every new word waits exactly until the ones in its way have gone: at no instant does a word show over another. The label and the time change too. Going back the same happens the other way, downwards and from the last word; on the first story the time rewinds and the reading with it. Holding, the time brakes in a quarter of a second and stops, the sentence stays as it is, and Paused comes up in place of 2 h. After the last story the viewer says You are all caught up and offers Watch again.

How it works

A sentence measured in a hidden twin

Every sentence is written first in an invisible twin, as wide as the sentence’s room, at the final axes (wdth WIDTH, wght WEIGHT, the accent word heavier and already slanted) and with text-wrap: balance, so the browser breaks and evens the lines. A bisection finds the biggest size, up to BIGGEST, at which the sentence fits in height and no word runs over. Then every word is made again for real, absolutely positioned where the twin had it: from there the axes can move it without the line moving.

Axes at work, inside their own slot

Every word has three custom properties, --wd, --wg and --wa, which GSAP animates and the letters read in font-variation-settings. New words start from a width NARROW never wider than the final one and from a thin weight, rise TRAVEL ems with back.out(1.5), a pixel too far and back, and the weight lands with back.out(1.8): a hair heavy, then it settles. Since a word held at its left only widens towards its final width, it never touches the next one. The old ones rise, narrow and lose weight in power2.in.

No word over another

The choreography is worked out from the geometry: for every old word it keeps the rectangle it takes on its way out (its place plus its travel) and the instant it will be fully transparent; every new word, in reading order and STAGGER seconds after the one before, waits for the latest of those its way in touches. Going on the words go from the first, going back from the last and downwards.

The story that reads itself

The story’s time is a GSAP tween of DURATION seconds; it fills the bar and runs the reading head, with an index per letter (spaces count as a breath). Every letter goes with a smoothstep 3.2 letters wide from UNREAD to full ink and from grade -150 to 0: Roboto Flex’s GRAD axis changes the thickness without changing the width, so nothing moves; the letter under the head gets a knock of extra grade. The accent letters go from the ink to the vermilion and from slnt 0 to -10 on the same curve. Kerning and ligatures are off, so letters with different settings do not shift by fractions of a pixel.

Holding, braking, rewinding

The tap zones are two real buttons, Previous story and Next story. A press longer than HOLD seconds does not change story: the time’s speed goes to zero in 0.26 s with power2.out (the tween’s timeScale), so bar and reading brake together, and it starts again with power1.in. Back on the first story the time rewinds in 0.45 s and the reading with it. The time and Paused swap places through a slot: one leaves upwards, the other comes up from below. A click from a script on the viewer (the library’s preview) makes a round: first story, on, back, a long press, on to the end.

Values to change

NameFileDefaultWhat it changes
DURATIONscript.js5The seconds each story stays on: the sentence is read in the first four fifths.
HOLDscript.js0.2The seconds a press must last to stop the time instead of changing story.
STAGGERscript.js0.05The seconds between one word coming in and the next.
TRAVELscript.js0.3How far the words travel coming in and going out, in ems.
NARROWscript.js34The font width (25 to 151) the words come in from and go out to; never wider than WIDTH.
WIDTHscript.js60The font width of the sentence once it has landed, 25 to 151: narrower, the sentence comes bigger.
WEIGHTscript.js540The sentence’s weight, 100 to 1000.
ACCENT_WEIGHTscript.js690The weight of the word between asterisks, the one that takes the accent colour.
UNREADscript.js0.34How much ink the letters have before the story reads them, from 0 to 1.
BIGGESTscript.js72The biggest a sentence can be, in pixels on a viewer 320 wide.
STORIESscript.js[['The north wing *reopens* on Saturday.', 'North wing', 'Saturday, 10 am', '2 h'], …]The stories: the sentence (the word between asterisks takes the accent), the label, its second half and how long ago.
LASTscript.jsYou are all *caught up*.The sentence after the last story, over Watch again.

Try them in the Playground

When to use it

For the stories of a museum, a gallery, a magazine or a publisher, where the thing to say is one sentence: an opening, a show, an opening time, a new issue. Or as timed announcements at the top of a page, with no images to prepare.

On phones

The viewer is 320 pixels wide and as tall as a phone, and on short screens it shrinks keeping its proportions: the sizes inside are in cqw and the sentence is measured again. It is tapped on the right and the left and held with a finger; the long press does not open the system’s menu nor select text. The same animations as on a computer.

With reduced motion

The time starts stopped (the button says Play) and the stories do not go on by themselves. The words do not rise nor change axes: the old sentence fades, then the new one fades in word by word, already read, with the accent word already coloured and slanted. The time stops and starts without easing.

Pitfalls

  • Reading on the weight axis would have widened the letters and made the line walk: the reading uses the GRAD axis, which changes the thickness and not the width, plus the opacity.
  • A word coming in or going out from a width larger than its final one runs into the next word: NARROW and the thin weights are capped at WIDTH and WEIGHT, and the words are held at their left.
  • With the same gap between words, new ones showed over old ones where the two sentences have different heights: now every new word waits for the old ones its way in touches.
  • The time and Paused in the same place, crossfading, read 2 hused half way: now they swap through a slot, first one leaves and then the other comes up.
  • The comma after the slanted word stayed upright and looked detached (first ,): the punctuation stuck to the accent word leans with it, without taking the colour.
  • The sentence after the last story has no time running and stayed unlit: it reads itself in 1.3 s as soon as it arrives.
  • The bars’ span is inline: without display: block the 1.5 pixel height did not count and the bars did not show.
  • The focus ring on the two big tap zones cut through the sentence with a line: for the keys the chevron shows in full, heavier, with a hairline under it.

Libraries

GSAP 3.15.0, Roboto Flex (font variabile, con gli assi della larghezza, del peso, del grado e dell’inclinazione) 5.3.0.

Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.

Testing

Tested on 16 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

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.

All components