KQ-1823 · TEXT/BOTTLE
Text Bottle
A bottle typed on a fixed grid of monospace cells: its glass is one ring of edge glyphs that re-letters itself as it turns, and its water is a density ramp over a shallow-water simulation that conserves volume, so tipping it makes the water lag, run up the glass and slosh back. Dragging turns it 1:1 and a release projects the throw and lands on springs.glide with the release velocity; arrow keys tip it on springs.snap, whose one overshoot slops the water. Past the lip the water pours out of the neck in gulps as a ballistic stream of glyphs into a puddle, and stood upright it refills from a dripping tap.
Basinworks Spring Water · Bottle 03
Still water, typed by hand
still · upright · 70% full
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/text-bottle
Props
- tilt / defaultTilt
- number
- default 0
- The bottle's angle in degrees, negative left and positive right; controlled or the uncontrolled start.
- onTiltChange
- (tilt: number) => void
- default —
- Fires from the drag release or the key that tipped it, with the new angle in whole degrees.
- viscosity
- number
- default 0.35
- How thick the liquid is, 0 (water: quick waves, a fast glug) to 1 (syrup: slow, damped, a slow pour).
- charset
- "ascii" | "blocks" | "dots" | "binary"
- default "ascii"
- The glyphs the water is typed in, light to heavy.
- ramp
- string
- default —
- A custom density ramp, light to heavy, starting with a space; overrides charset.
- fill
- number
- default 0.7
- How full the bottle starts and refills to, 0 to 1.
- maxTilt
- number
- default 135
- The furthest it tips either way, 30 to 170 degrees.
- refill
- number
- default 7000
- How long an upright bottle takes to refill from empty, ms; 0 never refills.
- cellSize
- number
- default 10
- The grid's font size in px; smaller is a finer, denser bottle.
- waterColor
- string
- default "var(--accent-bright)"
- The water, any CSS colour, drawn at fixed lightness so it reads on both themes.
- glassColor
- string
- default —
- The glass outline, any CSS colour; defaults to the ink at two thirds.
- onStateChange
- (state: TextBottleState, level: number) => void
- default —
- Fires when it starts or stops pouring, sloshing or refilling, or runs empty, with the level 0 to 1.
- onLevelChange
- (level: number) => void
- default —
- Fires as the level passes each 5% step while it pours or refills.
- sound
- boolean
- default false
- A glug loop while it pours, only after a tip by hand or key.
- label
- string
- default "Bottle of water"
- The slider's accessible name.
- paused
- boolean
- default false
- Freezes the water: no waves, pouring or refilling; the bottle still turns.
- disabled
- boolean
- default false
- No tipping by hand or key.
- dpr
- number
- default 2
- Cap on the canvas's device pixel ratio, at most 2.
- className
- string
- default —
- Sizes the piece; it fills a 448px-tall box by default.
| Prop | Type | Default | Description |
|---|---|---|---|
| tilt / defaultTilt | number | 0 | The bottle's angle in degrees, negative left and positive right; controlled or the uncontrolled start. |
| onTiltChange | (tilt: number) => void | — | Fires from the drag release or the key that tipped it, with the new angle in whole degrees. |
| viscosity | number | 0.35 | How thick the liquid is, 0 (water: quick waves, a fast glug) to 1 (syrup: slow, damped, a slow pour). |
| charset | "ascii" | "blocks" | "dots" | "binary" | "ascii" | The glyphs the water is typed in, light to heavy. |
| ramp | string | — | A custom density ramp, light to heavy, starting with a space; overrides charset. |
| fill | number | 0.7 | How full the bottle starts and refills to, 0 to 1. |
| maxTilt | number | 135 | The furthest it tips either way, 30 to 170 degrees. |
| refill | number | 7000 | How long an upright bottle takes to refill from empty, ms; 0 never refills. |
| cellSize | number | 10 | The grid's font size in px; smaller is a finer, denser bottle. |
| waterColor | string | "var(--accent-bright)" | The water, any CSS colour, drawn at fixed lightness so it reads on both themes. |
| glassColor | string | — | The glass outline, any CSS colour; defaults to the ink at two thirds. |
| onStateChange | (state: TextBottleState, level: number) => void | — | Fires when it starts or stops pouring, sloshing or refilling, or runs empty, with the level 0 to 1. |
| onLevelChange | (level: number) => void | — | Fires as the level passes each 5% step while it pours or refills. |
| sound | boolean | false | A glug loop while it pours, only after a tip by hand or key. |
| label | string | "Bottle of water" | The slider's accessible name. |
| paused | boolean | false | Freezes the water: no waves, pouring or refilling; the bottle still turns. |
| disabled | boolean | false | No tipping by hand or key. |
| dpr | number | 2 | Cap on the canvas's device pixel ratio, at most 2. |
| className | string | — | Sizes the piece; it fills a 448px-tall box by default. |