KQ-1584 · SERPENTINE/POP
Serpentine Pop
Paper streamers for any box: a moving pointer throws tight coils of coloured paper off the cursor, each popping out on the springs.recoil constants and flying on its throw while the strip pays out behind it, so the roll visibly unwinds in the air before the free paper flutters down, springing into ringlets from its curl memory and turning over to show its paler back. A click or tap fires a popper — a ring flashes, a dozen coils leave in an upward cone with a puff of tumbling flecks — and everything settles along the bottom edge in fixed 120 Hz Verlet physics. The box is one tab stop: Enter or Space fires a popper from an aim point on the bottom edge, and Left and Right move the aim.
Fieldline · release 2.0
Fieldline 2.0 is live
Shared boards, offline sync and a calmer inbox.
move to throw streamers · click to pop
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/serpentine-pop
Props
- children
- React.ReactNode
- default —
- The content the paper falls over (or behind, with layer="back"). It stays fully interactive.
- palette
- "party" | "sorbet" | "gilt" | "mono"
- default "party"
- The paper's colours: pigments at a fixed lightness, so a streamer looks the same on any page; mono follows the ink tokens.
- colors
- string[]
- default —
- Your own paper colours, any CSS colours (tokens included); replaces palette.
- rate
- number
- default 5
- How many streamers a moving pointer throws, at most, per second (1 to 12).
- curl
- number
- default 0.6
- How tightly the paper remembers its roll, 0 (straight ribbons) to 1 (tight ringlets).
- stripWidth
- number
- default 6
- Width of the paper strip, px.
- streamerLength
- number
- default 130
- Length of one streamer unrolled, px.
- burst
- number
- default 12
- Streamers a popper fires.
- flecks
- number
- default 36
- Paper flecks a popper puffs out with them.
- gravity
- number
- default 1
- How fast paper falls, 0.5 (floating) to 2 (heavy).
- linger
- number
- default 10
- Seconds settled paper lies on the floor before it fades; 0 keeps it until maxPaper is reached. The resting garland never lingers out.
- maxPaper
- number
- default 40
- Most streamers lying on the floor at once; past it the oldest fade.
- scatter
- number
- default 6
- Curled streamers already lying along the bottom edge at rest (fewer on a narrow box).
- layer
- "front" | "back"
- default "front"
- Draw the paper in front of the content or behind it.
- paused
- boolean
- default false
- Hold the paper where it is and stop throwing.
- seed
- number
- default 7
- Seeds the colours, curls and launch of every streamer.
- dpr
- number
- default 2
- Cap on the canvas's device pixel ratio, at most 2.
- label
- string
- default "Party popper"
- The popper's accessible name.
- onPop
- (count: number) => void
- default —
- Fires on every popper, with how many have been fired so far.
- sound
- boolean
- default false
- A pop and a paper crinkle when a popper fires (never on hover).
- disabled
- boolean
- default false
- No streamers, no poppers; the box is plain content and leaves the tab order.
- className
- string
- default —
- Sizes and shapes the box; it clips the paper (rounded corners included).
| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode | — | The content the paper falls over (or behind, with layer="back"). It stays fully interactive. |
| palette | "party" | "sorbet" | "gilt" | "mono" | "party" | The paper's colours: pigments at a fixed lightness, so a streamer looks the same on any page; mono follows the ink tokens. |
| colors | string[] | — | Your own paper colours, any CSS colours (tokens included); replaces palette. |
| rate | number | 5 | How many streamers a moving pointer throws, at most, per second (1 to 12). |
| curl | number | 0.6 | How tightly the paper remembers its roll, 0 (straight ribbons) to 1 (tight ringlets). |
| stripWidth | number | 6 | Width of the paper strip, px. |
| streamerLength | number | 130 | Length of one streamer unrolled, px. |
| burst | number | 12 | Streamers a popper fires. |
| flecks | number | 36 | Paper flecks a popper puffs out with them. |
| gravity | number | 1 | How fast paper falls, 0.5 (floating) to 2 (heavy). |
| linger | number | 10 | Seconds settled paper lies on the floor before it fades; 0 keeps it until maxPaper is reached. The resting garland never lingers out. |
| maxPaper | number | 40 | Most streamers lying on the floor at once; past it the oldest fade. |
| scatter | number | 6 | Curled streamers already lying along the bottom edge at rest (fewer on a narrow box). |
| layer | "front" | "back" | "front" | Draw the paper in front of the content or behind it. |
| paused | boolean | false | Hold the paper where it is and stop throwing. |
| seed | number | 7 | Seeds the colours, curls and launch of every streamer. |
| dpr | number | 2 | Cap on the canvas's device pixel ratio, at most 2. |
| label | string | "Party popper" | The popper's accessible name. |
| onPop | (count: number) => void | — | Fires on every popper, with how many have been fired so far. |
| sound | boolean | false | A pop and a paper crinkle when a popper fires (never on hover). |
| disabled | boolean | false | No streamers, no poppers; the box is plain content and leaves the tab order. |
| className | string | — | Sizes and shapes the box; it clips the paper (rounded corners included). |