What it is
A landing page for Sete Colinas, a fictional brand of city stays and homes in Lisbon, drawn over a real 3D map of the city. The brand is made up; the places are real. Scrolling flies the camera through six stops (Praça do Comércio and the Baixa, Alfama and São Jorge Castle, the Bairro Alto viewpoints, Ponte 25 de Abril, Belém, Parque das Nações), each with a card of general facts about the place. A time-of-day slider relights the sky, water, terrain and buildings from dawn to night.
Engineering decisions
- MapLibre GL JS only, no extra libraries. Streets and building footprints come from OpenFreeMap's vector tiles (no API key, the Positron style), recoloured in code: every basemap layer gets a role (land, park, water, road, major road, rail, label) and the colours for that role are pushed in again whenever the hour changes. Everything else (shields, POIs, borders, building footprints) is hidden.
- Buildings are a
fill-extrusion layer on OpenStreetMap heights (render_height), coloured by height so tall buildings read as lit towers at night. They rise from the ground between zoom 13 and 14.5 as the camera comes in. - Terrain and hillshade come from the keyless AWS Terrarium elevation tiles, as two sources of the same tiles (MapLibre advises against sharing one). The terrain is exaggerated (1.6×, 1.2× on the low tier) because Lisbon's hills are only around 100 m high. Sky and fog use MapLibre's
setSky, and the light (setLight) follows a rough sun path: east in the morning, south at midday, west at sunset, a high moon at night. - Time of day is pure TypeScript (
data.ts): five brand palettes at key hours, blended with a smoothstep so colours rest near each key hour and move between them, wrapping past midnight. The slider updates the map at most once per frame. The module is unit-tested. - The stops are numbered DOM markers instead of map symbols, so they need no fonts or sprites and still show when the street style fails. Clicking a pin goes to that stop.
- Scroll story: one ScrollTrigger per section flies the camera when the section reaches the middle of the screen. The story and all text are HTML over a sticky map; the map is the picture.
- Reduced motion: no scroll story. The stops become a list next to the map, the camera jumps, and the pin pulse is still.
- Ready means the first
idle after the style loads (tiles drawn), with a 7 s timeout so a slow tile server never holds the loader. Off screen or in a hidden tab, flights stop and the pin pulse pauses; MapLibre draws only on demand otherwise. Unmount removes markers and listeners and calls map.remove(). - If the street style fails, a plain background style replaces it, terrain and pins stay, and a designed message says the street map is unavailable. If terrain tiles fail, the map simply goes flat.
Performance
MapLibre and its worker are about 380 KB gzip and load only on this page. The low tier (phones, ?quality=low) renders at pixel ratio 1 without antialiasing and with flatter terrain.