Northbound looks like a native EV trip planner, but it's one HTML file, one stylesheet and one module — no framework, no build step. The whole identity rests on one moment: a route line drawing itself across a 3D topographic map.
Every EV routing app shows you the same thing: a flat 2D map with pins. Northbound's bet is that the thing you're actually anxious about isn't the map — it's the landscape. The climb that eats your range. The gap between chargers over a pass. So we made the terrain the hero, gave it real elevation, and let the route draw itself across it while a plain-English planner answers the only question that matters: what battery will I arrive with?
The interaction layer borrows two patterns from shipped apps (Rivian, Tesla, Google Maps, My BMW): a route-summary header — "512 km · 2 stops · arrive 6:40pm" — and a vertical charge-stop timeline with drive segments between stops. On top of those we invented a curation layer the real apps don't have: good coffee, a short trail, a fast plug.
import().The terrain is a PlaneGeometry displaced by fractal value-noise, plus two Gaussian bumps
so there are real ridges for the route to weave between. Each vertex carries a normalised height so a
shader can paint elevation contour lines — the topographic look — straight in the
fragment stage:
// fragment — contour bands from normalised height vH
float f = vH * 15.0;
float w = fwidth(f); // screen-space derivative
float line = abs(fract(f - 0.5) - 0.5) / max(w, 1e-4);
float contour = 1.0 - clamp(line, 0.0, 1.0);
vec3 col = mix(base, uContour, contour * 0.8); // earth base, sage lines
col = mix(col, uBg, vFog); // fade into the forest ink
The route is a CatmullRomCurve3 whose control points sample the same height function, so
the line hugs the surface. It's a TubeGeometry, and it draws itself by animating
the geometry's draw range from zero to full — no shaders, no line libraries:
const dp = clamp((now - start - 500) / 2600, 0, 1);
const draw = 1 - Math.pow(1 - dp, 3); // ease-out
tubeGeo.setDrawRange(0, Math.floor(tubeCount * draw));
// charging nodes switch on as the line passes their curve position
const on = draw >= node.u - 0.02;
Scrolling advances the camera along the corridor (the eye and look-at targets interpolate down the valley), with a slow idle drift when you're parked at the top. A second, fatter additive tube gives the line its electric-green glow.
visibilitychange, and everything disposes on pagehide. No console errors.Every path is relative and there's no build, so deploying is three commands:
gh repo create bswxyz/northbound-ev --public --source . --push
gh api --method POST /repos/bswxyz/northbound-ev/pages \
-f 'source[branch]=main' -f 'source[path]=/'
# live at https://bswxyz.github.io/northbound-ev/ in ~2 min
A .nojekyll file keeps Pages from touching the folder structure. The three.js import map
resolves from jsDelivr at runtime, so there's nothing to bundle.
Northbound is a design-showcase concept — the routing, charge curves and charger data are modelled for the demo, not wired to a live network. See the repository README for the full demo-vs-real map.
← Back to Northbound