KQ-1566 · SHUTTER/SPILL

Shutter Spill

A camera on a light table fires a burst of contact prints at `cadence` frames a second: each shot blinks the lens, throws a flash across the table, and sends a print out white, developing as it travels a loose spiral on springs.glide to a seeded resting place on a golden-angle spiral, where its angle lands on springs.snap. A click lifts a print into the middle on springs.glide while the others shuffle aside in a cascade, a double click gathers the set back into the lens, and a print slides 1:1 under the finger and coasts by projection. The prints are one roving tab stop: arrows step through them (carrying a held print along), Enter picks up, Escape puts down, Shift with an arrow slides one, and the camera button fires or gathers.

KQ-1566 · SHUTTER/SPILL

Fieldline darkroom · roll 12

Morning on the coast

The camera fires the frames onto the table or gathers them back. Arrow keys step through the frames, Enter picks one up and Escape puts it down; Shift with an arrow slides a frame across the table.

loaded · 8 frames, press the camera

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/shutter-spill

Props

items
{ src?: string; alt: string; caption?: string }[]
default eight drawn prints
The pictures, one print each, up to 24; prints without a src are drawn in code.
cadence
number
default 8
The burst's rhythm in frames per second, 3 to 15.
spread
number
default 0.85
How far across the table the spiral reaches, 0.3 to 1.
flash
number
default 0.6
Strength of each shot's flash, 0 (none) to 1.
swirl
number
default 0.3
How far each print curls round on its way out, in turns, 0 to 1.
tilt
number
default 7
The largest angle a print comes to rest at, degrees.
value / defaultValue
number | null
default null
The picked-up print, controlled or initial.
onValueChange
(index: number | null) => void
default —
Fires from the click or key that picked a print up or put it down.
spilled / defaultSpilled
boolean
default false
Whether the prints are out on the table, controlled or initial.
onSpilledChange
(spilled: boolean) => void
default —
Fires from the trigger, camera, double click or key that fired or gathered the set.
trigger
"inView" | "mount" | "manual"
default "inView"
What fires the first burst: coming into view, mounting, or only the camera.
light
string
default "oklch(from var(--warn) 0.94 0.04 h)"
The table's light, any CSS colour; it is mixed into the card colour.
frameSize
number
default fitted
A print's width on the table, px.
numbered
boolean
default true
Print frame numbers in the margins.
seed
number
default 1
Seeds the spiral's jitter and the resting angles.
sound
boolean
default false
A shutter click for each frame of a burst a visitor fired.
label
string
default "Contact prints"
The table's accessible name.
disabled
boolean
default false
No firing, picking or sliding.
className
string
default —
Sizes the table; it fills its parent by default (h-full w-full).