# Voice wave

A ribbon of light that moves like a voice and splits into colour where it bends, and can listen to yours. Live at https://design.how/vault#voice-wave.

Install the skill, which carries the template, the brief and a check script:

```sh
npx skills add https://design.how/vault/voice-wave
```

## What it is

One wave across the middle of the card, tapering to nothing at both ends, with a softer mirrored copy of itself as a reflection. While it rests it breathes as an almost flat line; through a phrase it swells, ripples and fans apart into colour at every bend, then settles back. On a light page it composites as ink on paper, each part of the ribbon taking away the light its colour does not favour; on a dark page it is added as light.

The idea is one curve drawn four times. Each copy is the same wave, a fundamental plus an incommensurate second harmonic so no two peaks match, offset in phase by a spread between minus and plus s. Where the wave is flat the four copies overlap and, because the shader divides by the summed palette, cancel to neutral; where it bends, the phase offset pulls them apart and the edge splits into the four stops. Each copy is a Lorentzian line, k / (sqrt(d^2 + soft^2) + thickness), so it has a bright core and a long tail that reads as emitted light, with a band filled between it and the main curve so the ribbon has volume.

Three bands drive it. Low sets how far the wave swings, high sets how far the colours separate, mid thickens the line. By default the bands come from a scripted voice: six phrases of uneven length and force with real pauses between them, each with a quick attack and a long release. Pressing the button asks for the microphone, and only then; a WebAudio analyser groups FFT bins by frequency (60 to 320, 320 to 1600 and 1600 to 6000 Hz), followed with a fast attack and slow release. The colour split follows the swing about 120 ms late, so the fringe blooms just after the bend. Nothing is recorded: the audio never leaves the analyser, and stopping or leaving gives the device back.

## How it works

- Draw one full-screen triangle from gl_VertexID; do everything in one WebGL2 fragment shader.
- Evaluate the centre line as a fundamental plus an incommensurate harmonic under a cosine end taper, then draw it four times at phase offsets spread across [-s, s].
- Shade each copy as a Lorentzian line with a filled band back to the main curve, weight it by its palette stop, and divide the sum by the summed palette so overlap goes neutral.
- Add a mirrored, softened copy as a reflection, fade toward the card edges, and composite subtractively on light or additively on dark.
- Feed low, mid and high from a scripted phrase envelope by default; on an explicit click, from an AnalyserNode with bins grouped by frequency, each band followed with fast attack and slow release.
- Lag the colour spread behind the swing with a symmetric follower, about 120 ms.
- Read the page background token and data-theme, and re-read them when the theme changes.
- Cap the pixel ratio at 2, stop the loop on webglcontextlost, and on destroy stop the microphone tracks, close the audio context, delete the program and lose the context.
- Under reduced motion, draw one shaped frame with the colour already split, and offer no microphone.

## What you can change

- `PALETTE` (blue-violet, purple, magenta, warm coral): The four dispersion stops. Keep them spread apart, or the split averages to grey.
- `PHRASES` (six {speak, pause, force}): The simulated voice: how long each phrase runs, the pause after it and how hard it swings.
- `ZOOM` (0.62): How much of the card the wave fills. Lower is bigger.

Last updated: 2026-11-02
