Spring became winter on the bass hit. That was the effect that mattered.

The second panorama is 3.4 MB. Same 3904×1088 scene as the spring image, same fixed camera — snow on the ground, icicles on a railing, kids mid-throw with snowballs where blossom had been. I dropped it into public/bg-winter.jpg and sat the spring layer on top of it.

The front layer carries a radial mask centred on the canvas. At rest, it renders spring opaque across the full frame. When the bass hits, the mask retreats from the centre outward — a window opens through the spring image into the winter beneath.

Bass drives opacity. Not position. Not scale.

That was the moment the visualiser stopped looking like a demo.

What was already live

Three reactive systems were running before the portal.

The neon bloom behind the city skyline flares on the kick. Its opacity runs min(1, 0.14 + kickEnv*0.7 + bass*0.22) — a floor of 0.14 so it’s never absent, then the kick envelope and sustained bass driving it upward. Gradient: pink → gold → transparent. On a strong kick the bloom saturates; between hits it dims to its floor.

The onset particles had been reworked into a proper life-arc. Each one launches at max energy then bleeds it off as it travels: energy = 0.9^age. That exponent scales a perpendicular wobble — sin(age·freq + phase) · amp · energy — so particles exit wide and erratic at birth, then narrow and settle as they decay. Lifespan doubled from the first version. They grow over the arc rather than holding a fixed size. On a busy passage: a field of trails in various stages of collapse.

Then the waveform, beat flash, RGB-split on the now-playing card. All pulled in from the design handover. Every layer moving in time with the signal.

Still read as a showcase.

Why motion stays at arm’s length

When something moves in response to audio, you register the mechanism. Particles kick out, bloom flares — you see the signal being transduced into motion. The effect is legible. It’s also demonstrative. You are watching a system respond to input; the scene hasn’t changed, it’s just animated differently depending on frequency content.

Opacity works on a different register. When a layer becomes transparent, the space changes. The spring panorama is a location. The winter panorama is the same location at a different time. When the bass drives the mask back and winter appears, you aren’t watching a spring scene react to music — you’re briefly somewhere else.

Motion communicates: the music is loud here. Opacity communicates: the music changed what you are looking at.

That gap is the point. Motion adds information about the signal. Opacity restructures the scene itself.

The portal

The two images are spatially coherent — same panorama dimensions, no positional offset. The winter scene registers exactly behind the spring one. The only impossible thing is that both can’t be simultaneously true, and the bass decides which one is.

The winter content earns its place. Snow-heavy branches, icicles catching diffuse light, kids frozen mid-throw with snowballs — kinetic content, seasonally extreme. The contrast with spring blossom is immediate. You don’t search for the difference; it arrives.

The radial mask shape matters for the reveal. The window opens from centre outward, so the first thing visible through it is the middle of the winter scene. The edges reveal as bass sustains. That ordering gives the cut a duration — it doesn’t flash. A flash reads as a beat effect. This reads as a transition.

The spring scene doesn’t acknowledge the winter. No blending, no morph, no narrative bridge. The mask retreats and the other season is there. Visually unambiguous; ontologically unresolved. The portal isn’t there to explain itself. The music opens the window; the music closes it. That’s the only contract on offer.

The track

All of the above was calibrated against the placeholder. Loading tasogare.wav — 3:16, 48kHz 24-bit stereo — confirmed the direction and shifted the balance.

Tasogare’s bass is structural. It builds under the midrange on phrase boundaries rather than hitting sharply on beats. That’s the right substrate for a portal that should reveal slowly. The window opens on the phrase, not on a transient. A kick-driven portal would read as a strobe; a phrase-driven one reads as a drift.

Against the 24-bit source, the bloom calibration shifted. The kick envelope reads crisper; kickEnv*0.7 contributes more of the bloom drive on most bars than bass*0.22. Bloom and portal end up with different temporal signatures from the same underlying signal: bloom flares on the hit, portal opens on sustained low-end. Two time-scales, one envelope.

The now-playing card picked up the real metadata at the same commit: TASOGARE as title, CAPTAIN RANDOM as artist. The invented placeholder text — track names like “ECHOES IN AMBER / SAKURA DUSK” — had been there since the scaffold. Gone.

What this costs

The portal requires a second full-size panorama. At 3.4 MB, bg-winter.jpg is not negligible. It loads at startup alongside the spring image. On a slow connection the winter layer is absent until the fetch resolves; the portal opens against whatever the compositing stack does with a missing image.

Deferred. The visualiser runs locally; the audience is small; the connection is fast. When those conditions change, lazy-loading the winter image against a bass-threshold trigger is the correct fix — fetch it when the player starts, not at page initialisation. Out of scope for now.

The mask implementation is CSS rather than Canvas 2D or WebGL. That limits control over the falloff curve. The edge is smooth enough for the current scene. A sharper or fully customisable gradient would require moving the compositing onto the Canvas layer. Also out of scope.

The lesson

Demos accumulate motion. Every audio-reactive canvas tutorial ends with something translating or scaling on the kick — motion is fast to implement, legible at a glance, self-documenting. It’s not wrong. It’s insufficient on its own.

The useful design question is what opacity can do that motion can’t. The answer isn’t more reaction — it’s a different kind. Motion leaves the scene intact and animates within it. Opacity can restructure the scene. Used with the right layering and the right driving signal, it moves the viewer rather than moving the elements.

The bloom and the particle life-arc are the right effects for the city skyline. Their job is to show that the audio is present and alive in the frame — information about the signal, expressed as motion. The portal’s job is different. It’s the effect that proves the music has consequences for what the scene is, not just for how it moves.

That distinction — effect versus consequence — is what the demo had been missing. Everything else was showing the music off. The portal lets the music decide something.

All writing →