KQ-1847 · SAND/PORTRAIT

Sand Portrait

A picture laid in coloured sand on a canvas: every grain keeps a home and a colour sampled from the picture, a tone of its own and a bead's highlight. Dragging sweeps an eddy along the stroke that drags grains with it and turns them the way the stroke curves, and a flick throws it on; when the stroke stops the sand flows home grain by grain from the bottom up, each on its own spring with the springs.drift constants and a sideways curl, so the picture rebuilds in curling streams. A tap swirls everything into a turning two-armed spiral on springs.glide constants while the colours turn toward the next picture, then the spiral slowly unwinds from the outside in into the new one; Right, Enter and Space swirl forward, Left back, Home and End to the ends.

KQ-1847 · SAND/PORTRAIT

Fernworks Sand Prints

Four prints, poured by hand

Dusk over the dunes: a low sun between rust and ochre ridges

Sweep through the sand and it flows back into the picture. Right, Enter or Space swirls it into the next picture, Left into the previous one; Home and End go to the first and last.

dusk over the dunes · 1 of 4 · sweep it, or tap for the next

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/sand-portrait

Props

items
SandPortraitItem[]
default defaultSandPortraits
The pictures: { src?, alt, caption? }; an item without src pours a drawn print.
value / defaultValue
number
default 0
The picture shown; controlled or initial.
onValueChange
(index: number) => void
default —
Fires from the tap or key that asked for another picture, with its index.
grains
number
default 3200
How many grains make the picture, 600 (a coarse mosaic) to 8000 (fine sand).
sweep
number
default 0.6
How wide and how hard a sweep catches the sand, 0 (a fingertip) to 1 (a palm).
reform
number
default 1
How quickly the picture rebuilds, 0.25 (lingers) to 2.5 (snaps back).
curl
number
default 0.5
How much returning grains curl on their way home, 0 (straight) to 1.
swirl
number
default 1.6
How many turns the spiral's arms wind when the picture changes, 0.5 to 3.
texture
number
default 0.5
Grain-to-grain variation in tone, 0 (flat colour) to 1 (coarse, mixed sand).
hold
number
default 220
How long the sand waits after a stroke stops before it flows back, ms.
tray
boolean
default true
A sand-coloured floor and a shaded rim; off, the grains lie on whatever is behind.
interactive
boolean
default true
Sweeping with the pointer and tapping for the next picture. Keys always work.
paused
boolean
default false
Hold every grain where it is.
seed
number
default 1
Seeds the grain grid and its tones, so two trays never match.
dpr
number
default 2
Cap on the canvas's device pixel ratio, at most 2.
height
number
default 300
Height of the tray, px.
label
string
default "Sand picture"
The tray's accessible name.
onStateChange
(state: "still" | "sweeping" | "settling" | "swirling") => void
default —
Fires when the sand starts or stops moving.
disabled
boolean
default false
No sweeps, taps or keys; the tray dims.
className
string
default —
Classes for the tray.