KQ-1810 · SMOKE/TUNNEL
Smoke Tunnel
A wind tunnel's test section in Canvas 2D with threads of smoke streaming past a wing model: the flow is real potential flow around a Joukowski aerofoil with the Kutta circulation, solved in closed form, so the smoke divides at the stagnation point, hugs the upper surface and leaves the trailing edge smoothly, and puffs released together at the rake are drawn by the time smoke takes to reach each point, so the ones over the top race ahead. From the tip a tube of haze peels away with two filaments and dust spiralling through it, wound tighter the more lift the wing makes. Drag the wing (its leading edge follows the finger 1:1) or use the keys; release projects the throw and the model locks on with springs.snap, and past the stall angle the threads leave the surface and tumble on curl noise, eddies roll off the separation point and the model buffets. It is a slider: arrows step a degree, Page Up and Down five, Home and End go to the ends, and crossing the stall is announced.
Gaugeworks · tunnel 2
See the lift.
6° · flow attached · drag the wing
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/smoke-tunnel
Props
- flow
- number
- default 0.5
- The tunnel's speed, 0 (a slow drift) to 1 (a brisk run): the puffs, the dust and the turbulence.
- angle
- number
- default 6
- The wing's angle of attack where it starts, in degrees; changing it turns the wing there.
- smoke
- number
- default 0.6
- How much smoke the rake lets out, 0 (a few faint threads) to 1 (many dense ones).
- value
- number
- default —
- The wing's angle when you control it, in degrees; pair with `onValueChange`.
- onValueChange
- (degrees: number) => void
- default —
- Fires from the drag or the keys that turned the wing, with its new angle in degrees.
- minAngle
- number
- default -10
- The lowest angle the wing turns to, in degrees.
- maxAngle
- number
- default 26
- The highest angle the wing turns to, in degrees.
- stallAngle
- number
- default 14
- The angle past which the flow over the top lets go and the smoke tumbles, in degrees.
- camber
- number
- default 0.5
- How arched the wing's section is, 0 (symmetric) to 1.
- thickness
- number
- default 0.5
- How thick the wing's section is, 0 (thin) to 1 (thick).
- vortex
- number
- default 0.6
- How strongly the tube of haze winds off the tip, 0 (none) to 1.
- smokeColor
- string
- default "var(--foreground)"
- The smoke's colour, any CSS colour; resolved on the element so it follows the theme.
- modelColor
- string
- default "var(--ink-2)"
- The model's colour, any CSS colour.
- children
- React.ReactNode
- default —
- Content above the tunnel: a caption, a heading, a call to action.
- interactive
- boolean
- default true
- The drag and keys turn the wing.
- paused
- boolean
- default false
- Stop the smoke where it is; the wing still turns.
- label
- string
- default "Wing angle"
- Names the wing's angle control for assistive technology.
- seed
- number
- default 8
- Seeds the turbulence and the dust, so two tunnels never tumble alike.
- dpr
- number
- default 2
- Cap on the canvas's device pixel ratio, at most 2.
- onStallChange
- (stalled: boolean) => void
- default —
- Fires when the flow stalls (true) or attaches again (false), from the drag or keys that caused it.
- disabled
- boolean
- default false
- Shows the tunnel running but takes no input.
- className
- string
- default "h-56 w-full"
- Sizes the box.
| Prop | Type | Default | Description |
|---|---|---|---|
| flow | number | 0.5 | The tunnel's speed, 0 (a slow drift) to 1 (a brisk run): the puffs, the dust and the turbulence. |
| angle | number | 6 | The wing's angle of attack where it starts, in degrees; changing it turns the wing there. |
| smoke | number | 0.6 | How much smoke the rake lets out, 0 (a few faint threads) to 1 (many dense ones). |
| value | number | — | The wing's angle when you control it, in degrees; pair with `onValueChange`. |
| onValueChange | (degrees: number) => void | — | Fires from the drag or the keys that turned the wing, with its new angle in degrees. |
| minAngle | number | -10 | The lowest angle the wing turns to, in degrees. |
| maxAngle | number | 26 | The highest angle the wing turns to, in degrees. |
| stallAngle | number | 14 | The angle past which the flow over the top lets go and the smoke tumbles, in degrees. |
| camber | number | 0.5 | How arched the wing's section is, 0 (symmetric) to 1. |
| thickness | number | 0.5 | How thick the wing's section is, 0 (thin) to 1 (thick). |
| vortex | number | 0.6 | How strongly the tube of haze winds off the tip, 0 (none) to 1. |
| smokeColor | string | "var(--foreground)" | The smoke's colour, any CSS colour; resolved on the element so it follows the theme. |
| modelColor | string | "var(--ink-2)" | The model's colour, any CSS colour. |
| children | React.ReactNode | — | Content above the tunnel: a caption, a heading, a call to action. |
| interactive | boolean | true | The drag and keys turn the wing. |
| paused | boolean | false | Stop the smoke where it is; the wing still turns. |
| label | string | "Wing angle" | Names the wing's angle control for assistive technology. |
| seed | number | 8 | Seeds the turbulence and the dust, so two tunnels never tumble alike. |
| dpr | number | 2 | Cap on the canvas's device pixel ratio, at most 2. |
| onStallChange | (stalled: boolean) => void | — | Fires when the flow stalls (true) or attaches again (false), from the drag or keys that caused it. |
| disabled | boolean | false | Shows the tunnel running but takes no input. |
| className | string | "h-56 w-full" | Sizes the box. |