Hangar 12 sells twelve-minute flights over city traffic, so the site has to feel like an engineering document you can touch: one HTML file, one stylesheet, one script, and a wireframe eVTOL you can grab and rotate.
Urban air mobility asks people to board a new kind of aircraft, so the site's whole job is to make the machine feel inspectable. Instead of glossy renders, the hero is the engineering drawing itself — a procedurally-built wireframe H12-A4 on a carbon background, rotating slowly over a vertiport pad while a scan line sweeps it and spec callouts (RANGE 120 KM · CRUISE 240 KM/H · NOISE 62 dB(A) · PAX 4) track the actual parts. Everything else follows booking patterns proven in shipped products: a route-search card, vertical-timeline trip cards, a departures board with flight-status pills, and a reserve button that carries the concrete promise ("Reserve seat 2A — Fri 07:10 · $95"), the way Uber Reserve does.
import('three') raced against a
9-second timeout. If the CDN fails, times out, WebGL is missing, or the visitor prefers reduced
motion, the hero swaps to a static SVG two-view engineering drawing. The page can never blank.cubic-bezier(.19,1,.22,1) — a pure expo-out, the way an
instrument needle settles.There is no model file. The airframe is generated from a station table — the same numbers that draw the SVG blueprint in the craft section. The fuselage is lofted the way real airframes are drawn: cross-section rings at each station, connected by eight longerons:
// [station x, radius] — nose to tail
const ST = [[1.95,.02],[1.8,.16],[1.55,.32],[1.2,.46],[.8,.54],[.3,.56],
[-.2,.52],[-.7,.44],[-1.15,.32],[-1.55,.2],[-1.9,.1],[-2.15,.04]];
// one elliptical ring per station…
ST.forEach(([x, r]) => poly(ringPts(x, r, r * 0.82, 24), true, segTi));
// …and 8 longerons threading every ring
for (let k = 0; k < 8; k++) {
const a = k / 8 * Math.PI * 2;
poly(ST.map(([x, r]) => [x, Math.cos(a)*r, Math.sin(a)*r*0.82]), false, segTi);
}
Wing, booms, V-tail, skids and the vertiport pad are all built the same way and merged into a
handful of LineSegments draw calls — the whole craft renders in about six. The four
rotor-blade sets live in their own groups so they can spin (adjacent rotors counter-rotate,
slightly different speeds so the motion never phase-locks). The camera is
orthographic — no perspective, like a drawing, not a product shot.
The scan is a glow-colored ellipse of line segments sweeping the hull vertically, its opacity following a half-sine so it fades at the extremes:
const phase = (t * 0.16) % 1;
scan.position.y = -1.05 + phase * 2.1;
matScan.opacity = Math.sin(phase * Math.PI) * 0.45 + 0.04;
The spec callouts are real HTML, not textures. Four empty Object3D anchors sit on
the wingtip, nose, rotor hub and cabin; every frame each anchor is projected to screen space and
an SVG leader line is redrawn from the part to its label:
anchor.getWorldPosition(v3).project(camera);
const ax = ( v3.x * 0.5 + 0.5) * w;
const ay = (-v3.y * 0.5 + 0.5) * h;
leader.setAttribute('points', `${ax},${ay} ${elbowX},${labelY} ${edgeX},${labelY}`);
So the labels stay crisp, selectable and accessible while their leader lines chase the rotating airframe — the drawing annotates itself.
visibilitychange stops it on hidden tabs. Pixel ratio is
capped at 2, and geometries, materials and the renderer are disposed on pagehide.
A webglcontextlost handler swaps to the SVG fallback instead of a dead canvas.<symbol>
and reused by both the craft section and the no-WebGL fallback. Its reveal is a plotter wipe —
a clip-path inset chased by a glowing scan edge..js class gates every reveal, so the
page is fully readable with scripts off. Reduced motion disables the turntable, the scan,
the counters, the plotter wipe and the board cycling in one media query and one flag.aria-pressed, the
reserve confirmation is a role="status" live region, and every interactive element
has a :focus-visible outline in nav-light blue.Every path is relative and there is no build, so deploying is three commands:
gh repo create hangar-12 --public --source . --push
gh api --method POST /repos/USER/hangar-12/pages \
-f 'source[branch]=main' -f 'source[path]=/'
# live at https://USER.github.io/hangar-12/ in ~1 min
A .nojekyll file keeps Pages from touching the folder. That's the whole deploy.
HANGAR 12 IS A DESIGN-SHOWCASE CONCEPT — THERE IS NO FLEET, NO BOOKING BACKEND AND NO TYPE CERTIFICATE. SEE THE REPOSITORY README FOR THE FULL DEMO-VS-REAL MAP.
← BACK TO HANGAR 12