Skip to content
animorio
IT
All components

The desk lamp card

Cards, Physics, Hover, SVGGSAP, CSS onlyAdvancedCompressed weight: Demo 8.6 KBGSAP 28 KBFreeDownload the zip

The card of an architect’s lamp in a small design shop: the lamp stands on a desk at night and points its light where you are. The shade aims at the pointer (or the finger, or the arrow keys) and the two arms bend to bring it there, worked out like a real arm with two bones and the elbow always on top; each joint reaches its angle on a spring of its own, so the lamp has weight and comes to rest with a small nod. The cone of light and the pool on the desk follow it; a click switches it off and on again with a little catch of the filament, and choosing another finish repaints the lamp.

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 desk lamp card</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/jost@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the card of an architect's desk lamp in a small design shop. The lamp stands on a dark desk and points its
         light where you are: its two arms bend at the elbow, worked out like a real arm, and swing there on springs,
         the shade turns to face the pointer, and the cone of light and the pool on the desk follow. A click (or the
         switch) turns it off and on; choosing another finish repaints it. The lamp and its light are drawn by the
         script at every frame -->
    <article class="lamp" aria-labelledby="lamp-name" data-lamp>
      <div class="desk" tabindex="0" role="group" aria-label="The lamp on the desk. Aim it with the arrow keys; Enter switches it" data-desk>
        <svg class="desk__svg" viewBox="0 0 300 214" preserveAspectRatio="xMidYMid meet" aria-hidden="true" data-svg>
          <defs>
            <linearGradient id="lamp-beam" gradientUnits="userSpaceOnUse" data-beam>
              <stop offset="0" class="glow" stop-opacity="0.7" />
              <stop offset="1" class="glow" stop-opacity="0" />
            </linearGradient>
            <radialGradient id="lamp-pool">
              <stop offset="0" class="glow" stop-opacity="0.9" />
              <stop offset="1" class="glow" stop-opacity="0" />
            </radialGradient>
          </defs>
          <g class="light" data-light>
            <path class="light__beam" fill="url(#lamp-beam)" data-cone />
            <ellipse class="light__pool" fill="url(#lamp-pool)" data-pool />
          </g>
          <path class="desk__top" d="M0 196.5H300" />
          <g class="body" data-body>
            <path class="body__base" d="M40 196c0-7 6-10 24-10s24 3 24 10z" />
            <path class="body__post" d="M64 186v-8" />
            <path class="body__spring" data-spring1 />
            <path class="body__spring" data-spring2 />
            <path class="body__arm" data-arm1 />
            <path class="body__arm" data-arm2 />
            <circle class="body__joint" r="4.2" data-j0 />
            <circle class="body__joint" r="4.2" data-j1 />
            <g data-head>
              <path class="body__mouth" d="M27 -15.5 L27 15.5 L29.5 15.5 L29.5 -15.5 Z" data-mouth />
              <path class="body__shade" d="M-5 -5.5 L7 -6.5 L29 -17 L29 17 L7 6.5 L-5 5.5 Z" />
              <circle class="body__joint" r="4.2" />
            </g>
          </g>
        </svg>
        <button class="desk__switch" type="button" aria-pressed="true" data-switch>
          <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 1.8v5.4M4.6 3.9a5.2 5.2 0 1 0 6.8 0" /></svg>
          <span data-switch-word>On</span>
        </button>
      </div>

      <div class="info">
        <div class="info__head">
          <h2 class="info__name" id="lamp-name">Arca desk lamp</h2>
          <p class="info__price">&euro;240</p>
        </div>
        <p class="info__kind">Steel and cast iron &middot; <span data-finish-name>Graphite</span></p>
        <div class="finishes" role="radiogroup" aria-label="Finish" data-finishes></div>
      </div>
      <p class="sr" aria-live="polite" data-live></p>
    </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.

27 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 desk lamp card” from Animorio and fit it into my site

What it does

On a white background a white card 340 pixels wide, with a grey hairline and a soft shadow. At the top a desk at night, almost black, the edge of the top as a thin line, and on it an architect’s lamp drawn in pale strokes: the cast iron base, two thin arms with the coil springs beside them, three round joints and the conical shade. When the page opens the lamp unfolds from its folded pose, switches on with a little catch and points at the desk: an amber cone of light comes out of the shade and fades, and an oval pool lights up on the top. When the pointer enters the desk the shade follows it: the arms bend at the elbow to bring the head to a set distance from it, sway a little on the springs and stop, the shade turns quicker than the arms and the cone and the pool follow; leaving the desk the lamp goes back to looking at the top. At the top right a pill switch says On in amber (Off in grey): a click there, or on the desk, turns it off and on again, and the shade gives a small nod. Below, the name Arca desk lamp in Jost, the price, Steel and cast iron · Chalk and three little squares of paint, Chalk, Sage and Brick, with a ring that slides to the chosen one while the lamp changes colour.

How it works

Where the head has to be

The lamp points where you are: the head stands REACH units from the aimed point, on the line back to the shoulder, above the desk top and inside the drawing. The aimed point chases the pointer with a small lag, so the lamp never snaps.

An arm with two bones

Shoulder, elbow and head are a two-bone arm (ARMS): the distance from shoulder to head, capped to what the arms can cover, gives the elbow’s angle through the law of cosines. Of the two solutions the one with the elbow on top is taken, as on a real lamp, unless that elbow would end up outside the drawing.

A spring for every joint

The two arms and the shade each have their own angle spring (SPRING for the arms, HEAD for the shade, quicker), integrated on the GSAP ticker in four sub-steps with the step capped at 1/30 of a second. The angles are unwrapped towards the target, so a joint never goes the long way round; when everything is still the ticker lets go.

The light

From the mouth of the shade two rays open by BEAM degrees: where they meet the top they stop, otherwise they go far, and between them is the cone, filled with a linear gradient along the axis that fades to nothing. Where the axis meets the desk there is the pool, an ellipse with a radial gradient as wide as the cone there. All the light is in mix-blend-mode screen over the dark ground.

On and off

Switching on, the light rises to 55%, drops back to 40% and then rises fully, like a filament warming up; switching off it fades in a fifth of a second. Either way the shade gets a small push and nods. The finish changes the --lamp variable with a colour tween.

Keyboard and touch

The desk takes the focus: the arrows move the aimed point by 14 units (40 with Shift), Enter and Space switch it on and off, Escape sends it back to looking at the top. The switch is a button with aria-pressed and the finishes a radiogroup. On a phone a finger tapping or dragging on the desk aims the lamp (touch-action: none) and the switch turns it on.

Values to change

NameFileDefaultWhat it changes
FINISHESscript.js[{ name: 'Chalk', color: '#ebe7e0' }, …]The finishes on sale: the name and the colour of the paint. Five at most; light or bright colours work best, as the desk is dark.
ARMSscript.js[92, 86]The length of the two arms, in the drawing’s units (the desk is 300 wide).
REACHscript.js74How far the shade stays from the point it aims at.
SPRINGscript.js[70, 0.55]The arms’ spring: how stiff it is and how much it is held back (1 = no bounce).
HEADscript.js[150, 0.6]The shade’s spring.
BEAMscript.js21Half the opening of the cone of light, in degrees.

Try them in the Playground

When to use it

For the card of a lamp, a spotlight or a piece of furniture in a design shop; also for a smart home app, a reading lamp in a catalogue or the page of a studio that designs objects. It works wherever the product should answer the hand as it would in a shop.

On phones

The card is 340 pixels wide, or the screen minus 32, and the drawing fits through its viewBox. A finger touching the desk moves the aim and dragging it the lamp follows (over the desk the finger does not scroll the page); the switch, 34 pixels tall, turns it off and on. Springs and light are the same as on a computer.

With reduced motion

The lamp does not chase the pointer along the way: when the aim changes the drawing dims for a moment and comes back with the lamp already turned. No entrance, no catch: the light fades on and off, and the finishes change colour in a moment.

Pitfalls

  • Aiming up and to the left, near the base, the elbow «on top» left the drawing on the left edge: the other solution is taken when the top one would end up outside, and the head always stays inside the drawing.
  • The springs on the angles, when an angle crossed from +180 to −180 degrees, made the joint spin a full turn backwards: each target is unwrapped towards the current angle before the spring gets it.
  • The light’s colour written as var() in the gradients’ stop-color attributes does not work: presentation attributes do not read CSS variables, so stop-color sits in a CSS rule.
  • The cone, fading to nothing exactly where the axis meets the top, ended with a sharp cut where the edge rays went further: the gradient reaches 125% of that distance.

Libraries

GSAP 3.15.0, Jost (font variabile) 5.3.0.

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

Testing

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