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.

KQ-1810 · SMOKE/TUNNEL

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.