From af5f213f0e98f101180c7651f88320f6a061f3fb Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Wed, 4 Feb 2026 10:44:02 +0100 Subject: [PATCH 01/19] Set lauch post to midnight on Feb 8/9 --- ssg/content/posts/hello-world.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssg/content/posts/hello-world.md b/ssg/content/posts/hello-world.md index e030b20..a8718b9 100644 --- a/ssg/content/posts/hello-world.md +++ b/ssg/content/posts/hello-world.md @@ -1,5 +1,5 @@ +++ -date = '2026-02-09T16:35:33+01:00' +date = '2026-02-09T00:00:00+00:00' title = 'Launch day' +++ From 185f625bbaeec8dc1feef38518b1b93d32d303cd Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Wed, 4 Feb 2026 10:56:15 +0100 Subject: [PATCH 02/19] Default local dev pmtiles/longest lines source to prod --- website/src/HeatmapLayer.ts | 7 +------ website/src/Worker.ts | 16 +++++++++++----- website/src/getLongestLine.ts | 6 +----- website/src/utils.ts | 3 ++- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/website/src/HeatmapLayer.ts b/website/src/HeatmapLayer.ts index b320f9a..b9a0c26 100644 --- a/website/src/HeatmapLayer.ts +++ b/website/src/HeatmapLayer.ts @@ -12,7 +12,6 @@ import { PMTILES_SERVER, packFloatToU8s, tileKey, - WORLD_PMTILES, } from './utils'; import vertex from './vertex.glsl?raw'; import type { WorkerEvent } from './Worker'; @@ -70,11 +69,7 @@ function initialise() { const params = new URLSearchParams(self.location.search); let source = params.get('pmtiles'); if (!source) { - if (import.meta.env.DEV) { - source = `/${WORLD_PMTILES}.pmtiles`; - } else { - source = PMTILES_SERVER; - } + source = PMTILES_SERVER; } state.worker.postMessage({ type: 'init', source }); state.worker.onmessage = onWorkerMessage; diff --git a/website/src/Worker.ts b/website/src/Worker.ts index d82f91b..05c2289 100644 --- a/website/src/Worker.ts +++ b/website/src/Worker.ts @@ -1,6 +1,12 @@ import { PMTiles } from 'pmtiles'; import type { TileGL } from './HeatmapLayer'; -import { CACHE_BUSTER, MAP_SERVER, tileKey, tileToLatLonBounds } from './utils'; +import { + CACHE_BUSTER, + MAP_SERVER, + MAP_SERVER_SUBDOMAIN, + tileKey, + tileToLatLonBounds, +} from './utils'; export type WorkerEvent = | { type: 'init'; source: string } @@ -9,6 +15,7 @@ export type WorkerEvent = let localTiler: PMTiles; let pmtilesSource: string; +let isProductionMapServer: boolean; const loading = new Map(); @@ -16,7 +23,8 @@ self.onmessage = async (event: MessageEvent) => { if (event.data.type === 'init') { const { source } = event.data; pmtilesSource = source; - if (import.meta.env.DEV) { + isProductionMapServer = pmtilesSource.includes(MAP_SERVER); + if (!isProductionMapServer) { localTiler = new PMTiles(pmtilesSource); } console.debug('Tile worker ready for:', pmtilesSource); @@ -37,13 +45,11 @@ self.onmessage = async (event: MessageEvent) => { // For zoom levels 0-8 we skip the Cloudflare Worker and use a CDN cache. This saves // on Cloudflare Worker monthly quotas. url = url - .replace('https://pmtiles.', 'https://cdn.') + .replace(`https://${MAP_SERVER_SUBDOMAIN}.`, 'https://cdn.') .replace('world.pmtiles/world', 'cache') .replace('.bin', ''); } - const isProductionMapServer = - !import.meta.env.DEV || localTiler.source.getKey().includes(MAP_SERVER); if (isProductionMapServer) { let tile: Response; tile = await fetch(url); diff --git a/website/src/getLongestLine.ts b/website/src/getLongestLine.ts index 095bbbc..afa90bb 100644 --- a/website/src/getLongestLine.ts +++ b/website/src/getLongestLine.ts @@ -327,11 +327,7 @@ function getLongestLinesSource() { const params = new URLSearchParams(self.location.search); const source = params.get('longest_lines'); if (!source) { - if (!import.meta.env.DEV) { - return `${CDN_BUCKET}/runs/${VERSION}/longest_lines_cogs`; - } else { - return `/longest_lines`; - } + return `${CDN_BUCKET}/runs/${VERSION}/longest_lines_cogs`; } else { return source; } diff --git a/website/src/utils.ts b/website/src/utils.ts index cb5edfc..3a45d14 100644 --- a/website/src/utils.ts +++ b/website/src/utils.ts @@ -3,7 +3,8 @@ import { LngLat, LngLatBounds } from 'maplibre-gl'; export const VERSION = 'ryan-fullworld-raw'; export const CDN_BUCKET = 'https://cdn.alltheviews.world'; -export const MAP_SERVER = 'https://pmtiles.alltheviews.world'; // Remember to change domain in `Worker.ts` too. +export const MAP_SERVER_SUBDOMAIN = 'pmtiles'; +export const MAP_SERVER = `https://${MAP_SERVER_SUBDOMAIN}.alltheviews.world`; export const WORLD_PMTILES = 'world.pmtiles/world'; // TODO move the file to its proper place export const PMTILES_SERVER = `${MAP_SERVER}/runs/${VERSION}/pmtiles/${WORLD_PMTILES}`; From 514c0abfc1bbe3cbdec302d73480760923980746 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Wed, 4 Feb 2026 11:02:50 +0100 Subject: [PATCH 03/19] Use `_` as coordinate separator in URLs Fixes #42 --- ssg/content/_index.md | 6 +++--- website/src/renderLongestLine.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ssg/content/_index.md b/ssg/content/_index.md index 77dfce1..410bc6e 100644 --- a/ssg/content/_index.md +++ b/ssg/content/_index.md @@ -4,7 +4,7 @@ With the help of a custom-developed algorithm, [CacheTVS](https://github.com/All checked _every single_ view on Earth in search of the coveted **longest line of sight on the planet**. Based on the method we detail [here](/faq/), we present the greatest view of all: -## [1. Hindu Kush to Pik Dankova (530km) ↗](https://map.alltheviews.world/longest/78.76539611816406-36.31400680541992) +## [1. Hindu Kush to Pik Dankova (530km) ↗](https://map.alltheviews.world/longest/78.76539611816406_36.31400680541992) ![Longest Line Of Site](/longest_line_1.webp 'The longest line of sight on the planet, at 530km, from the Hindu Kush to Pik Dankova') @@ -12,13 +12,13 @@ Based on the method we detail [here](/faq/), we present the greatest view of all Longest lines of sight tend to group together around peaks and ridges. So the following are more of our own curated list rather than the technically correct runners up. We chose them based on being in notably different regions of the world. -## [2. Telkik Shan to Kongur Tagh (507km) ↗](https://map.alltheviews.world/longest/80.29535675048828-36.52555465698242) +## [2. Telkik Shan to Kongur Tagh (507km) ↗](https://map.alltheviews.world/longest/80.29535675048828_36.52555465698242) ![Second Longest Line Of Site](/longest_line_2.webp 'The second longest line of sight, at 507km, between the Telkik Shan and Kongur Tah') Another long line of sight in the Himalays, over the Tarim Basin and Tibetan Plateau. With such tall mountain ranges on either side, they're everywhere! -## [3. Antioquia to Pico Cristobal (504km) ↗](https://map.alltheviews.world/longest/-75.72223663330078-6.75514030456543) +## [3. Antioquia to Pico Cristobal (504km) ↗](https://map.alltheviews.world/longest/-75.72223663330078_6.75514030456543) ![Third Longest Line Of Site](/longest_line_3.webp 'The third longest line of sight, at 504km, from Antioquia to Pico Crostobal in Colombia') Now we go right to the other side of the world to Colombia in South America. We've found a line of sight from the department of Antioquia to Pico Cristobal, Colombia's highest mountain. diff --git a/website/src/renderLongestLine.ts b/website/src/renderLongestLine.ts index 9783e09..6c1f9cc 100644 --- a/website/src/renderLongestLine.ts +++ b/website/src/renderLongestLine.ts @@ -46,7 +46,7 @@ export function setupLongestLines(coordFromURL: string | undefined) { } function extractCoordFromURL(coordFromURL: string) { - const parts = coordFromURL.replace('longest/', '').split('-'); + const parts = coordFromURL.replace('longest/', '').split('_'); const lng = parseFloat(parts[0]); const lat = parseFloat(parts[1]); const coord = new LngLat(lng, lat); @@ -120,5 +120,5 @@ function rotate(x: number, y: number, degrees: number) { } export function longestLineURL(lon: number, lat: number) { - return `/longest/${lon}-${lat}${window.location.search}`; + return `/longest/${lon}_${lat}${window.location.search}`; } From 7a0a6b71472906b050731fa96942888c679a8a46 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Wed, 4 Feb 2026 12:34:46 +0100 Subject: [PATCH 04/19] Better contrast colour for line of sight polygon Fixes #38 --- website/src/renderLongestLine.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/renderLongestLine.ts b/website/src/renderLongestLine.ts index 6c1f9cc..c294c6a 100644 --- a/website/src/renderLongestLine.ts +++ b/website/src/renderLongestLine.ts @@ -22,9 +22,9 @@ export function setupLongestLines(coordFromURL: string | undefined) { type: 'fill', source: 'longest-line', paint: { - 'fill-color': '#fd6612', - 'fill-outline-color': '#141f41', - 'fill-opacity': 0.5, + 'fill-color': '#5BC0EB', + 'fill-outline-color': '#ffffff', + 'fill-opacity': 0.8, }, }); From efd370adfd7a278fb746f2617b13a25d7cd6fdf9 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Wed, 4 Feb 2026 12:45:44 +0100 Subject: [PATCH 05/19] Rename heatmap's `State` to `HeatmapState` Avoids confusion with Svelte state. --- website/src/HeatmapLayer.ts | 131 +++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/website/src/HeatmapLayer.ts b/website/src/HeatmapLayer.ts index b9a0c26..75d6e05 100644 --- a/website/src/HeatmapLayer.ts +++ b/website/src/HeatmapLayer.ts @@ -36,7 +36,7 @@ type Uniforms = { uAverageSurfaceVisibility: WebGLUniformLocation | null; }; -type State = +type HeatmapState = | { map: MapLibre; gl: WebGL2RenderingContext; @@ -59,10 +59,10 @@ const AVERAGE_SURFACE_VISIBILITY = 700000.0; let fillerTile: TileGL; -let state: State; +let heatmapState: HeatmapState; function initialise() { - if (state === undefined) { + if (heatmapState === undefined) { return; } @@ -71,14 +71,14 @@ function initialise() { if (!source) { source = PMTILES_SERVER; } - state.worker.postMessage({ type: 'init', source }); - state.worker.onmessage = onWorkerMessage; + heatmapState.worker.postMessage({ type: 'init', source }); + heatmapState.worker.onmessage = onWorkerMessage; makeFillerTile(); } function onWorkerMessage(event: MessageEvent) { - if (state === undefined) { + if (heatmapState === undefined) { return; } @@ -89,10 +89,10 @@ function onWorkerMessage(event: MessageEvent) { return; } - state.tileCache.set(key, tile); + heatmapState.tileCache.set(key, tile); // Should these be throttled? - state.map?.redraw(); + heatmapState.map?.redraw(); } } @@ -149,7 +149,7 @@ const HeatmapLayer: CustomLayerInterface = { ), }; - state = { + heatmapState = { map, gl, program, @@ -164,39 +164,39 @@ const HeatmapLayer: CustomLayerInterface = { }, prerender() { - if (state === undefined) { + if (heatmapState === undefined) { return; } - if (Date.now() - state.lastGC < 60 * 1000) { + if (Date.now() - heatmapState.lastGC < 60 * 1000) { return; } - const mapBounds = state.map.getBounds(); + const mapBounds = heatmapState.map.getBounds(); - for (const [key, tile] of state.tileCache.entries()) { + for (const [key, tile] of heatmapState.tileCache.entries()) { if (isTileIntersectingBounds(tile.bounds, mapBounds)) { } else { - state.tileCache.delete(key); + heatmapState.tileCache.delete(key); } } - state.lastGC = Date.now(); + heatmapState.lastGC = Date.now(); }, async render(gl, matrix) { let max = 0.0; - if (state === undefined) { + if (heatmapState === undefined) { return; } let isSomethingToRender = false; - for (const tile of state.map.coveringTiles({ tileSize: 256 })) { + for (const tile of heatmapState.map.coveringTiles({ tileSize: 256 })) { const key = tileKey(tile.canonical.z, tile.canonical.x, tile.canonical.y); - let cachedTile = state.tileCache.get(key); + let cachedTile = heatmapState.tileCache.get(key); if (!cachedTile) { - state.worker.postMessage({ + heatmapState.worker.postMessage({ type: 'getTile', z: tile.canonical.z, x: tile.canonical.x, @@ -214,7 +214,7 @@ const HeatmapLayer: CustomLayerInterface = { continue; } const parentKey = tileKey(parent.z, parent.x, parent.y); - cachedTile = state.tileCache.get(parentKey); + cachedTile = heatmapState.tileCache.get(parentKey); if (cachedTile) { break; @@ -239,19 +239,22 @@ const HeatmapLayer: CustomLayerInterface = { return; } - gl.useProgram(state.program); - gl.bindBuffer(gl.ARRAY_BUFFER, state.vertexBuffer); - const positionLocation = gl.getAttribLocation(state.program, 'a_pos'); + gl.useProgram(heatmapState.program); + gl.bindBuffer(gl.ARRAY_BUFFER, heatmapState.vertexBuffer); + const positionLocation = gl.getAttribLocation( + heatmapState.program, + 'a_pos', + ); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); - for (const tile of state.map.coveringTiles({ tileSize: 256 })) { + for (const tile of heatmapState.map.coveringTiles({ tileSize: 256 })) { let scaleIfParent = 1.0; let offsetIfParentX = 0.0; let offsetIfParentY = 0.0; const key = tileKey(tile.canonical.z, tile.canonical.x, tile.canonical.y); - let cachedTile = state.tileCache.get(key); + let cachedTile = heatmapState.tileCache.get(key); if (!cachedTile) { let child = { @@ -265,7 +268,7 @@ const HeatmapLayer: CustomLayerInterface = { continue; } const parentKey = tileKey(parent.z, parent.x, parent.y); - cachedTile = state.tileCache.get(parentKey); + cachedTile = heatmapState.tileCache.get(parentKey); if (cachedTile) { const zoomDifference = tile.canonical.z - parent.z; @@ -283,38 +286,42 @@ const HeatmapLayer: CustomLayerInterface = { cachedTile = fillerTile; } - const projection = state.map.transform.getProjectionData({ + const projection = heatmapState.map.transform.getProjectionData({ overscaledTileID: tile, }); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, cachedTile.texture); - gl.uniform1i(state.uniforms.uData, 0); + gl.uniform1i(heatmapState.uniforms.uData, 0); gl.uniform1f( - state.uniforms.uIntensity, + heatmapState.uniforms.uIntensity, sharedState.heatmapConfig.intensity, ); gl.uniform1f( - state.uniforms.uContrast, + heatmapState.uniforms.uContrast, sharedState.heatmapConfig.contrast, ); gl.uniformMatrix4fv( - state.uniforms.uProjectionMatrix, + heatmapState.uniforms.uProjectionMatrix, false, new Float32Array(matrix.defaultProjectionData.mainMatrix), ); gl.uniform4f( - state.uniforms.uTileMatrix, + heatmapState.uniforms.uTileMatrix, ...projection.tileMercatorCoords, ); - gl.uniform1f(state.uniforms.uWorldOffset, tile.wrap); - - gl.uniform1f(state.uniforms.uMax, max); - gl.uniform1f(state.uniforms.uScale, scaleIfParent); - gl.uniform2f(state.uniforms.uOffset, offsetIfParentX, offsetIfParentY); + gl.uniform1f(heatmapState.uniforms.uWorldOffset, tile.wrap); + + gl.uniform1f(heatmapState.uniforms.uMax, max); + gl.uniform1f(heatmapState.uniforms.uScale, scaleIfParent); + gl.uniform2f( + heatmapState.uniforms.uOffset, + offsetIfParentX, + offsetIfParentY, + ); gl.uniform1f( - state.uniforms.uAverageSurfaceVisibility, + heatmapState.uniforms.uAverageSurfaceVisibility, AVERAGE_SURFACE_VISIBILITY, ); @@ -329,42 +336,42 @@ function makeTile( bounds: LngLatBounds, data: Uint8Array, ) { - if (state?.gl === undefined) { + if (heatmapState?.gl === undefined) { console.warn("No GL context, couldn't make tile"); return; } - const texture = state.gl.createTexture(); - state.gl.bindTexture(state.gl.TEXTURE_2D, texture); - state.gl.texParameteri( - state.gl.TEXTURE_2D, - state.gl.TEXTURE_MIN_FILTER, - state.gl.NEAREST, + const texture = heatmapState.gl.createTexture(); + heatmapState.gl.bindTexture(heatmapState.gl.TEXTURE_2D, texture); + heatmapState.gl.texParameteri( + heatmapState.gl.TEXTURE_2D, + heatmapState.gl.TEXTURE_MIN_FILTER, + heatmapState.gl.NEAREST, ); - state.gl.texParameteri( - state.gl.TEXTURE_2D, - state.gl.TEXTURE_MAG_FILTER, - state.gl.NEAREST, + heatmapState.gl.texParameteri( + heatmapState.gl.TEXTURE_2D, + heatmapState.gl.TEXTURE_MAG_FILTER, + heatmapState.gl.NEAREST, ); - state.gl.texParameteri( - state.gl.TEXTURE_2D, - state.gl.TEXTURE_WRAP_S, - state.gl.CLAMP_TO_EDGE, + heatmapState.gl.texParameteri( + heatmapState.gl.TEXTURE_2D, + heatmapState.gl.TEXTURE_WRAP_S, + heatmapState.gl.CLAMP_TO_EDGE, ); - state.gl.texParameteri( - state.gl.TEXTURE_2D, - state.gl.TEXTURE_WRAP_T, - state.gl.CLAMP_TO_EDGE, + heatmapState.gl.texParameteri( + heatmapState.gl.TEXTURE_2D, + heatmapState.gl.TEXTURE_WRAP_T, + heatmapState.gl.CLAMP_TO_EDGE, ); - state.gl.texImage2D( - state.gl.TEXTURE_2D, + heatmapState.gl.texImage2D( + heatmapState.gl.TEXTURE_2D, 0, - state.gl.RGBA8UI, + heatmapState.gl.RGBA8UI, config.tileSize, config.tileSize, 0, - state.gl.RGBA_INTEGER, - state.gl.UNSIGNED_BYTE, + heatmapState.gl.RGBA_INTEGER, + heatmapState.gl.UNSIGNED_BYTE, data, ); From ff70fa35538086b45cba21a084c96dbf928f0c71 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Wed, 4 Feb 2026 12:57:37 +0100 Subject: [PATCH 06/19] Default map cursor to pointer Also changes the "load" button for finding the longest line of sight in the viewport to use `cursor: pointer`. Fixes #36 --- website/src/Home.svelte | 5 +++++ website/src/index.scss | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/website/src/Home.svelte b/website/src/Home.svelte index 0e0fed4..603cf9d 100644 --- a/website/src/Home.svelte +++ b/website/src/Home.svelte @@ -188,6 +188,7 @@ loading... {:else} - - - From ad51d6c14e61fe200dac288ef5729669bbb30c29 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Sat, 7 Feb 2026 18:53:57 +0100 Subject: [PATCH 17/19] Update FAQs and add contact email everwhere --- ssg/content/about.md | 4 +++- ssg/content/faq.md | 12 +++++++++--- website/src/Home.svelte | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/ssg/content/about.md b/ssg/content/about.md index 60269b6..601ed3c 100644 --- a/ssg/content/about.md +++ b/ssg/content/about.md @@ -7,12 +7,14 @@ of sight. Ryan met Tom through a tech forum, [HackerNews](https://news.ycombonat after reaching out to recruit him for a company opening. They instantly clicked and have kept in touch since. -Recently, Tom started working on an algorithm to find the longest line of sight, +Recently (in July of 2025), Tom started working on an algorithm to find the longest line of sight, and Ryan noticed it through a post on a tech forum explaining the project. For the last 6 months both have spent innumerous hours engineering [a tool](https://github.com/AllTheViews/CacheTVS) for the longest line of sight for every point on the planet, and to let others explore the beautiful world and all its long lines of sight. +You can contact us at hello@alltheviews.world + ## Tom {{
}} When Tom isn't on his laptop he's meditating or getting to know a new country. He's been a digital nomad since 2015. You can find his longest lines blog posts [here](https://tombh.co.uk/packing-world-lines-of-sight) and [here](https://tombh.co.uk/longest-line-of-sight), or see what else he is up to on his website https://tombh.co.uk diff --git a/ssg/content/faq.md b/ssg/content/faq.md index dd85cf7..0ad45ad 100644 --- a/ssg/content/faq.md +++ b/ssg/content/faq.md @@ -16,12 +16,18 @@ Tom and Ryan go into detail on their respective blogs: * The original source of our data is from [NASA's SRTM survey](https://www.earthdata.nasa.gov/data/instruments/srtm) which is ~100m resolution analysis of the planet's elevation data. It is known to have some issues so we used a cleaned version kindly provided by [viewfinderpanoramas.org](https://www.viewfinderpanoramas.org/Coverage%20map%20viewfinderpanoramas_org3.htm). * We used a globe earth meaning it is an approximation of earth's shape, as it is an oblate spheroid. * We take refraction into account, and we use what the GIS community has calculated to be the world average, which is a refraction coefficient of `0.13`. +* The height of the observer is `1.65m` or `5'5"`. * Each viewshed is calculated using 360 lines of sight each seperated by 1°. This could potentially miss some longest lines of sight, but it is considered to be the optimal resolution to balance the accumulation of errors and computational costs. For more details, see: Siham Tabik, Antonio R. Cervilla, Emilio Zapata, Luis F. Romero in their 2014 paper _Efficient Data Structure and Highly Scalable Algorithm for Total-Viewshed Computation_ https://ieeexplore.ieee.org/document/6837455 -* All computation is done on [AEQD](https://en.wikipedia.org/wiki/Azimuthal_equidistant_projection) reprojections of the raw data. For the longest lines of sight on the planet, ~500km, the worst case errors caused by this projection can reach ~0.0685%. This error is only relevant to viewsheds at the edge of the computable area of the tile, therefore those viewsheds around 500km from the centre of the tile. - - +* All computation is done on [AEQD](https://en.wikipedia.org/wiki/Azimuthal_equidistant_projection) reprojections of the raw data. For the longest lines of sight on the planet, ~500km, the worst case errors caused by this projection can reach ~0.0685%. This error is only relevant to viewsheds at the edge of the computable area of [tiles](https://tombh.co.uk/packing-world-lines-of-sight), therefore those viewsheds around 500km from the centre of tiles. ## Is The Source Code Available? Yes. [The core algorithm](https://github.com/AllTheLines/CacheTVS). [The pipeline and web app](https://github.com/AllTheLines/viewview). +## Why Don't You Make The Sea More Visible? + +The sea actually has above average visibility, due to it generally not being blocked by hills and mountains. As such it can generate some often interesting and sometimes beautiful heatmaps, like around [the islands of South Korea](https://map.alltheviews.world/longest/126.9141495423105_33.956726689880966). + +## How Do I Contact You? + +💌 hello@alltheviews.world diff --git a/website/src/Home.svelte b/website/src/Home.svelte index 5c39d04..bd46063 100644 --- a/website/src/Home.svelte +++ b/website/src/Home.svelte @@ -261,7 +261,8 @@

Acknowledgments

This project was made by Tom and - Ryan. + Ryan. Contact us at + hello@alltheviews.world .

From f8dd021d9135734e0064717202416cfa7e3f0ba6 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Sat, 7 Feb 2026 18:54:23 +0100 Subject: [PATCH 18/19] Update heatmap layer toggle --- website/src/components/LayerToggle.svelte | 10 ++++++---- website/src/images/heatmap_layer.png | Bin 10189 -> 28047 bytes 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/website/src/components/LayerToggle.svelte b/website/src/components/LayerToggle.svelte index 37d0714..ee7fdf8 100644 --- a/website/src/components/LayerToggle.svelte +++ b/website/src/components/LayerToggle.svelte @@ -42,12 +42,14 @@ } } &.layer_toggle__on { - border: 2px solid green; - border-color: green; + border: 2px solid color-mix(in srgb, var(--primary-colour) 70%, white 30%); + &:hover { + border-color: var(--primary-colour); + } img { - opacity: 1; + opacity: 0.8; &:hover { - opacity: 0.7; + opacity: 1; } } } diff --git a/website/src/images/heatmap_layer.png b/website/src/images/heatmap_layer.png index 2b86858ccdbd11a6654d60a177d923b1b7abba91..02ef28f28bebcbc8998801c5e395c0cc0c9b07a9 100644 GIT binary patch literal 28047 zcmV*AKySZ^P)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rl0tX5<8&Id+rydey>G0stE(q=vpI>>K#8PmQlexij^aVGrN9X?IdFg= zKoSH&kbE-W7zqNy0c;0`11A>z!GYl^av;ev3CWVFP;OFUIEq74?5>_`t~cE2OndKq z*n6LQ?tS-7Rn?Ln&|R-y-FNP}=d81a|N5{05>Gz&Nhejp$zSf2e;2*i3C{lIPXDRv z{o%lcAGrAQ_%jz@>bjpfw{kq^&`P4D#8^w+S~iOmV;xHQ^ESqjYDH@;t+4tfA&>BmTYKD2jcvf?9vp@(z-Lyz4an6ANYe7pvnkba??_V`7LcspCLWpqH zgb07et0cjHPT^5~&#GDbMUEYGbL5 zplU6myT7Z()w_=3k!@?eVTiMs%;^T#|I(+w^`jcsai=04;@1R11mhB-b6fqRItO{8 zdG_)at+gD?D(c1?NNX&q5M){bEdXU>5CX>Y+8ZB^rZwnXQX50n7!GH(HzXyz@dH+b#tp?^>u2yuef zci_as$A@)pWBEX5^-HH)hdlBs@kmZ}O=#?&{pv>&XMTrue;0i(-g`l7Ez`1QS~lRI zNEJ!)o;7jo8!!Ef0O7!`^;*m4(zKSnX~n2W83j+0r<#o- zrD_bRR!qtoYkg>JOv^A!P*SotDRDTo^v|3Pp)*SqhqDS}!Qg0&1q9AH=2c6gCC