Skip to content
animorio
IT
All components

The breathing name footer

Footers, Hover, Text, PhysicsGSAP, CSS onlyAdvancedCompressed weight: Demo 12 KBGSAP 28 KBFreeDownload the zip

In the minimal style: the black footer of a studio, with its name along the bottom so big it fills the whole width. The letters feel the pointer and breathe on the weight and width axes, each on a spring of its own, but the word stays justified: when one letter widens the others give the room back, and the edges never move by a pixel. Underneath, a hairline marks the joints and a vermilion line sits under the letter that breathes the most.

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">
  <title>The breathing name footer</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/anybody@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the footer of a small studio: the address, two columns of links, the year, and along the bottom the studio's
         name, so big it fills the width. Its letters feel the pointer and breathe on the weight and width axes, while
         the word stays justified: when one letter takes more room the others give it back, and the edges never move -->
    <footer class="ft" data-footer>
      <i class="ft__rule" aria-hidden="true" data-rule></i>

      <div class="ft__top">
        <div class="ft__reach" data-rise>
          <p class="ft__label">Start a project</p>
          <div class="ft__mailrow">
            <a class="ft__mail" href="mailto:hello@somna.studio" data-breathe data-mail>hello@somna.studio</a>
            <button class="ft__copy" type="button" aria-label="Copy the email address" data-copy>
              <svg viewBox="0 0 20 20" aria-hidden="true">
                <rect class="c-back" x="3.5" y="3.5" width="9" height="9" rx="2" data-copy-back/>
                <rect class="c-front" x="7.5" y="7.5" width="9" height="9" rx="2" data-copy-front/>
                <path class="c-tick" d="M5.2 10.4l3.2 3.2 6.6-7.2" data-copy-tick/>
              </svg>
            </button>
            <span class="ft__copied" aria-live="polite" data-copied></span>
          </div>
        </div>

        <nav class="ft__cols" aria-label="Footer">
          <div class="ft__col" data-rise>
            <h2 class="ft__head">Studio</h2>
            <ul class="ft__list">
              <li><a class="ft__link" href="#" data-breathe>Work</a></li>
              <li><a class="ft__link" href="#" data-breathe>Approach</a></li>
              <li><a class="ft__link" href="#" data-breathe>Journal</a></li>
            </ul>
          </div>
          <div class="ft__col" data-rise>
            <h2 class="ft__head">Elsewhere</h2>
            <ul class="ft__list">
              <li><a class="ft__link" href="#" data-breathe>Instagram</a></li>
              <li><a class="ft__link" href="#" data-breathe>Are.na</a></li>
              <li><a class="ft__link" href="#" data-breathe>Vimeo</a></li>
            </ul>
          </div>
        </nav>
      </div>

      <!-- the name: the script builds one letter per span and solves their widths every frame -->
      <div class="ft__name" data-name></div>

      <!-- the scale under the name: a hairline, a tick at every joint between letters, and the span of the letter
           that breathes the most -->
      <div class="ft__scale" aria-hidden="true" data-scale>
        <i class="ft__base-line" data-base-line></i>
        <i class="ft__span" data-span></i>
      </div>

      <div class="ft__base" data-rise>
        <p class="ft__year"><svg viewBox="0 0 12 12" aria-hidden="true"><circle cx="6" cy="6" r="5.1"/><path d="M7.9 4.5a2.3 2.3 0 1 0 0 3"/></svg><span class="sr">Copyright</span> 2026 Somna Studio</p>
        <nav class="ft__legal" aria-label="Legal">
          <a class="ft__link ft__link--small" href="#" data-breathe>Privacy</a>
          <a class="ft__link ft__link--small" href="#" data-breathe>Imprint</a>
        </nav>
      </div>
    </footer>
  </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.

31 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 “The breathing name footer” from Animorio and fit it into my site

What it does

On a black background the footer of Somna Studio, a made-up studio, 1040 pixels wide and about 410 tall, with no box: a hairline on top, then on the left the invitation to write (Start a project, the address hello@somna.studio and beside it a small button that copies it), on the right two columns of links (Studio: Work, Approach, Journal; Elsewhere: Instagram, Are.na, Vimeo). At the bottom the name, SOMNA, in Anybody, a variable typeface with width from 50 to 150 and weight from 100 to 900, set so big that the ink of the first and the last letter touches the two edges of the footer. Under the name a hairline with a tick at every joint between the letters and two longer ticks at the edges, and the year with the legal links. When the footer comes into view the top hairline draws from the left, the address and the columns rise one after the other, the letters of the name rise out of the baseline as hairlines and take their weight from the middle out; then a wave crosses the name from left to right, so the trick shows before anyone points. With the pointer over it, the letters nearby get blacker and wider, more the closer it is, each one getting there on a spring of its own, with a little swing and a settle; the letters far away narrow and lighten just enough for the word to stay exactly as wide as before: the edges keep still, only the joints move, and the ticks under them follow and grow where the letter next to them breathes. A vermilion line slides under the letter that breathes the most, its two ends on two springs, the leading one a little quicker. When nobody points, after a moment a slow breath travels back and forth through the name by itself. The links breathe too: pointed at, or reached with the Tab key, their letters take weight and width in a small wave that starts from the side the pointer came in, and brighten. The button beside the address copies it: the two little squares go, a vermilion tick draws, Copied appears, and two and a half seconds later everything is as it was.

How it works

A table of widths, measured once

At the start every letter of the name is measured at 100 pixels over a grid of widths (every 10, from 50 to 150) and weights (every 100, from 100 to 900): 99 positions, each a single read for all the letters together. In between, the width is read bilinearly, and since the width grows in proportion to the type size, nothing is measured again when the window changes size.

The solver that keeps the line justified

Every letter has its intensity e (0 at rest, 1 under the pointer): the weight is the rest weight plus e times the difference, the width is lam plus SPREAD times e, within the axis. lam is one for the whole word, and the sum of the widths grows with lam: every frame a 22-step bisection on the table finds the lam that makes the word exactly as wide as the footer, less the spaces between letters. If not even the narrowest lam is enough, SPREAD is scaled down instead. The leftover half pixel goes into a flex with justify-content: space-between, so the edges are still by construction.

Ink on the edges, not the box

Every letter leaves a little room at its sides (its side bearings): with the box on the edges, the S and the A sat a few pixels inside. A canvas reads the ink of the first and the last letter at three weights, as a share of their width; the name reaches past the edges by that room, in the same frame, and the solver counts the extra pixels.

A spring per letter, and a breath of its own

Every intensity chases its goal on a damped spring (SPRING), in steps of 1/120 of a second, with the frame step capped at 1/30. The goal is the largest of three bells: the pointer's (with a strength that rises quickly and falls slowly, and fades out 180 pixels from the footer), the entrance wave's, and the slow breath that travels back and forth when nobody points. The vermilion line has two springs of its own, one for each end.

Links that breathe, in CSS

The letters of each link have font-variation-settings written with calc() on a variable --b; GSAP takes --b from 0 to 1 with a stagger that starts from the side the pointer comes in and a spring as its ease. The link keeps its whole name in aria-label for screen readers.

Values to change

NameFileDefaultWhat it changes
NAMEscript.js'SOMNA'The studio's name, the big word at the bottom: one word (the longer it is, the smaller it gets to fit).
WEIGHTscript.js[270, 860]The weight of a letter at rest and of one right under the pointer (100 to 900).
SPREADscript.js62How much wider the letter under the pointer gets, on the width axis: the others narrow to make room.
REACHscript.js0.8How far the pointer is felt, in letters.
SPRINGscript.js[150, 15]Each letter's spring: how stiff, and how much it is held back (less holds it back, more it wobbles).
BREATHEscript.jstrueWhen nobody points, a slow breath travels through the name by itself.
BREATHscript.js9How many seconds a slow breath lasts, there and back.
DEPTHscript.js0.62How deep the breath on its own goes, from 0 to 1.

Try them in the Playground

When to use it

For the footer of a design studio, an agency, a photographer, a fashion label or a portfolio: anywhere the name is the best thing to leave at the bottom of the page, and words are few.

On phones

The footer takes the width of the screen: the address goes on top and the two columns of links stay side by side, with 44-pixel rows to tap. The name fills the width with narrower letters at rest, so it is taller. The letters breathe under a finger that touches and slides sideways on the name (up and down scrolls the page), and between touches the slow breath travels by itself. A tap on a link makes its letters breathe.

With reduced motion

The name keeps still, justified, at its rest weight: no springs and no breath on its own. Everything fades in. Under the letter pointed at the vermilion line fades in, and moving to another letter it fades out and back in at the new place without sliding. The links only brighten, and the copy button swaps the squares for the tick with a fade.

Pitfalls

  • With the letters' boxes on the edges the S and the A sat a few pixels inside the ticks: their side bearings. Now the name reaches past the edges by the empty room of its first and last letter, read with a canvas.
  • The canvas cannot set the width axis (ctx.fontStretch changes nothing with this typeface): the bearings are read at the normal width as a share of the letter's width, and that share holds at every width.
  • The stagger of a breathing link, cut short by a quick exit with the Tab key, finished afterwards: the letters that had not started yet swelled anyway. Now every new tween removes all the ones on the same letters (overwrite: true), also the parts not started yet.
  • The round of the S and the O goes a little below the baseline and the entrance mask cut it flat: the mask now leaves a few pixels below the baseline.
  • On a phone the page grid grew as wide as the name wanted and the footer ran off the screen: the page column is minmax(0, 1fr).
  • Reading the letters' positions from getBoundingClientRect would also have followed the entrance rise: offsetLeft ignores transforms, so the ticks and the vermilion line always sit on the real joints.

Libraries

GSAP 3.15.0, Anybody (font variabile) 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