# Changelog ## v0.1.0 — 2026-09-16 18:24 - Scaffolded Next.js (App Router) + TypeScript project; dev server on port 3000 (`npm run dev`) - Full-viewport MapLibre GL map with OpenFreeMap "liberty" vector basemap (attribution included via style) - Zoom-driven camera controller: top-down at zoom ≤ 6, smoothstep tilt to 45° pitch by zoom ≥ 12; pitch is a pure function of zoom so it never fights pan/zoom gestures - Lite 3D terrain from AWS terrarium raster-DEM tiles (`map.setTerrain`); toggleable off via `?terrain=0` or `NEXT_PUBLIC_TERRAIN=0` (off in e2e for deterministic headless rendering) - EU airport seed data: OurAirports CSV (official mirror at davidmegginson.github.io/ourairports-data — the legacy ourairports.com/data URL now 404s) filtered to continent=EU with types small/medium/large_airport + seaplane_base → committed as `data/airports_eu.csv` (7445 airports; heliports, closed and balloon sites excluded — rationale documented in `lib/airports.ts`) - `GET /api/airports`: server-side CSV parse with in-memory cache, returns GeoJSON FeatureCollection (properties: name, icao, lat, lon) - Client renders airports as a GeoJSON circle layer (no DOM markers); click → popup with name + ICAO; header shows loaded airport count (`data-testid="airport-count"`) - MapLibre worker served from node_modules via `/api/mlworker/*` route — bundlers break the URL MapLibre derives for its worker, which silently hangs GeoJSON source updates - Tests: vitest unit suite (CSV parse/filter, GeoJSON build, pitch curve, API handler) + self-contained Playwright e2e suite (`webServer` auto-starts `npm run dev`; page load + airport count + marker click popup)