KQ-1793 · POLE/TRAILS
Pole Trails
A long exposure of the northern sky above a dark ridge: while the shutter is open every star traces an arc round the celestial pole at 15° per hour of sky, gathered at `exposure` minutes a second and tinted faintly by its temperature, while skyglow gathers on the horizon and an occasional meteor scores a straight streak that stays in the picture. A click, tap, Enter or Space closes the shutter — a curtain drops on springs.flick, the exposure clears and it lifts away on springs.glide over a fresh sky of points — and dragging moves the pole 1:1 so every arc re-centres live, a throw gliding on with its release velocity. Arrow keys move the pole and Home brings it back.
Fieldline · dark-sky reserve
Tonight from the north ridge
f/2.8 · iso 800 · 14 mm
exposing · tap for a fresh frame, drag to move the pole
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/pole-trails
Props
- exposure
- number
- default 10
- How fast the exposure builds, in minutes of sky per second of real time (1 to 60).
- density
- number
- default 0.5
- How many stars, 0 (a hazy night) to 1 (a dark-sky site).
- horizon
- "ridge" | "hills" | "pines" | "sea"
- default "ridge"
- The ground under the sky: a jagged ridge, rolling hills, a hill with a treeline of pines, or a flat sea horizon.
- maxHours
- number
- default 6
- Hours of sky an exposure gathers before it holds (1 to 12).
- loop
- boolean
- default true
- A finished exposure closes the shutter and starts a fresh one after `hold`.
- hold
- number
- default 5000
- How long a finished exposure is held before it loops, ms.
- meteors
- number
- default 4
- Meteors per minute of real time while the shutter is open; 0 for none.
- pole / defaultPole
- readonly [number, number]
- default [0.64, 0.3]
- The celestial pole as fractions of the frame, [x, y]; controlled or uncontrolled. Home returns to defaultPole.
- onPoleChange
- (pole: [number, number]) => void
- default —
- Fires from the drag release or the key that moved the pole, with where it lands.
- skyColor
- string
- default "oklch(from var(--accent) 0.19 0.055 h)"
- The night sky, any CSS colour; a pigment at fixed lightness so the print reads the same in both themes.
- groundColor
- string
- default "oklch(from var(--accent) 0.085 0.018 h)"
- The ridge silhouette, any CSS colour.
- starColors
- readonly [string, string, string]
- default —
- Star tints [hot, neutral, cool]; every star sits between them by its temperature.
- glow
- number
- default 0.5
- Skyglow along the horizon, 0 to 1; it gathers as the exposure builds.
- interactive
- boolean
- default true
- Drag moves the pole; a click, Enter or Space closes the shutter.
- paused
- boolean
- default false
- Holds the exposure where it is.
- seed
- number
- default 7
- Seeds the stars, the ridge and the meteors.
- dpr
- number
- default 2
- Cap on the canvas's device pixel ratio, at most 2.
- onShutter
- () => void
- default —
- A fresh exposure opened (the shutter closed and reopened).
- onComplete
- (hours: number) => void
- default —
- The exposure reached maxHours, with the hours gathered.
- label
- string
- default "Night sky"
- The sky's name; the button reads "<label>: start a new exposure".
- disabled
- boolean
- default false
- No drag, no shutter: the exposure keeps running and the button is aria-disabled.
- className
- string
- default "h-56 w-full"
- Sizes and shapes the frame.
| Prop | Type | Default | Description |
|---|---|---|---|
| exposure | number | 10 | How fast the exposure builds, in minutes of sky per second of real time (1 to 60). |
| density | number | 0.5 | How many stars, 0 (a hazy night) to 1 (a dark-sky site). |
| horizon | "ridge" | "hills" | "pines" | "sea" | "ridge" | The ground under the sky: a jagged ridge, rolling hills, a hill with a treeline of pines, or a flat sea horizon. |
| maxHours | number | 6 | Hours of sky an exposure gathers before it holds (1 to 12). |
| loop | boolean | true | A finished exposure closes the shutter and starts a fresh one after `hold`. |
| hold | number | 5000 | How long a finished exposure is held before it loops, ms. |
| meteors | number | 4 | Meteors per minute of real time while the shutter is open; 0 for none. |
| pole / defaultPole | readonly [number, number] | [0.64, 0.3] | The celestial pole as fractions of the frame, [x, y]; controlled or uncontrolled. Home returns to defaultPole. |
| onPoleChange | (pole: [number, number]) => void | — | Fires from the drag release or the key that moved the pole, with where it lands. |
| skyColor | string | "oklch(from var(--accent) 0.19 0.055 h)" | The night sky, any CSS colour; a pigment at fixed lightness so the print reads the same in both themes. |
| groundColor | string | "oklch(from var(--accent) 0.085 0.018 h)" | The ridge silhouette, any CSS colour. |
| starColors | readonly [string, string, string] | — | Star tints [hot, neutral, cool]; every star sits between them by its temperature. |
| glow | number | 0.5 | Skyglow along the horizon, 0 to 1; it gathers as the exposure builds. |
| interactive | boolean | true | Drag moves the pole; a click, Enter or Space closes the shutter. |
| paused | boolean | false | Holds the exposure where it is. |
| seed | number | 7 | Seeds the stars, the ridge and the meteors. |
| dpr | number | 2 | Cap on the canvas's device pixel ratio, at most 2. |
| onShutter | () => void | — | A fresh exposure opened (the shutter closed and reopened). |
| onComplete | (hours: number) => void | — | The exposure reached maxHours, with the hours gathered. |
| label | string | "Night sky" | The sky's name; the button reads "<label>: start a new exposure". |
| disabled | boolean | false | No drag, no shutter: the exposure keeps running and the button is aria-disabled. |
| className | string | "h-56 w-full" | Sizes and shapes the frame. |