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