Longwave looks like an earth-observation console, but it is one HTML file, one stylesheet and one ES-module — no framework, no build step, and not one image asset. Two techniques carry the whole thing: a three.js data globe generated entirely from math, and a canvas warming-stripes chart. Here is how both work.
Climate data is easy to make dramatic and hard to make credible. The brief was the opposite of a doom poster: an instrument. So the interface borrows the grammar of shipped tools — a floating layer switcher, a legend with a color scale, a time-scrubber, metric tiles with deltas — studied from Apple Weather, The Weather Channel, Flighty, Felt and a few carbon dashboards. The visuals on top are invented; the interaction patterns are the ones analysts already trust.
Everything reduces to one sentence the hero states plainly: the planet is a time series — read it. The globe is the map; the stripes are the series; the layer panel decides which sensor you are reading through.
:root.importmap + CDN, imported with a dynamic
import('three') inside the globe initialiser — so if the CDN ever misses, the reveals, the
stripes chart and the counters still run and the page stays fully readable.There is no texture map anywhere. The continents are faked with a sum of Gaussian "blobs" placed at real
continent centroids. For any direction on the sphere, landness() returns how land-like it is, and
that single function seeds the dot-field, the heat nodes and the SVG fallback alike:
function landness(dir){
let m = 0;
for (const [lat, lon] of CONTINENTS){
const p = ll(lat, lon); // centroid → unit vector
const d = p.x*dir.x + p.y*dir.y + p.z*dir.z;
const ang = Math.acos(clamp(d,-1,1)); // angular distance
m = Math.max(m, Math.exp(-(ang/0.34)**2));
}
return m; // > 0.34 ≈ land
}
A ~3,200-point Fibonacci sphere becomes the globe: land directions get bright steel dots, ocean gets a sparse
dim grid, all drawn in a single THREE.Points draw call with additive blending so
they glow on the near-black. A fresnel shader on a slightly larger back-side sphere gives the atmosphere rim.
Each layer's heat nodes are scattered around seed regions (industrial cores for emissions, the poles for ice)
and colored by a shared diverging ramp. The strongest nodes throw arcs — quadratic béziers
lifted off the surface with a glowing sprite travelling along curve.getPoint(t) — and
expanding rings oriented tangent to the surface. Toggling a layer card just flips
obj.visible and re-points the legend:
globeApi.setLayers([...enabled], focus); // one method drives the render
// auto-rotate + drag-inertia, dpr capped at 2, RAF paused on tab-hide,
// geometries/materials tracked and disposed on pagehide.
The 1980–2024 record is generated deterministically — a linear warming trend plus ENSO wiggles, with El Niño
spikes and volcanic dips nudged in and 2024 pinned to the real-world headline of +1.28 °C.
Each year is one full-height bar colored by the same ramp the globe uses, so the legend reads across
both visualisations:
const t = (anomaly - AMIN) / (AMAX - AMIN);
ctx.fillStyle = rgb(ramp(t)); // cool → amber → hot
ctx.fillRect(x, 0, barWidth, H);
The bars draw in left-to-right the first time the chart scrolls into view. A pointer moving over the canvas — or the range scrubber below it — snaps to a year, moves the cursor, outlines that stripe and updates the big readout with the year, anomaly and a short note. Press Play trend and it walks 1980 → 2024 on a timer.
prefers-reduced-motion freezes the globe to a single
rendered frame (drag still works, on-demand), draws all stripes at once, and disables the pulse and ticker..js class gates every reveal; with
scripts off, a <noscript> rule un-hides the content so nothing is ever trapped invisible.landness) is shown and the canvas is hidden. No console errors on any path.<button>s with
aria-pressed; the metric card is an aria-live region; :focus-visible
rings everywhere; decorative layers are aria-hidden.Every path is relative and there is no build, so deploying is just:
gh repo create bswxyz/longwave-climate --public --source . --push
gh api --method POST /repos/bswxyz/longwave-climate/pages \
-f 'source[branch]=main' -f 'source[path]=/'
# live at https://bswxyz.github.io/longwave-climate/ in ~1–2 min
A .nojekyll file stops Pages from touching the folder structure. That's the whole deploy.
Longwave is a design-showcase concept — the sensors, datasets and numbers are synthetic and illustrative, not a live satellite feed or backend. See the repository README for the full demo-vs-real map.
← Back to Longwave