KQ-1785 · BISTABLE/SEA

Bistable Sea

A slim e-paper display showing an open-sea swell under a low sun, rendered at e-paper resolution as a real perspective ocean (four wave trains, sky Fresnel, a sun-glitter path, foam on steep crests, cloud bands) and error-diffused to pure ink and paper. It refreshes like e-paper rather than video: each step of the sea rolls down the panel in bands, changed pixels flipping through their inverse before they land and keeping a faint ghost of what they were, and every few partials a full refresh flashes inverse, blanks and lands clean. The pointer tilts the device toward it on the glide spring with a sheen across the glass, a horizontal drag turns the bearing 1:1, and the device is a slider whose arrow, Page and Home keys turn the sea, each turn kicking it into a small tilt that settles on glide.

KQ-1785 · BISTABLE/SEA
SSW 200°Swell 1.9 m · 11 s

Left and Right turn the display 15 degrees, Shift or Page keys 45, Home faces north. Drag sideways to turn it by hand. Facing SSW.

Basinworks Tide

Harbour Display, 6 in

The swell off the breakwater on paper that holds its picture without power. A month on a charge.

partial refresh · facing SSW 200° · drag or use the arrow keys

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/bistable-sea

Props

refresh
number
default 1
Partial refreshes a second, 0.25 to 3: how often the sea steps on.
ghosting
number
default 0.5
How much of each old frame a refresh leaves behind, 0 (clean) to 1 (heavy ghosts).
swell
number
default 0.55
How big the swell runs, 0 (glassy) to 1 (heavy).
value / defaultValue
number
default 200
The bearing the display faces, in degrees (0 north, 90 east); controlled or initial.
onValueChange
(bearing: number) => void
default —
Fires from the drag or key that turned the display, with the new bearing.
onRefresh
(kind: "partial" | "full") => void
default —
Fires as each refresh lands on the panel.
fullEvery
number
default 6
Partial refreshes between full ones, which flash and clear every ghost; 0 never.
bands
number
default 8
How many bands a partial refresh rolls down the panel in, 3 to 16.
dither
"floyd" | "atkinson"
default "floyd"
The error-diffusion kernel: smooth Floyd–Steinberg or crisp, lighter Atkinson.
dot
number
default 2
CSS px per e-paper pixel, 1 to 4.
chop
number
default 0.35
Wind chop on top of the swell, 0 to 1.
tilt
number
default 0.6
How far the device tilts toward the pointer, 0 to 1.
caption
boolean
default true
Show the readout strip at the top of the paper.
paperColor
string
default a warm light grey
The paper's colour, any CSS colour.
inkColor
string
default a soft black
The ink's colour, any CSS colour.
label
string
default "Swell display"
The display's accessible name.
interactive
boolean
default true
The pointer tilts and turns the display. Keys still turn it.
paused
boolean
default false
Stop refreshing: the panel keeps its last frame.
seed
number
default 4
Seeds the sea's phases and the clouds.
disabled
boolean
default false
The display stops answering; it stays focusable and says so.
className
string
default "w-72"
Sizes the device; its height follows from a 3:2 aspect.