KQ-1812 · ICE/CUBE

Ice Cube

A clear ice cube slides across a picture after the pointer on a per-frame glide spring, turning with the curve of its slide, and bends the picture per pixel in one WebGL2 pass: bevelled facets displace it, the top magnifies it through frosted cracks and a cloudy core, a cold highlight runs along the top edge and a pale shadow holds a bright caustic. Sliding melts it — it shrinks, rounds and softens and lays meltwater into a ping-pong field that bends the picture too before it dries — and a click drops a fresh cube that lands on springs.recoil with a splash. Keyboard focus on anything inside parks the cube on it, and a drop button that appears on focus takes Enter or Space.

KQ-1812 · ICE/CUBE

Coldbrook · Iced

Cold brew, poured slow.

cube 1 · slide it, click to drop another

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/ice-cube

Props

children
React.ReactNode
default —
Content laid over the picture: headlines, buttons, links. It stays fully interactive.
image
{ src?: string; alt?: string }
default —
The picture under the cube, drawn cover-fit. Without a src, a cloth is drawn from the theme.
pattern
"gingham" | "tiles" | "linen"
default "gingham"
The drawn cloth when there is no src.
melt
number
default 0.4
How fast it melts as it slides, 0 (never) to 1 (a few seconds of sliding).
facets
number
default 0.7
Facet sharpness, 0 (soft, rounded sides) to 1 (flat facets with hard folds).
trail
number
default 0.5
How much meltwater it leaves and how long the trail takes to dry, 0 to 1.
size
number
default 68
The fresh cube's edge, px; it never exceeds a third of the box's height.
refraction
number
default 0.6
How hard the ice and the water bend the picture, 0 to 1.
frost
number
default 0.5
Frosted cracks and the cloudy core inside the ice, 0 to 1.
tint
string
default "oklch(from var(--accent-bright) 0.9 0.05 calc(h - 40))"
The colour of the ice, any CSS colour.
rest
{ x: number; y: number }
default { x: 0.74, y: 0.55 }
Where the cube rests before anyone moves it, as fractions of the box.
interactive
boolean
default true
The pointer moves the cube and a click drops a fresh one.
paused
boolean
default false
Holds the cube and the water where they are.
dpr
number
default 2
Cap on the canvas's device pixel ratio, at most 2.
sound
boolean
default false
A clack when a fresh cube lands or the cube hits the frame. Off unless asked for.
onDrop
(count: number) => void
default —
Fires when a fresh cube is dropped, with how many cubes there have been.
onMelt
(left: number) => void
default —
Fires as the cube melts, with how much of it is left (1 to 0.2) in 5% steps.
onMelted
() => void
default —
Fires when the cube has melted into a puddle.
dropLabel
string
default "Drop a fresh cube"
The keyboard's drop button, shown on focus.
disabled
boolean
default false
Nothing moves the cube and the drop button is off.
className
string
default "h-56 w-full"
Sizes the box.