← Driftline The guide · how it was built
Design showcase · build notes

Waves, depth, and receipts.

Driftline is an ocean-conservation nonprofit concept built as one HTML file, one stylesheet and one script — vanilla JS with GSAP for scroll work. No photos anywhere: the water is drawn, the data is the imagery, and scrolling the page is a dive from the surface to the midnight zone.

The driftline: 3–4 translucent sine bands, each the sum of two sines, flowing at different speeds.

The idea

Nonprofit sites usually beg. Driftline testifies: every claim on the page is a number that came off a scale, and the design's job is to make data feel like water. Three moves carry the whole site — a wave system that flows across every section boundary, a scroll descent that takes you from sunlight to midnight zone (with a depth gauge reading out the metres), and a donation panel that converts dollars into kilograms in real time. Urgency lives in one coral accent, used maybe three times. Everything else is patient, deep blue.

The stack

Signature technique #1 — the wave system

Every wave on the page — the hero water and the four section-boundary dividers — is the same WaveField class pointed at a different canvas. Each band is two summed sines (one long swell plus one short chop, moving in opposite directions), which is the cheapest trick there is for water that never visibly repeats:

const y = mid
  + A * .62 * Math.sin(u / L.l1 * TAU + t * L.s1 + seed)
  + A * .38 * Math.sin(u / L.l2 * TAU - t * L.s2 + seed * 1.7);

The band is built as an open Path2D, cloned and closed to fill the water, and the open copy is stroked with a faint foam line — so crest and body come from one set of points:

const closed = new Path2D(open);
closed.lineTo(w, h); closed.lineTo(0, h); closed.closePath();
ctx.fill(closed);            // the water
if (L.stroke) ctx.stroke(open);  // the foam crest

One shared rAF loop drives all six canvases, but an IntersectionObserver marks which are on screen and only those get drawn. Each divider gets a data-seed so no two flow in phase. With prefers-reduced-motion, the loop never starts — every field is drawn once at a fixed t and the ocean holds still.

Signature technique #2 — the scroll descent

The trick most people would reach for JS to do is pure CSS: the document body's background is one vertical gradient from surface blue to midnight black, so scrolling the page literally is descending through the water column — no JavaScript, no scroll listener, works in every browser ever:

body{
  background:linear-gradient(180deg,
    #0a2a43 0%, #072138 32%, #051b2e 52%,
    #041524 72%, #020a16 100%);
}

JS then adds the instrumentation on top. A fixed mono HUD converts scroll progress into metres and names the zone you're in — the sections are labelled 0 m, 200 m, 400 m … 1000 m to match:

const p = scrollY / (doc.scrollHeight - innerHeight);
const d = Math.round(p * 1000);
hud.textContent = String(d).padStart(4, '0');
zone.textContent = d < 180 ? 'sunlight zone'
                 : d < 860 ? 'twilight zone' : 'midnight zone';

GSAP's ScrollTrigger scrubs one more layer — a fixed radial "veil" whose opacity rises with depth, so the edges of the viewport dim the deeper you go. If GSAP fails to load, a plain scroll listener does the same job; if motion is reduced, the veil stays off entirely.

Signature technique #3 — data that rides the waves

The tide chart is a canvas bar chart where the bars flood upward — each bar eases in with a 110 ms stagger, filled with a teal-to-transparent gradient and capped with a 2px foam line, so the series reads as a tide coming in year over year. The donation panel runs the same philosophy in reverse: a real radio group of preset amounts and a one-time/monthly toggle feed one function that rewrites an aria-live line — "$25 hauls and logs 40 kg" — so screen readers hear the equivalence exactly when sighted users see it.

Details that matter

The tide remembers everything we give it. The site's only job is to make you believe a dataset can be a love letter.

Ship it on GitHub Pages

Every path is relative and there is no build step, so deploying is three commands:

gh repo create driftline-ocean --public --source . --push
gh api --method POST /repos/USER/driftline-ocean/pages \
  -f 'source[branch]=main' -f 'source[path]=/'
# live at https://USER.github.io/driftline-ocean/ in ~1 min

A .nojekyll file keeps Pages from processing the folder. That's the whole deploy.


Driftline is a design-showcase concept, not a real nonprofit — the organisation, events and figures are illustrative. See the repository README for the full demo-vs-real map.

← Back to Driftline