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 @@