- The public-page backdrop is a wave front made of particles: a big arc that sweeps across the page
- Particle size comes from a size field computed in the vertex shader, in one draw call
- The pointer stirs the field and leaves a bounded wake; scroll position carries the front
- It is decoration: it stops when the tab is hidden and draws one still frame under reduced motion
Updated 11 October 2026. The first version of this post described an earlier build, with a curl-noise flow field and a saturating pointer damper. That shader was removed on 8 October. This post now describes the backdrop that ships today (
static/js/landing-scroll-orb.js).
The interactive backdrop behind CFIN's public pages is a field of small capsule-shaped particles. It is drawn with raw WebGL, in one draw call, with the work done in the vertex shader from each particle's home position and the time.
A wave front, not a ring
Earlier builds drew a ring or a blob. The current one draws a wave front: a large arc, with a radius of about 1.15 times the viewport height, whose centre roams mostly below and beside the screen. A curved front therefore crosses the page. A travelling swell band of bigger particles crosses it too, and the page's scroll position carries the front across. With a pointer, the front moves to pass through the hand.


Size comes from a field
Every particle's size is read from a field rather than set per particle: a narrow ring, a wide crest that grows toward the edge of the ring, a faint inner disc and slow noise blobs over the page. Below a threshold a particle is not drawn at all, which is why the page reads sparse and the front reads as a wave rather than a band of lines. In the shader, the size becomes the point size, with a floor of one pixel:
float sz = max(s * uK * uDpr, 1.);
vPx = sz;
gl_PointSize = sz;
The whole scene is a single call, gl.drawArrays(gl.POINTS, 0, count), over roughly 1,600 points at a 1440 by 900 window.
The pointer stirs it, within a cap
Moving the pointer makes the crest grow and the capsules fatten, then relax over a couple of seconds. It also leaves a wake of up to four impulses that push, swirl and fatten the particles they pass. Each impulse is capped so that a fast swipe cannot throw particles off the page:
var REST = .45, FAR = .55, INK = 1, WAKE_CAP = .095;
// ...
var c = Math.min(1, WAKE_CAP / v); // v is the pointer speed: faster swipes are scaled down, never up
At a 1440-pixel window, one impulse never moves a particle more than about 86 pixels.
What it costs, and when it stops
- Device pixel ratio is capped at 1.5.
- Low-power devices draw every other frame.
- It stops while the tab is hidden.
- Under reduced motion or a data-saver setting it draws a single still frame.
- If WebGL is unavailable, the page falls back to a static background, and nothing on a page waits for the backdrop.
The colours come from CSS custom properties on the layer and are re-read when the theme changes, so the same code serves light and dark. The sign-up page embedded in other sites does not run it.