Every parameter, and why it is set where it is
Each of the 21 parameters below is a real control in the panel. The reasoning is
first-hand: where a bound comes from a measured property of the work, that measurement is named.
Ground
Ground colour bg
colour, default #000000 (dark) / #0a0a0b (light)
The shells are emissive: the shader accumulates from zero and outputs uBg + acc, so black is not painted, it is the absence of light. That is why it stayed hardcoded through the first extraction — adding zero changes nothing. Exposing it costs one uniform and lets the work sit on a ground other than black; the dot grid flips to darkening above 0.5 luma so it survives light grounds.
When there is a ground at all, it belongs to the work rather than to the room it sits in — but by default there is none (see Ground opacity). The context is created with alpha: false — there is no alpha channel to be transparent in — and uBg feeds the dot polarity, so the ground is something the shader reads rather than something painted behind it. It travels with Copy code, and every export carries it. To place this on your own background, composite the black-ground frame with mix-blend-mode: screen: emissive light adds, and that is the original, not an approximation of it.
Ground opacity bgA
0–1, default 0
The default is 0: this work ships as light on transparency. The shells are emissive, so a ground is something you add, not something you remove — and on a page that already has a background of its own, an opaque black rectangle reads as a hole rather than as the work. At 1 the shader outputs exactly vec4(uBg + acc, 1.0), which is the original rendering as first published; anything between the two dissolves one into the other.
The dot grid goes with it. The dots are the ground reacting to the light, not part of the light — that is what Light sensitivity does — so at 0 they leave along with the surface they sit on. Leaving them behind would hand you a transparent export that is still speckled, and the panel already promised otherwise by grouping them under GROUND.
Two more things follow the slider rather than switching at an endpoint. The dot polarity leans back to additive as the ground leaves — a darkening dot on transparency would have to be negative light, which does not exist. And alpha is groundOpacity + brightest_channel * (1 - groundOpacity): the max channel, not luma, because luma weights blue at 0.114 and would make a deep blue glow export nearly invisible.
⚠️ Video cannot carry this. The recorder fills an opaque background on every frame and the codecs offered (VP8/VP9/AVC) are configured without an alpha plane, so transparency is a still-image capability. For video, put the glow on black and composite with mix-blend-mode: screen — emissive light adds, so that is the original rather than an approximation of it.
Dot spacing dot
6–48 px, default 18
Not yet derived — tuned by eye. See the list below.
Dot size dotSize
0.5–5 px, default 1.6
Not yet derived — tuned by eye. See the list below.
Dot brightness dotAlpha
0–0.3, default 0.045
Not yet derived — tuned by eye. See the list below.
Light sensitivity dotReact
0–4, default 0.9
Not yet derived — tuned by eye. See the list below.
Form
Circle radius scale
0.05–0.42, default 0.18
The radius of the ring in uv units, where the short edge of the canvas is 1. The shader computes R = uScale * (0.72 + 0.28 * uLevel), so at full activity R equals this value directly — at the ceiling of 0.42 the ring is 0.84 of the short edge across, leaving about 8% of margin on each side for the glow, which extends past R. Half the short edge would put the ring itself on the boundary and clip the halo. The exact 0.42 is not forced; the constraint it respects is.
The floor is forced. Frequency is divided by the radius (mix(0.4, 3.0, uFreq) / max(uScale, 0.02)) and that max clamps at 0.02 — below it nothing changes any more. The slider stops at 0.05, above the clamp, so every position on it does something.
Shells shells
1–48, default 30
The ceiling of 48 is hardcoded in the shader: for (int i = 1; i <= 48; i++). GLSL ES 1.0 requires a constant loop bound, so raising this number means editing the shader too — a language constraint, not a taste one. The floor of 1 is deliberate: pulled right down you can see that the halo is only ever a handful of lines.
Motion
Flow speed speed
0–64, default 26.1
The ceiling of 64 was measured, not picked. The four turbulence layers advance at 1x/2x/3x/4x in time, and past a certain speed their per-frame phase advances cancel one another: the form starts to boil rather than to flow faster. Measured, 32 to 48 barely reads as quicker. So 64 marks where the returns have gone, not a limit of the technique.
Spin spin
-0.6–0.6, default 0.05
Not yet derived — tuned by eye. See the list below.
Turbulence amount amp
0–3, default 0.15
Not yet derived — tuned by eye. See the list below.
Turbulence frequency freq
0–1, default 0.12
0–1 is normalised; the shader maps it through mix(0.4, 3.0, uFreq) and then divides by the ring radius. That division is a real fix, not tidiness: without it, radii of 0.12/0.20/0.34 gave 1/2/1 main lobes and the form jumped around as you turned the size. With it, the lobe count holds while the radius changes.
Time trail trail
0–4, default 0.62
Measured in field time (the same units as t inside the shader), not seconds. In seconds the visual length of the trail would change with the speed — measured, at speed 40 a seconds-based trail decorrelates the layers into noise.
Shell phase spread spread
0–1, default 0.38
0–1 normalised, mapped to 0–2π in the shader (mix(0.0, 6.283185, uSpread)). A full turn is every phase arrangement there is; going further only comes back around.
Layer variance variance
0–1, default 0.29
0–1 is the interpolation factor in a *= mix(1.0, max(w.x, w.y), uVariance). At 0 every layer shares one amplitude; at 1 the noise sets it entirely.
Activity level
0–1, default 0.49
0–1 normalised loudness. The real source of this parameter is an external signal — microphone RMS or band energy fed to setLevel(); the slider is only standing in for it. The bounds are the normalisation itself, nothing more.
Palette
Base color color
colour, default #22d3ee (dark) / #0f2ee0 (light)
On a light ground this work moves from cyan to blue, and the reason is arithmetic. The alpha this work exports is its brightest channel, so a colour dark enough to read against white is also nearly transparent — while luminance weights green at 0.72 and blue at only 0.072. Blue is the only hue that can be both almost opaque and dark. Measured: #22d3ee reaches 11.62:1 against black and 1.71:1 against white, which is invisible; the best any colour can do against white under this alpha rule is 8.74:1, at a saturated blue. The light default sits at 6.56:1 and keeps some of the cyan lineage rather than going to a pure primary.
There is only this one colour; every other is derived from it via colorShift. Change the base and the whole palette follows on its own, which is what makes the work reusable — split into three HSL sliders it would lose that property entirely.
Colour shift shift
0–2, default 0.7
The 0.25 factor makes shift = 1 sweep a quarter of the colour wheel. The span is then multiplied by (1 − 1/N), so it moves with the shell count: at shells = 30, shift = 1 gives 87° and shift = 2 gives 174°.
Render
Edge softness edge
0–2, default 0.2
Not yet derived — tuned by eye. See the list below.
Motion blur smooth
0–3, default 1
Not yet derived — tuned by eye. See the list below.
Brightness bright
0–2, default 1
Not yet derived — tuned by eye. See the list below.