
The star rating
Ratings, SVG, Hover
NewFree
The Log out of an app as a pill of sky with a small portrait drawn in SVG in its round end: awake, its eyes follow the pointer and it blinks at random moments. At a click the eyelids grow heavy, a yawn, the sky in the pill runs from day through the sunset to night, the sun goes down, the moon comes up with a few drawn stars and the portrait falls asleep with its head on one side, breathing, while small drawn z letters float up: Signed out · Sleep well. Another click wakes it with a start and the sun comes back: Good morning, Ada.
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 goodnight button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the goodnight button: the Log out of an app, a pill of sky with a small drawn portrait in its round left end. The
portrait is awake: its eyes follow the pointer and it blinks now and then. A click logs out: the eyelids grow
heavy, a yawn, the sky inside the pill runs from day to night, the moon comes up and the portrait falls asleep
with its head on one side, breathing. Another click signs in: it wakes with a start and the sun comes back -->
<div class="night" data-night>
<button class="night__btn" type="button" data-btn data-autoclic>
<span class="night__fill" aria-hidden="true" data-fill>
<span class="night__sky" data-sky></span>
<span class="night__dark" data-dark></span>
</span>
<span class="night__clip" aria-hidden="true">
<svg class="night__heavens" viewBox="0 0 40 60" data-heavens>
<defs>
<mask id="nb-crescent">
<circle cx="20" cy="23" r="7.4" fill="#fff" />
<circle cx="23.6" cy="20" r="6.6" fill="#000" />
</mask>
</defs>
<g class="night__stars" data-stars>
<path transform="translate(5 12) scale(0.9)" d="M0 -2.4Q0.35 -0.35 2.4 0Q0.35 0.35 0 2.4Q-0.35 0.35 -2.4 0Q-0.35 -0.35 0 -2.4Z" />
<path transform="translate(34 42) scale(0.7)" d="M0 -2.4Q0.35 -0.35 2.4 0Q0.35 0.35 0 2.4Q-0.35 0.35 -2.4 0Q-0.35 -0.35 0 -2.4Z" />
<path transform="translate(9 46) scale(0.55)" d="M0 -2.4Q0.35 -0.35 2.4 0Q0.35 0.35 0 2.4Q-0.35 0.35 -2.4 0Q-0.35 -0.35 0 -2.4Z" />
<path transform="translate(33 8) scale(0.5)" d="M0 -2.4Q0.35 -0.35 2.4 0Q0.35 0.35 0 2.4Q-0.35 0.35 -2.4 0Q-0.35 -0.35 0 -2.4Z" />
</g>
<g data-sun>
<g class="night__rays" data-rays>
<path d="M20 12.2v-2.6M20 33.8v2.6M9.2 23h-2.6M30.8 23h2.6M12.4 15.4l-1.8-1.8M27.6 30.6l1.8 1.8M12.4 30.6l-1.8 1.8M27.6 15.4l1.8-1.8" />
</g>
<circle class="night__sun" cx="20" cy="23" r="7" />
</g>
<g data-moon>
<rect class="night__moon" x="10" y="13" width="20" height="20" mask="url(#nb-crescent)" />
</g>
</svg>
<svg class="night__zs" viewBox="0 0 14 21" data-zs>
<path d="M0 0h3.4L0 3.8h3.4" />
<path d="M0 0h3.4L0 3.8h3.4" />
<path d="M0 0h3.4L0 3.8h3.4" />
</svg>
</span>
<span class="night__portrait" aria-hidden="true">
<svg viewBox="0 0 50 50" data-portrait>
<defs>
<clipPath id="nb-round"><circle cx="25" cy="25" r="23.6" /></clipPath>
<clipPath id="nb-eye"><path d="M-2.7 0Q0 -2.7 2.7 0Q0 2.1 -2.7 0Z" /></clipPath>
</defs>
<g clip-path="url(#nb-round)">
<circle class="pt-back" cx="25" cy="25" r="24" />
<g data-body>
<path class="pt-sweater" d="M2 52C3 41.5 12.5 37.6 25 37.6S47 41.5 48 52Z" />
<path class="pt-neck" d="M21.3 30.5h7.4v6.6q-3.7 2.8 -7.4 0Z" />
<path class="pt-collar" d="M19.4 38.2Q25 43 30.6 38.2" />
</g>
<g data-head>
<path class="pt-hair" d="M12.2 26C11.2 14.2 17 8.6 25 8.6S38.8 14.2 37.8 26C37.6 31 36.4 33.8 34.6 34.6L15.4 34.6C13.6 33.8 12.4 31 12.2 26Z" />
<ellipse class="pt-skin" cx="15.2" cy="25.4" rx="1.8" ry="2.5" />
<ellipse class="pt-skin" cx="34.8" cy="25.4" rx="1.8" ry="2.5" />
<ellipse class="pt-skin" cx="25" cy="24.6" rx="9.7" ry="11.1" />
<g data-face>
<ellipse class="pt-cheek" cx="18.6" cy="28.6" rx="2" ry="1.2" />
<ellipse class="pt-cheek" cx="31.4" cy="28.6" rx="2" ry="1.2" />
<g class="pt-eye" transform="translate(21 24.6)">
<path class="pt-white" d="M-2.7 0Q0 -2.7 2.7 0Q0 2.1 -2.7 0Z" />
<g clip-path="url(#nb-eye)">
<g data-pupil>
<circle class="pt-iris" r="1.45" />
<circle class="pt-glint" cx="-0.45" cy="-0.5" r="0.42" />
</g>
<rect class="pt-lid" x="-3" y="-2.6" width="6" height="0" data-lid />
</g>
<path class="pt-lash" d="M-2.8 0Q0 -1.2 2.8 0" data-lash />
</g>
<g class="pt-eye" transform="translate(29 24.6)">
<path class="pt-white" d="M-2.7 0Q0 -2.7 2.7 0Q0 2.1 -2.7 0Z" />
<g clip-path="url(#nb-eye)">
<g data-pupil>
<circle class="pt-iris" r="1.45" />
<circle class="pt-glint" cx="-0.45" cy="-0.5" r="0.42" />
</g>
<rect class="pt-lid" x="-3" y="-2.6" width="6" height="0" data-lid />
</g>
<path class="pt-lash" d="M-2.8 0Q0 -1.2 2.8 0" data-lash />
</g>
<path class="pt-brow" d="M18.6 20.6Q21 19.4 23.2 20.4" data-brow />
<path class="pt-brow" d="M26.8 20.4Q29 19.4 31.4 20.6" data-brow />
<path class="pt-nose" d="M25.3 25.8Q26.2 27.6 24.9 28" />
<path class="pt-smile" d="M23 30.6Q25 32 27 30.6" data-smile />
<ellipse class="pt-yawn" cx="25" cy="31.2" rx="0" ry="0" data-yawn />
</g>
<path class="pt-hair" d="M14.8 23C14.6 14.6 19.6 11.4 25.6 11.4C31 11.4 35.4 14.4 35.6 22C32.6 18.4 28.6 16.6 24.2 17.4C20.4 18.2 17.2 20.2 14.8 23Z" data-fringe />
</g>
<rect class="pt-moonlight" x="0" y="0" width="50" height="50" data-moonlight />
</g>
<circle class="pt-ring" cx="25" cy="25" r="23.8" />
</svg>
</span>
<span class="night__label">
<span class="night__word" data-word data-wide="Log out">Log out</span>
</span>
</button>
<p class="sr" aria-live="polite" data-live></p>
</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.
30 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 goodnight button” from Animorio and fit it into my siteThe portrait is a 50 unit SVG clipped to a circle: the sweater and the neck in one group, the head (hair behind, ears, face, features, the fringe in front) in another that tilts round the neck. Each eye is a white almond holding, clipped to the same almond, the pupil with its glint and the lid: a skin coloured rectangle that grows in height. The edge of the lid is a single curve that comes down with it and turns over: arched when open, the curve of sleep when shut.
The face is an object of numbers (the gaze, the lids, the blink, the smile, the yawn, the brows, the tilt, the jolt, the breath, the moonlight) that GSAP animates and one function writes into the SVG: transforms, the height of the lids, the paths of the lid edge, the brows and the mouth rebuilt on each frame. A key made of the numbers rounded to the hundredth says whether anything changed: if not, nothing is written and the loop stops.
The direction from the middle of the portrait to the pointer, full beyond 90 pixels and in proportion closer in, is where the gaze is going; the drawn gaze follows it on a quick spring (4.2 swings a second, damping 0.62), so the eyes dart and settle. The pupils move 1.1 units, the features 0.9, the fringe 0.45 and the head turns 1.2 degrees: the parallax between the layers gives the depth. LOOK scales them all.
A GSAP delayedCall calls itself again at a random moment between the two numbers of BLINK; the blink comes down in 0.07 seconds and goes back up in 0.15, and about one time in five it does it again at once. It is a number apart from the heavy lids, and the more shut of the two counts, so a blink never opens the eyes of someone sleepy.
The sky is a gradient four pills tall (day, peach sunset, mauve, night) inside the fill with overflow: hidden; logging out slides it up by yPercent −75 in DUSK seconds, and signing in brings it back down. The sun goes down 60 units, the moon (a rectangle with a crescent mask: a white circle less a black one moved aside) comes up in its place, the four pointed stars come in with back.out and stay still, the colour of the words goes through a CSS variable animated by GSAP, and a blue veil on the portrait makes the moonlight.
The button is a real button, with aria-disabled while it signs out or in and an aria-live region that says Logging out…, Signed out · Sleep well, Signing in… and Good morning, Ada; it is all simulated with GSAP's timing. Asleep, the breath is an endless yoyo tween and the z letters a repeating timeline of three drawn letters; waking stops them. The pill takes the measured width of the words with back.out, leaving room for the sun on the right, and when it narrows it waits for the old words to be gone.
| Name | File | Default | What it changes |
|---|---|---|---|
LOG_OUT | script.js | Log out | The words at rest, signed in. |
LOGGING_OUT | script.js | Logging out… | The words while it logs out; the aria-live region says them too. |
SLEEP_WELL | script.js | Signed out · Sleep well | The words once signed out, before Sign in. |
SIGN_IN | script.js | Sign in | The words at rest, signed out. |
SIGNING_IN | script.js | Signing in… | The words while it signs in. |
MORNING | script.js | Good morning, Ada | The greeting once signed in. The sign in is simulated: in a real site this is the name your server returns. |
DUSK | script.js | 1.8 | The seconds the sky takes to go from day to night. |
BLINK | script.js | [1.6, 4.8] | In how many seconds, at least and at most, the next blink comes. |
LOOK | script.js | 1 | How far the eyes and the face turn to the pointer: 0 they stay still. |
TILT | script.js | 13 | How far the head falls on one side asleep, in degrees. |
BREATH | script.js | 3.4 | The seconds of one breath asleep. |
For the sign out of an evening or quiet app: sleep and meditation, reading, music, a journal, an app for children or families, or simply a product with a gentle voice. The portrait is meant to be replaced by the user's avatar or the product's mascot; the real sign out has to be wired to your server: here it is simulated.
The pill is between about 190 and 300 pixels wide and always fits at 390; if the words are too long the type gets smaller. The eyes follow a finger resting on the page, a tap signs out or in; the portrait, the sun and the moon keep their size.
The eyes do not follow the pointer or blink, no yawn, no breath and no z letters, the pill does not swell and the words do not widen. At a click the portrait fades out and back in already asleep, the night fades in over the day, the sun fades out and the moon and the stars fade in; another click brings back the day and the waking portrait the same way.
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 11 KB; the libraries: GSAP 28 KB.
Tested on 12 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.