lucasmarkes /hairline

Isometric line figures that answer the pointer. For React and for anything with a DOM.

★ 3,572⑂ 226
TypeScriptCreated 2026-10-01hairline.lucasmarkes.com
3,572
stars
+30
226
forks
–
watchers
–
prs
–
issues
–
releases
–
discussions

README

hairline

Thirty-three isometric line figures that answer the pointer. For React and for anything with a DOM.





!The six figures: a tray of cards, a field of pillars, a window in layers, a dot matrix, a conveyor belt and a turntable

Live, with a slider for intensity: hairline.lucasmarkes.com

 Install

sh
npm i @lucasmarkes/hairline


No dependencies. ESM only. React 18 or later is an optional peer, needed only by @lucasmarkes/hairline/react.

With shadcn, which adds the package and a wrapper that reads your theme's tokens:

sh
npx shadcn@latest add https://hairline.lucasmarkes.com/r/hairline.json


 Use

 React

tsx
import { Terrain } from "@lucasmarkes/hairline/react";

export function Hero() {
  return ;
}


The figure fills its parent's width at a 5:4 aspect ratio. The entry is a client module, so a Server Component can render it with no "use client" of its own. Every component takes the options below and every  attribute, and forwards its ref to the .

 Anything else

ts
import { terrain } from "@lucasmarkes/hairline";

const figure = terrain(document.getElementById("figure")!, { play: true });

figure.update({ intensity: 0.8 });
figure.destroy();


A figure draws into the element you give it, at the element's width and a 5:4 aspect ratio. update changes options on the running figure; destroy removes what the figure added. That is the shape of a Svelte action, so use:terrain={{ intensity }} works as it is.

 The figures

| Function | Component | What it is | A stronger intensity |
| --- | --- | --- | --- |
| riffle | Riffle | A tray of eight cards. The card under the pointer stands up; the arrow keys walk the cards. | The ripple spreads further from the pulled card. |
| terrain | Terrain | Eighty-one pillars on a plinth that rise around the pointer. | A wider area rises. |
| exploded | Exploded | An app window in four layers. Moving across opens the gap; moving down picks a layer. | The layers open further. |
| phosphor | Phosphor | A dot matrix that plays a loop, and fades like phosphor where the pointer paints it. | The trail lingers longer. |
| slow | Slow | Crates riding a belt through a gate. Hovering slows the clock without stopping it. | Time slows down more. |
| turntable | Turntable | Blocks on a turntable. A flick spins it; it settles on the nearest quarter turn. | The spin coasts longer. |
| keyboard | Keyboard | A sixty-key board. The key under the pointer sinks, and its neighbours follow it down. | A wider patch of keys sinks. |
| elevator | Elevator | Four floors beside an open shaft. The pointer's height picks a floor; the car travels there. | The car travels faster between floors. |
| phone | Phone | A phone in layers: glass, board, battery, shell. Moving across opens the gap; moving down picks a layer. | The layers open further. |
| laptop | Laptop | A thin laptop, open on its hinge. The pointer's height sets the lid; it follows on a spring. | The lid opens wider. |
| terminal | Terminal | A terminal window with its hist

Snapshot: 2026-11-10T09:35:25Z · Data license Apache-2.0 · JSON