Star 历史趋势
数据来源: GitHub API · 生成自 Stargazers.cn
README.md

hairline

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

npm CI license

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

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:

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

Use

React

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

export function Hero() {
  return <Terrain />;
}

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 <div> attribute, and forwards its ref to the <div>.

Anything else

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

const figure = terrain(document.getElementById("figure")!);

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

FunctionComponentWhat it isA stronger intensity
riffleRiffleA 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.
terrainTerrainEighty-one pillars on a plinth that rise around the pointer.A wider area rises.
explodedExplodedAn app window in four layers. Moving across opens the gap; moving down picks a layer.The layers open further.
phosphorPhosphorA dot matrix that plays a loop, and fades like phosphor where the pointer paints it.The trail lingers longer.
slowSlowCrates riding a belt through a gate. Hovering slows the clock without stopping it.Time slows down more.
turntableTurntableBlocks on a turntable. A flick spins it; it settles on the nearest quarter turn.The spin coasts longer.
keyboardKeyboardA sixty-key board. The key under the pointer sinks, and its neighbours follow it down.A wider patch of keys sinks.
elevatorElevatorFour floors beside an open shaft. The pointer's height picks a floor; the car travels there.The car travels faster between floors.
phonePhoneA phone in layers: glass, board, battery, shell. Moving across opens the gap; moving down picks a layer.The layers open further.
laptopLaptopA thin laptop, open on its hinge. The pointer's height sets the lid; it follows on a spring.The lid opens wider.
terminalTerminalA terminal window with its history in rows. The pointer's height scrolls back; the line under it lifts and its neighbours follow.The lift spreads further.
cabinetCabinetA rack of twelve blades, a few half out. The pointer's height pulls the nearest ones out, the farther the less.More blades come out.
branchesBranchesA commit graph with a branch forking off main and merging back. The commit under the pointer rises, and its history rises after it.More of the history rises.
vaultVaultA vault door with a dial and three bolts. The pointer turns the dial; detents catch every ten, and on the combination the bolts draw back.The dial coasts longer.
lockersLockersA bank of twelve lockers, one ajar at rest. The locker under the pointer opens; the one at rest closes.The door opens wider.
padlockPadlockA padlock with its shackle in. As the pointer comes near the shackle lifts out and swings open.The shackle swings further.
patchPatchA patch panel of twenty-four ports with cables. The cable under the pointer lifts and its neighbours lean away.The lean spreads further.
dishDishA parabolic dish on a two-axis gimbal. The pointer aims the dish; it follows on a spring.The dish swings further.
routerRouterA router with its antennas up. Each antenna leans toward the pointer, the nearest most.The lean spreads further.
loupeLoupeA stand loupe on a blank ruled sheet. The pointer drags it across; the rules pass enlarged under the glass, with nothing between them.The glass magnifies more.
sieveSieveThree test sieves stacked over a pan. The pointer's height picks one; it rises clear of the stack, and every mesh is bare.The gap opens further.
railRailA garment rail with seven bare hangers. The pointer brushes them; each rocks away, the nearest most, and settles.The brush reaches more hangers.
plugPlugA wall socket, and a plug lying on the floor at the end of its cord. The pointer draws the plug up toward the socket; it stops short, and falls back.The plug comes closer to the socket.
queryQueryA question mark built as a bent bar over a loose ball. The hook turns toward the pointer, and the ball rolls after it.The hook turns further.
drawerDrawerA cabinet of three drawers. The pointer's height picks one; it slides out and shows two dividers with nothing between them.The drawer opens further.
basketBasketA wire basket under a bail handle. It tilts toward the pointer and shows its bare floor; the handle swings after it.The basket tilts further.
plotPlotA bar chart with seven flat tabs where the bars would stand. The pointer brushes them; each lifts a little and drops back to zero.The tabs lift higher.

Options

Every figure takes the same four, all optional:

OptionTypeDefault
intensitynumber0.5How strongly the figure answers the pointer, from 0 (subtle) to 1 (strong). A number outside 0…1 is clamped; anything that is not a number is 0.5.
theme"auto" | "light" | "dark""auto""auto" follows the page: an ancestor with class dark or data-theme="dark", then the page's color-scheme.
labelstringa description in EnglishThe accessible name. In React, aria-label does the same.
onRead(text: string) => voidThe figure's caption, each time it changes: "03", "gap 28.0", "rate 0.20×".

In update, a key set to undefined goes back to its default, and a key left out stays as it is.

Theme

Six custom properties, set on the figure or on anything above it:

.figures {
  --hairline-plate: #101014; /* the fill of every plate: the colour the figure sits on */
  --hairline-hi: #fafafa;    /* what is lit */
  --hairline-edge: #a1a1aa;  /* silhouettes */
  --hairline-mid: #52525b;   /* every other stroke */
  --hairline-lo: #27272a;    /* what recedes */
  --hairline-stroke: 0.9;    /* stroke width, in CSS pixels at any size */
}

--hairline-plate is the one to get right. Plates are filled, not transparent, because a plate hides what is drawn behind it; on a background that is neither white nor #08090a, set it to that background.

The figure's styles have no specificity, so any rule of yours wins without !important.

Notes

  • Accessibility. A figure is an image with a description you can replace with label. Riffle is the exception: it is a focusable group, the arrow keys walk its cards, and a live region reads the card out.
  • Reduced motion. With prefers-reduced-motion, the figures that play on their own (Phosphor and Slow) hold still, and every figure still answers the pointer.
  • Performance. Every figure on a page shares one requestAnimationFrame loop. A figure off screen, or at rest, does no work, and the loop stops when nothing is moving.
  • Server rendering. On the server a component is an empty box with a 5:4 aspect ratio, so nothing shifts when it draws. The functions need a DOM: call them in an effect, in onMount, or in a script after the element.
  • Shadow DOM. A figure mounted inside a shadow root styles itself there.

Make your own

hairline-create is a skill for coding agents. Give it an idea and it draws a new figure to Hairline's ten rules, on the same engine as the twenty-seven above, as one HTML file.

npx skills add lucasmarkes/hairline

Then type the command with an idea in the agent:

/hairline-create a sales funnel

It offers two or three concepts, draws the one you pick, checks it against the rules and in a browser, and hands over hairline-<name>.html: one file with no dependencies that opens from disk. It runs on any agent that reads skills: Claude Code, Cursor, Codex and others.

Four figures it drew, each beside its prompt: hairline.lucasmarkes.com/skill.

More

License

MIT © Lucas Marques

关于 About

Six isometric line figures that answer the pointer. For React and for anything with a DOM.
animationisometricreactshadcnsvgtypescript

语言 Languages

TypeScript57.8%
HTML26.0%
JavaScript11.9%
CSS4.2%

提交活跃度 Commit Activity

代码提交热力图
过去 52 周的开发活跃度
157
Total Commits
峰值: 157次/周
Less
More

核心贡献者 Contributors