KQ-1531 · THREAD/HOURGLASS
Thread Hourglass
A string sculpture in Canvas 2D: two glowing hoops, one above the other, strung with straight threads and seen from a little above with real perspective. Twist the top hoop and every thread leans the same way, so together they draw a hyperboloid — a curve made of nothing but straight lines — whose waist narrows into a band of light where the threads gather. A sideways drag twists it 1:1 with the hoop's front bead under the finger and rubber-bands past the limit; release and it unwinds on springs.snap from the release's speed, overshooting straight once. It is a slider: arrow keys twist it a step at a time on the same spring and Escape, Enter or Space let go.
Drag sideways, or use the arrow keys, to twist the top hoop; Escape or Enter lets it go.
Waylight · breathing room
Twist in, let go out
at rest · 48 threads, twist up to 150°
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/thread-hourglass
Props
- threadCount
- number
- default 48
- How many threads string the two hoops, 8 to 120.
- twist
- number
- default 150
- How far the top hoop can be twisted either way, in degrees, 30 to 178; near 180 the waist closes to a point.
- glow
- number
- default 0.6
- The threads' bloom and the light at the waist, 0 (fine lines only) to 1.
- color
- string
- default "var(--accent-bright)"
- The threads' colour.
- hoopColor
- string
- default —
- The hoops' colour; by default the thread colour mixed toward the foreground.
- tilt
- number
- default 20
- How far we look down on the hoops, in degrees, 0 (side on) to 40.
- spin
- number
- default 6
- The sculpture's slow turn about its axis, degrees per second, 0 to 40.
- step
- number
- default 15
- Degrees per arrow key.
- bounce
- number
- default 0.35
- How far the hoop overshoots straight as it unwinds, 0 (none) to 1; the default is the house snap spring.
- springBack
- boolean
- default true
- Let go and the hoop unwinds to straight; off, it stays where it is left.
- paused
- boolean
- default false
- Hold the slow turn; twisting still works.
- disabled
- boolean
- default false
- No twisting by hand or key.
- dpr
- number
- default 2
- Cap on the canvas's device pixel ratio, at most 2.
- label
- string
- default "Twist the top hoop"
- The accessible name of the twist control.
- onTwistChange
- (degrees: number) => void
- default —
- Fires as a hand or key sets the twist, in whole degrees (positive to the right), and with the resting twist on release.
- onHoldChange
- (holding: boolean) => void
- default —
- Fires when a drag or the keyboard takes hold of the top hoop (true) and lets go (false).
- className
- string
- default —
- Sizes the box; it fills its parent by default (h-full w-full).
| Prop | Type | Default | Description |
|---|---|---|---|
| threadCount | number | 48 | How many threads string the two hoops, 8 to 120. |
| twist | number | 150 | How far the top hoop can be twisted either way, in degrees, 30 to 178; near 180 the waist closes to a point. |
| glow | number | 0.6 | The threads' bloom and the light at the waist, 0 (fine lines only) to 1. |
| color | string | "var(--accent-bright)" | The threads' colour. |
| hoopColor | string | — | The hoops' colour; by default the thread colour mixed toward the foreground. |
| tilt | number | 20 | How far we look down on the hoops, in degrees, 0 (side on) to 40. |
| spin | number | 6 | The sculpture's slow turn about its axis, degrees per second, 0 to 40. |
| step | number | 15 | Degrees per arrow key. |
| bounce | number | 0.35 | How far the hoop overshoots straight as it unwinds, 0 (none) to 1; the default is the house snap spring. |
| springBack | boolean | true | Let go and the hoop unwinds to straight; off, it stays where it is left. |
| paused | boolean | false | Hold the slow turn; twisting still works. |
| disabled | boolean | false | No twisting by hand or key. |
| dpr | number | 2 | Cap on the canvas's device pixel ratio, at most 2. |
| label | string | "Twist the top hoop" | The accessible name of the twist control. |
| onTwistChange | (degrees: number) => void | — | Fires as a hand or key sets the twist, in whole degrees (positive to the right), and with the resting twist on release. |
| onHoldChange | (holding: boolean) => void | — | Fires when a drag or the keyboard takes hold of the top hoop (true) and lets go (false). |
| className | string | — | Sizes the box; it fills its parent by default (h-full w-full). |