KQ-1798 · TORUS/PUZZLE

Torus Puzzle

A square of glowing dots holding a simple picture whose rows and columns slide one cell at a time on springs.snap and wrap round the edges like a torus — the dot leaving one side is drawn again entering the other — while the moving line's track lifts behind it and its dots glow with its speed. On its own it scrambles itself and then solves back in a calm, deliberate sequence, the inverse of every step; drag any row or column and it follows 1:1, wraps as far as you pull and snaps to a whole step with the release velocity, and after a quiet spell it solves from wherever you left it. Solved, the lit dots swell and land on springs.recoil in a cascade from the centre. It is a real grid: arrow keys move between dots, wrapping at the edges, and Shift with an arrow slides the row or column.

KQ-1798 · TORUS/PUZZLE

Coldbrook · lobby

The sign that solves itself

Arrow keys move between dots. Shift with an arrow slides the row or column.

solved · drag a row or a column

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/torus-puzzle

Props

grid
number
default 6
Dots per side (3 to 9).
pace
number
default 600
Milliseconds per autoplay step while solving; scrambling runs at 0.7 of it (200 to 1500).
glow
number
default 0.6
Halo of the lit dots and their boost while moving, 0 (none) to 1.
pattern
"ring" | "plus" | "diamond" | "arrow" | readonly boolean[]
default "ring"
The picture the lit dots make at home, or your own grid × grid cells, row by row.
scramble
number
default 7
Steps in each scramble (1 to 30).
autoplay
boolean
default true
Scrambles and solves itself while on screen.
resumeAfter
number
default 5000
Quiet time after your last move before autoplay solves from where you left it, ms.
colors
{ lit?: string; dim?: string; track?: string }
default —
Colours, as CSS colours: lit dots, unlit dots and home rings, and the track behind a moving line.
onMove
(move: { axis: "row" | "column"; index: number; by: number }) => void
default —
Fires for every committed move — yours, a button's or autoplay's — with the shortest equivalent shift.
onSolvedChange
(solved: boolean) => void
default —
Whether the picture is home, reported from the start and on every change.
onDistanceChange
(steps: number) => void
default —
Steps from home, reported from the start and on every change.
sound
boolean
default false
Ticks as dots cross cells, for the visitor's own moves only.
disabled
boolean
default false
No drag and no keys; autoplay still runs.
label
string
default "Torus puzzle"
The grid's name; it reads "<label>, solved" or "<label>, N steps from home".
className
string
default "w-52"
Sizes the square board.
ref
React.Ref<{ solve: () => void; scramble: () => void }>
default —
Imperative solve() and scramble(), played one step at a time; autoplay waits resumeAfter before taking over again.