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