diff --git a/package-lock.json b/package-lock.json
index 1c3fb8b..fc01efe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,13 +9,15 @@
"version": "0.2.0",
"dependencies": {
"@fontsource/jetbrains-mono": "^5.2.8",
- "@fontsource/poppins": "^5.2.7"
+ "@fontsource/poppins": "^5.2.7",
+ "leaflet": "^1.9.4"
},
"devDependencies": {
"@sveltejs/adapter-node": "^5.3.4",
"@sveltejs/kit": "^2.50.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.2.1",
+ "@types/leaflet": "^1.9.22",
"svelte": "^5.51.0",
"svelte-check": "^4.4.2",
"tailwindcss": "^4.2.1",
@@ -1026,9 +1028,9 @@
"license": "MIT"
},
"node_modules/@sveltejs/acorn-typescript": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz",
- "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==",
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.11.tgz",
+ "integrity": "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -1053,18 +1055,18 @@
}
},
"node_modules/@sveltejs/kit": {
- "version": "2.55.0",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.55.0.tgz",
- "integrity": "sha512-MdFRjevVxmAknf2NbaUkDF16jSIzXMWd4Nfah0Qp8TtQVoSp3bV4jKt8mX7z7qTUTWvgSaxtR0EG5WJf53gcuA==",
+ "version": "2.70.2",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.70.2.tgz",
+ "integrity": "sha512-RzRoRpuR2KXqc5yMO0akQHDZeT4AslOlznGITURsqHaVbtyYP4Wn3eE3gxj9JcDyNYO0crkxhdwFHc+2vkVm6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
- "@sveltejs/acorn-typescript": "^1.0.5",
+ "@sveltejs/acorn-typescript": "^1.0.9",
"@types/cookie": "^0.6.0",
- "acorn": "^8.14.1",
+ "acorn": "^8.16.0",
"cookie": "^0.6.0",
- "devalue": "^5.6.4",
+ "devalue": "^5.8.1",
"esm-env": "^1.2.2",
"kleur": "^4.1.5",
"magic-string": "^0.30.5",
@@ -1082,7 +1084,7 @@
"@opentelemetry/api": "^1.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0",
"svelte": "^4.0.0 || ^5.0.0-next.0",
- "typescript": "^5.3.3",
+ "typescript": "^5.3.3 || ^6.0.0",
"vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0"
},
"peerDependenciesMeta": {
@@ -1419,6 +1421,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/leaflet": {
+ "version": "1.9.22",
+ "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.22.tgz",
+ "integrity": "sha512-h3lhECYEKDasG7LFHu+GiHqAvsgLuQvlJvVZzJDGONo3sEL+wUOqSFLnwkZlK0qVxnxbuGFW8iBlJNYs5wgndA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
@@ -1433,20 +1452,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@typescript-eslint/types": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz",
- "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
@@ -1544,9 +1549,9 @@
}
},
"node_modules/devalue": {
- "version": "5.6.4",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz",
- "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
+ "version": "5.9.0",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.0.tgz",
+ "integrity": "sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==",
"dev": true,
"license": "MIT"
},
@@ -1624,14 +1629,21 @@
"license": "MIT"
},
"node_modules/esrap": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz",
- "integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.3.0.tgz",
+ "integrity": "sha512-GQ/7RN8uOtEfNpzZzBMTzW9JBcX42oaSVtPzdF+6cEL8pqIL094iUpr9jzYGn4O4P/1S60dJ6izyT8F4LYARng==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "peerDependencies": {
"@typescript-eslint/types": "^8.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/types": {
+ "optional": true
+ }
}
},
"node_modules/estree-walker": {
@@ -1757,6 +1769,12 @@
"node": ">=6"
}
},
+ "node_modules/leaflet": {
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
+ "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
+ "license": "BSD-2-Clause"
+ },
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
@@ -2056,9 +2074,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"dev": true,
"funding": [
{
@@ -2103,9 +2121,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2116,9 +2134,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.8",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
- "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "version": "8.5.25",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
+ "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==",
"dev": true,
"funding": [
{
@@ -2136,7 +2154,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.16",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -2284,24 +2302,24 @@
}
},
"node_modules/svelte": {
- "version": "5.54.0",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.54.0.tgz",
- "integrity": "sha512-TTDxwYnHkova6Wsyj1PGt9TByuWqvMoeY1bQiuAf2DM/JeDSMw7FjRKzk8K/5mJ99vGOKhbCqTDpyAKwjp4igg==",
+ "version": "5.56.8",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.8.tgz",
+ "integrity": "sha512-PY8LOw7xP6c8IOiVqdo0sbbZVYhXRSfklOQLAUyGBKqjTX0wx/z4l/9J+PmBpmlLnxzEb1NqltxQ5/wZme/Cmg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
- "@sveltejs/acorn-typescript": "^1.0.5",
+ "@sveltejs/acorn-typescript": "^1.0.10",
"@types/estree": "^1.0.5",
"@types/trusted-types": "^2.0.7",
"acorn": "^8.12.1",
"aria-query": "5.3.1",
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
- "devalue": "^5.6.4",
+ "devalue": "^5.8.1",
"esm-env": "^1.2.1",
- "esrap": "^2.2.2",
+ "esrap": "^2.2.12",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",
diff --git a/package.json b/package.json
index 9b03f45..ce9007a 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@sveltejs/kit": "^2.50.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.2.1",
+ "@types/leaflet": "^1.9.22",
"svelte": "^5.51.0",
"svelte-check": "^4.4.2",
"tailwindcss": "^4.2.1",
@@ -23,6 +24,7 @@
},
"dependencies": {
"@fontsource/jetbrains-mono": "^5.2.8",
- "@fontsource/poppins": "^5.2.7"
+ "@fontsource/poppins": "^5.2.7",
+ "leaflet": "^1.9.4"
}
}
diff --git a/src/app.css b/src/app.css
index d5b4271..91fb55a 100644
--- a/src/app.css
+++ b/src/app.css
@@ -219,6 +219,12 @@ img {
letter-spacing: -0.05em;
}
+.subpage-hero h1 .status-badge {
+ vertical-align: middle;
+ margin-left: 14px;
+ font-size: 12px;
+}
+
.subpage-lead,
.subpage-copy {
margin: 20px 0 0;
@@ -415,6 +421,136 @@ img {
color: var(--brand);
}
+/* ─── Network map ────────────────────────────────────────────────────── */
+
+.map-panel {
+ display: flex;
+ flex-direction: column;
+}
+
+.map-frame {
+ width: 100%;
+ height: 380px;
+ margin: 8px 0 0;
+ border-radius: 10px;
+ overflow: hidden;
+ border: 1px solid var(--border-mid);
+ background: var(--surface-2);
+}
+
+/* ─── Leaflet dark-theme overrides ───────────────────────────────────── */
+
+.leaflet-pop-marker {
+ display: block;
+ width: 14px;
+ height: 14px;
+ border-radius: 4px;
+ background: var(--brand);
+ box-shadow: 0 0 0 4px rgba(255, 113, 113, 0.22);
+}
+
+.leaflet-ix-marker {
+ display: block;
+ width: 14px;
+ height: 14px;
+ border-radius: 999px;
+ background: var(--green-live);
+ box-shadow: 0 0 0 4px rgba(52, 211, 153, 0.2);
+}
+
+.leaflet-ix-marker.is-activating {
+ background: transparent;
+ border: 2px dashed rgba(255, 255, 255, 0.55);
+ box-shadow: none;
+}
+
+.leaflet-container {
+ background: var(--surface-2);
+ font-family: var(--font-mono);
+}
+
+.leaflet-popup-content-wrapper,
+.leaflet-popup-tip {
+ background: var(--surface-3);
+ color: var(--text);
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.4);
+}
+
+.leaflet-popup-content {
+ margin: 10px 12px;
+ font-family: var(--font-mono);
+ font-size: 12px;
+ line-height: 1.6;
+}
+
+.leaflet-container a.leaflet-popup-close-button {
+ color: var(--muted);
+}
+
+.leaflet-control-attribution {
+ background: rgba(12, 12, 14, 0.75) !important;
+ color: var(--muted) !important;
+ font-size: 10px !important;
+}
+
+.leaflet-control-attribution a {
+ color: rgba(255, 255, 255, 0.75) !important;
+}
+
+.leaflet-bar {
+ border: 1px solid var(--border-mid) !important;
+}
+
+.leaflet-bar a {
+ background: var(--surface-2) !important;
+ color: var(--text) !important;
+ border-bottom: 1px solid var(--border-mid) !important;
+}
+
+.leaflet-bar a:hover {
+ background: var(--surface-3) !important;
+}
+
+.map-legend {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 14px 24px;
+ margin-top: 24px;
+ padding-top: 20px;
+ border-top: 1px solid var(--border-mid);
+}
+
+.legend-item {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ font-family: var(--font-mono);
+ font-size: 12px;
+ color: var(--muted);
+}
+
+.legend-dot {
+ display: inline-flex;
+ width: 10px;
+ height: 10px;
+ border-radius: 999px;
+ flex: none;
+}
+
+.legend-dot.pop {
+ background: var(--brand);
+ border-radius: 3px;
+}
+
+.legend-dot.active {
+ background: var(--green-live);
+}
+
+.legend-dot.activating {
+ background: transparent;
+ border: 1.5px dashed var(--muted-soft);
+}
+
/* ─── Footer ─────────────────────────────────────────────────────────── */
.site-footer {
diff --git a/src/lib/components/SiteFooter.svelte b/src/lib/components/SiteFooter.svelte
index 2a12948..d104c69 100644
--- a/src/lib/components/SiteFooter.svelte
+++ b/src/lib/components/SiteFooter.svelte
@@ -12,6 +12,7 @@