Skip to content
animorio
IT
All components

The hair stylist's card

Cards, Physics, HoverCanvas, GSAPAdvancedCompressed weight: Demo 16 KBGSAP 28 KBFreeDownload the zip

The profile card of a hair stylist, with her portrait drawn by the script and her long copper hair made of real locks: each lock is a chain of points that keeps its waves on springs, has a weight and never stretches. The pointer runs through the hair like fingers, the locks follow it and swing back into place, and she looks at it. Follow makes her toss her head: the hair fans out and falls back, bouncing, while she smiles with her eyes closed.

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 hair stylist's card</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bodoni-moda@5.3.0/opsz.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
  <script>document.documentElement.classList.add('js');</script>
</head>
<body>
  <main class="stage">
    <!-- the profile card of a hair stylist. Her portrait is drawn by the script, and her long copper hair is made of
         locks that move like hair: the pointer runs through them as fingers would, a tap ruffles them, and when she
         is followed she tosses her head and the hair flies out and falls back into its waves -->
    <article class="profile" aria-labelledby="profile-name" data-profile data-autoclic>
      <div class="profile__portrait" data-portrait-box>
        <canvas class="profile__canvas" role="img" aria-label="Portrait of Lucía Ferrer, smiling, with long wavy copper hair" data-portrait></canvas>
      </div>
      <div class="profile__body">
        <h2 class="profile__name" id="profile-name" data-name>Lucía Ferrer</h2>
        <p class="profile__role">Hair stylist and colourist in Valencia</p>
        <dl class="facts">
          <div class="facts__item">
            <dt>Experience</dt>
            <dd>14 years</dd>
          </div>
          <div class="facts__item">
            <dt>286 reviews</dt>
            <dd>
              4.9
              <svg class="facts__star" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3.2l2.6 5.5 6 .8-4.4 4.2 1.1 6-5.3-2.9-5.3 2.9 1.1-6-4.4-4.2 6-.8z"/></svg>
            </dd>
          </div>
          <div class="facts__item">
            <dt>Cut and finish</dt>
            <dd>€48</dd>
          </div>
        </dl>
        <div class="actions">
          <button class="act act--follow" type="button" data-follow>
            <span class="act__icon" aria-hidden="true">
              <svg class="act__glyph" viewBox="0 0 24 24" data-glyph="plus"><path d="M12 5.8v12.4M5.8 12h12.4"/></svg>
              <svg class="act__glyph" viewBox="0 0 24 24" data-glyph="check"><path d="M5.6 12.6l4.1 4.1 8.7-9.2"/></svg>
            </span>
            <span class="act__label" data-label>Follow</span>
          </button>
          <button class="act act--message" type="button" data-message>
            <span class="act__icon" aria-hidden="true">
              <svg class="act__glyph" viewBox="0 0 24 24" data-glyph="bubble"><path d="M5 6.8A2.3 2.3 0 0 1 7.3 4.5h9.4A2.3 2.3 0 0 1 19 6.8v7.1a2.3 2.3 0 0 1-2.3 2.3h-5.2L7.4 19.4v-3.2h-.1A2.3 2.3 0 0 1 5 13.9z"/></svg>
              <svg class="act__glyph" viewBox="0 0 24 24" data-glyph="check"><path d="M5.6 12.6l4.1 4.1 8.7-9.2"/></svg>
            </span>
            <span class="act__label" data-label>Message</span>
          </button>
        </div>
        <p class="sr" aria-live="polite" data-live></p>
      </div>
    </article>
  </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.

39 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 hair stylist's card” from Animorio and fit it into my site

What it does

On a white background a white card with round corners. At the top the portrait, on a sea-green backdrop with a pale light coming from the left: a woman in a cream sweater with a ribbed collar, a gold hoop in her ear and long wavy copper hair, parted almost in the middle; on the left it falls over her shoulder in light locks, on the right it is tucked behind the ear and falls behind the shoulder. Below: Lucía Ferrer in a fine Bodoni, Hair stylist and colourist in Valencia, a row of three facts split by thin rules (14 years, Experience; 4.9 with a drawn star, 286 reviews; €48, Cut and finish) and two pills: Follow, dark green with a plus, and Message, white with a speech bubble. As it arrives the card rises and the hair lifts a little and falls back into place. Her eyes follow the pointer wherever it is on the page, her head leans a little towards it, and now and then she blinks. The pointer passing over the portrait takes along the locks it touches, like a hand through hair: the locks follow it, then swing back into their waves; a click or a tap ruffles them round the point, and a finger resting on the portrait combs them. With the pointer over them the pills swell past their edges with a bounce and their words widen on the font’s width axis; pressed, they squash. Follow makes her toss her head: the head tips to the side and back, the hair fans out on both sides and falls back bouncing, the hoop in her ear swings, her eyes close in a smile and the smile stays wider; the pill becomes Following, pale green with a tick that draws itself. Pressed again, she shakes her head a little and the smile goes back to what it was. Message makes her nod, the pill says Sent with a tick and after a moment goes back to Message.

How it works

The portrait on a board

Everything is drawn on a board of 324 by 300 points brought to the canvas size, with the figure a little enlarged from its foot. The head has a pose, an angle round the base of the neck and a shift: face, eyes, mouth, ear and the crown of the hair are drawn inside that transform, and the neck bends between the still shoulders and the head. The drawing is redone only when something moves; when the hair is still, the loop draws nothing.

The locks

The hair is 35 locks in five groups: behind the head on the left and on the right, leaving along the curve of the skull and then falling; on the right short ones combed back and tucked behind the ear; in front on the left, over the shoulder, and those that leave the parting and fall past the cheek. Each lock is born from a starting angle that turns downwards, with a sine wave along the way, in 12 points. It is drawn as a ribbon: a Catmull-Rom curve through the points, thin at the root, full a third of the way and pointed at the end, with two dark strands inside and the shine on the side of the light.

The physics of a lock

Each point is a Verlet point, in steps of 1/240 of a second with the frame step capped at 1/30: it keeps its speed (which dies down in SWING seconds) and the weight WEIGHT. Then the lock looks for its shape: the rest shape, turned with the head, is turned again by 75 per cent of the angle that best lays it over the lock as it is now, and every point moves towards it with the strength SET, firmer at the root and softer at the tip. So the lock swings as a whole and keeps its waves without tangling. Last, each point goes back to one segment from the one before (the lock never stretches) and the ones in front stay out of an oval on the face.

The hand in the hair

The pointer is brought onto the board and, at every step, its move is given to the points within HAND pixels of it, in full close by and less and less further out: the locks are dragged and seven tenths of that push stay in their speed, so after the hand has passed they go on a little and then come back. Lifted, hair is heavy and slips through the fingers (an upward push counts less than half). A click or a tap pushes the hair away from the point; with a finger resting, the hand follows the finger.

The toss

Follow starts a GSAP timeline on the head’s pose (TOSS degrees to the side, a little hop, the way back) and, at the top of the gesture, gives every lock a turn round its own root, outwards, plus a little lift: a turn is the one push the length constraint does not eat, and the hair fans out and falls back. The eyes close into half moons, the smile opens and stays open while she is followed. The hoop in her ear is a pendulum that feels the lobe’s change of speed.

The gaze, the blink, the sleep

The irises move towards the pointer by GAZE pixels on a spring, the features of the face a little less, and the head leans a few degrees the same way; once there, the spring stops altogether. With the keys she looks at the button that has the focus. A blink comes at intervals of 2.6 to 5.4 seconds, with a GSAP delayedCall. When for twelve frames nothing moves by a hundredth of a pixel, the physics goes to sleep.

Values to change

NameFileDefaultWhat it changes
HANDscript.js[50, 0.9]The hand in her hair: how far it reaches, in pixels, and how much of its move the hair takes (0 to 1).
SETscript.js0.12How firmly the locks keep their waves: low is loose and heavy, high is set with spray.
WEIGHTscript.js220How heavy the hair is: how fast it falls, in pixels a second squared.
SWINGscript.js0.17How long the hair keeps swinging after a push, in seconds.
TOSSscript.js9How far she tosses her head when she is followed, in degrees.
GAZEscript.js2.4How far her eyes turn towards the pointer, in pixels.
WORDSscript.js['Following', 'Sent']The two pills’ words once she is followed, and once the message has gone.

Try them in the Playground

When to use it

For the profile of people who work with their hands and their looks in a booking app: a hair stylist, a barber, a make-up artist, a dance teacher; for a salon’s page, an artist’s card in a marketplace, a team page where everyone has a drawn portrait.

On phones

The card is 340 pixels wide, or the screen minus 32, and the portrait is drawn again at the real size. A finger resting on the portrait combs the hair and she looks at it (the portrait has touch-action: none, so over it the finger does not scroll the page); a tap ruffles it. The pills are 50 pixels tall and the toss is the same.

With reduced motion

The hair stays still in its waves, the eyes look ahead and she does not blink; the card appears without rising. Follow does not toss her head: the wider smile is simply there and the pill changes in a fade; Sent arrives in a fade too. The pills only change colour under the pointer, without swelling.

Pitfalls

  • Pulling each segment towards its rest direction, taken on the head, was fine at rest, but under a strong push the locks bent into steps and tied knots: now the lock looks for its shape as a whole, turned by the angle that best lays it over the lock as it is now.
  • Pulling each segment to bend from the one before as it does at rest, moving only the child, put energy into the system: the hair writhed and never stopped. A force has to act on the whole shape, not one way.
  • The length constraint (each point one segment from the one before) passed all of its correction into the speed, and the tips trembled for ever: now a tenth of it goes.
  • A push straight out and up for the toss was eaten by the length constraint and the hair hardly moved: the right push is a turn round the root, which the constraint keeps.
  • The short locks on the right resting against the face’s oval trembled at the contact and kept the loop awake: now their rest shape stays outside the oval, they are held firmer, and the push out of the oval does not become a bounce.
  • Sleep measured on the speed (position minus previous position) never reached zero for the points resting on the face: now it counts the real move from one frame to the next, and the gaze spring stops altogether once it arrives.
  • The locks behind, rooted on the edge of the skull, showed their ends cut straight above the crown: the roots sit inside the crown and the ribbon starts a sixth wide.
  • Before the script started the card showed for a moment with an empty portrait, then vanished and came in again: the js class keeps it hidden until the start, and if the script never comes a CSS animation shows it after two and a half seconds.

Libraries

GSAP 3.15.0, Bodoni Moda (font variabile) 5.3.0, Mona Sans (font variabile) 5.3.0.

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

Testing

Tested on 7 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