Project rebuild from a static placeholder to a live 3D vector globe with real-time data layers. Sessions are listed newest-first. Each entry shows the type of change (feat / fix / refactor / data / infra).
/api/headlines.php. It scrolls like a
TV news ticker (pauses on hover), and each headline links to a real news
article (Google News) — the Gironde fire → France 24. Click the
card to fly the map there and switch on the matching layer; click the ↗ to
open the article.
nginx.conf that dropped
listen 127.0.0.1:3300 — the loopback origin the Cloudflare
tunnel connects to — so Cloudflare returned 502 for all dynamic content
(cached static assets still served). Restored the listener and fixed it in
the source config so future deploys can't drop it again.
/-focusable search box over all 75 streams;
six one-click curated presets (Disasters / Air traffic / Maritime /
Space / Internet / Threats) + clear. A floating legend (top-left) lists
active layers with colour + live count and lets you click to remove one.
Keyboard shortcuts (/ 1-6 0
h t p +/-
?) with a help overlay. First-visit onboarding nudge.
Day/Night terminator now on by default. Returning visitors get a
fast-forwarded boot.
paintPanel() rebuilt the whole 75-row DOM on every refresh
(lightning every 8 s), yanking the user's scroll back to the top
mid-read. Split into a structural paint + a lightweight
refreshLayerRow() that only rewrites a single row's
count/active state — scroll position is now preserved. The live count
also flashes when a layer updates.
solar mode is no longer a flat sky chart — it's a real
WebGL scene (Three.js r160, self-hosted in /vendor). Nine
textured sphere meshes (NASA / solarsystemscope equirectangular maps),
OrbitControls (drag to orbit, scroll to zoom, click a body to focus),
a 4 000-star field, Saturn's rings, faint orbit paths, and a PointLight
at the Sun giving every planet a real day/night terminator. Lazy-loaded
via dynamic import() so visitors who never open solar pay
zero bytes for it; MapLibre is hidden while the scene is active.
great-quakes.json with a ±2° / same-year tolerance,
appends the new ones, and auto-redeploys only if the file changed. It
also probes the upstream feeds (USGS, NOAA SWPC, OpenSky, CelesTrak,
Onionoo) and logs latency. Keeps the historical-quake layer current with
no manual edits.
rm -f'd /tmp/und3r-visitors.jsonl
on every run, resetting the stats dashboard's visitor counter to zero on
each ship. The append-only beacon log now survives deploys
(stats.php only reads the last 7 days anyway). Also widened
the live-visitors globe layer window from 30 min to 24 h.
mars-quakes (NASA InSight SEIS catalog),
mars-features (named craters + Tharsis volcanoes).
Earth: great-quakes (deadliest M7+ since 1556),
cable-landings (37 strategic submarine-cable terminuses).
Space: space-debris (Cosmos-1408 fragments via Celestrak
TLE + SGP4). New planet mode solar — Sun + 8 planets at
their live heliocentric positions from J2000 Keplerian elements;
flat sky chart on a black background.
_runOnceMapReady;
(3) pulse rings + halos dominated the view at city zoom — added
zoom-dependent radiusMaxPixels caps; (4) far-side bleed-through of
thousands of satellites — added great-circle culling against camera
center for points + rings, skipped for arcs/paths.
matchMedia('(max-width: 720px)') at boot and add
.hidden to the panel so the user lands on the globe,
not the layer list. Bigger tap targets on layer rows, taller
feedback textarea, bottom-anchored panel sheet stops at 60vh, HUD
logo scaled 0.7×, footer wrapped + smaller font, panel-reopen tab
moved to top-right where it doesn't fight the about/donate row.
/tmp/und3r-visitors.jsonl,
/var/lib/und3r-feedback/feedback.jsonl, and the last
256 KB of nginx access.log. Counts: total visits, 24h,
7d, top countries, HTTP 2xx/3xx/4xx/5xx, top paths, full feedback
list newest-first. Terminal-styled to match the site. Auth uses
inet_pton round-trip so IPv6 normalization can't lock
the owner out.
/manifest.json declares the site installable (standalone
display, theme color #00ffaa, SVG icon). /sw.js
precaches the shell (/, /style.css,
/manifest.json) via Promise.allSettled so
a transient miss doesn't abort install. Runtime cache: same-origin
assets + unpkg vendor libs (cache-first). Bypass: /api/*
+ all tile hosts (always live). Navigation: network-first with
per-URL fallback cache (so /about doesn't overwrite
/'s offline copy).
?p=mars/?l=... query is hashed and used as
the cache key in /tmp/und3r-cache/og-{hash}.png with
10-minute TTL + probabilistic 1% eviction sweep so the dir can't
fill from adversarial probing. Replaces the static og-image.png in
the index meta tags.
flock(LOCK_EX)
added to proxy.php in [s16] caused every FPM worker to queue behind
a leader fetching upstream (up to 25 s). With 5 workers + the
thundering-herd-prone cables endpoint, the entire pool
froze and every PHP endpoint — feedback.php, visitors.php,
stats.php, ALL of them — started returning 504 because no worker
was free to accept the next connection. Switched to
LOCK_EX | LOCK_NB: contender either serves stale
cache or 503's with Retry-After instead of waiting.
_auToLat
rebalanced so Sun + inner planets share a band instead of stacking;
(c) planet-aware constructor defaults for center/zoom so
?p=solar doesn't boot at Earth's Europe-framed view;
(d) space-debris refreshMs 2000→1000 to match the
other TLE layers (no more 2-second teleport hops);
(e) _animTimer + _viewportRefetchTimer
cleared on pagehide;
(f) feedback.php's flock return value checked (was
silently writing if locking failed);
(g) og.php cache hit re-validates filesize > 0 (truncated cache
files were serving empty PNGs).
/changelog.html — the page is
now public and linked from the footer next to "about" and "donate".
Removed the "private endpoint" line from the changelog footer; the
page still ships with Cache-Control: no-store +
CDN-Cache-Control: no-store so every visit fetches the
latest version (it gets a new commit per deploy). The previous
allow / deny / @notfound nginx block is gone — the
route is now just a plain try_files on the file.
feedback button that opens a terminal-
styled modal with three fields: type (bug / feature / other), message
(10–2000 chars), and optional contact. The modal closes on ✕ click,
backdrop click, or ESC. Submits post-form-urlencoded to
/api/feedback.php; success auto-closes after 1.5 s.
/var/lib/und3r-feedback/feedback.jsonl. Operator reads
with sudo tail -f. Defences: nginx rate-limit
(10 req/s burst 25), Origin header check (only
https://und3rgr0und.com), invisible honeypot field
(silent 200 on fill so bots don't learn), client-stamped
timestamp token rejected if > 24 h old, length bounds, control-
char strip. Endpoint added to the nginx PHP whitelist; everything
else under /api/*.php still 404s. Log dir is mode 0770
root:www-data — PHP-FPM (www-data) can write, world
can't read.
interleaved: true on a globe-projected map, the
deck.gl overlay can finish a drag in a half-redrawn state — one
hemisphere shows the pre-drag pointcloud, the other shows the
post-drag one, with a visible vertical seam down the middle of the
sphere. With dense layers active (satellites + Starlink = ~7 700
points), the seam looks like two separate globes overlapping.
moveend,
deck doesn't always know to invalidate its previous frame. Forcing
map.triggerRepaint() on moveend drops the
stale frame and re-projects deck.gl against MapLibre's final
camera. Cheap (one repaint per gesture end) and doesn't fight the
in-flight drag the way the [s18]-reverted
styledata-time setProjection did.
map.on('styledata', …) handler was
re-asserting map.setProjection({ type: 'globe' }) on
every styledata event. MapLibre 5 fires styledata
frequently during camera-driven re-renders (panning, zooming,
tile-load progress), and each setProjection call inside
the gesture resets the projection matrix mid-drag — the cumulative
pan delta gets clobbered and the camera barely moves.
setProjection call from the
styledata handler. The projection is set once at the
constructor and re-asserted once per style.load (after
setStyle() for the planet/theme swap) — that's
sufficient. Verified by dragging from (740, 400) to (200, 400):
lng goes from 0 to 136.8, globe rotates to show Asia/Oceania, and
every earthquake ring stays glued to its real geographic position
(Japan, Philippines, Indonesia, PNG, Vanuatu, New Zealand all on
the Ring of Fire).
interleaved: true and
back-tracked when MapLibre's load event stopped firing.
Root cause for the stall: deck.gl 9.0.36 in interleaved mode
registered a custom layer that kept the map's tile-loaded check
permanently false on globe maps, deadlocking
map.on('load'). Upgraded to deck.gl 9.3.2 (latest), which
ships proper MapLibre 5 globe integration. Verified on the Pacific
Ring of Fire: 217 earthquakes all pin to their exact lat/lng on the
globe — California cluster on the San Andreas, Alaska/Aleutian arc,
Vanuatu/Tonga subduction, Andes belt. Same fix carries Mars: 12
rovers now sit on the actual Mars surface inside Jezero / Gale.
deck.gl from 9.0.36 to
9.3.2 in index.html with new SRI digest
sha384-AZDnlCTAkFvSh/+Cf/M6f7gaXokQypLzR6WElZxMcHLxGp+6BXRftDtJDH42vG/U.
Switched MapboxOverlay from default non-interleaved
mode to interleaved: true so deck layers share
MapLibre's WebGL context and view-projection matrix — the only way
to get globe vertex transforms applied to scatter / ring / arc
layers. Verified deck.gl 9.3 lifts the 9.0.x load-event deadlock.
map.on('load') unreliable in interleaved modeloaded() probe can
return false indefinitely when an interleaved deck layer is sitting
in the style — so the load event stalls and the boot
crossfade never triggers. Replaced the single
map.on('load', _runOnceMapReady) with a triple-binding
against load, idle, and
style.load; _runOnceMapReady is idempotent
(gated by _mapLoaded) so the first trigger to fire wins.
style.load fires reliably as soon as the style is
parsed — well before tiles are in — which is what the boot panel
and URL-state replay actually need.
STYLE_MARS gets
its projection: { type: 'globe' } back, the
constructor stays at globe, and every setProjection()
call asserts globe rather than mercator.
app.js:670 wrote L._error directly into the
layer row's innerHTML. That field comes from
e.message on a caught exception (sliced to 24 chars), which
ultimately originates from upstream API responses. A misbehaving
proxy returning HTML-laden errors could inject script into the panel.
All panel-row interpolations now go through esc()
(label, desc, count, err message).
_planetSwapInFlight was cleared in
map.once('style.load') only — if MapLibre rejected the
style (malformed JSON, network error on tile source), the
'error' event fired first and left the flag stuck at
true, locking out every future Earth/Mars swap until a
page reload. Now also reset in the error handler.
startLayer() had no idempotency guard. When a layer was
both in the URL state and in defaultOn, or when
setReplayMode() re-entered an already-running layer, the
second call called setInterval again and overwrote the
Map entry. The first interval kept firing forever with no way to
stop it — slow leak measurable over hours. Added early-return when
state.enabled.has(L.id).
readURLState() used
parseFloat(p.get('z')) with ?? null fallback.
parseFloat('abc') returns NaN, and
NaN ?? null stays NaN. MapLibre 5's stricter
validator throws "invalid LngLat" on NaN center/zoom and the map
fails to construct. A shared link like
?z=foo&lat=bar would 502 the whole page. Now filtered
via Number.isFinite().
map.on('styledata') called _boostLightLabels()
which called setPaintProperty on every text-bearing
layer. In MapLibre 5, setPaintProperty re-fires
styledata — so each boost spawned another boost. Bounded
in practice (idempotent values) but burned CPU on every theme/planet
swap. Added a _boostedThisStyle flag, cleared on
style.load.
sec.js AbuseIPDB / HoneyDB / Feodo / URLhaus filters
checked timestamp fields but never lat/lng. When the proxy's geoip
lookup failed on a row, row.lat.toFixed(2) threw
TypeError mid-map, killing the whole fetch and showing
err: row.lat is undefined in the panel. Added
Number.isFinite(row.lat) && Number.isFinite(row.lng)
to every sec.js feed.
space.js ISS layer used d.latitude/
d.longitude without validation. A failed proxy fetch
resolves to {} (or a cached stale-null), producing two
ring objects with NaN coordinates. Added an
isFinite() guard at the top of the fetch.
L.pad?.latitude && L.pad?.longitude filter relied on
JavaScript's truthiness — numeric 0 is falsy, so any
pad on the equator (lat=0) or prime meridian (lng=0) was excluded.
No real-world spaceport sits exactly on 0,0 yet, but Sea Launch's
Odyssey platform routinely sat at lng≈154 lat=0. Changed to
explicit != null checks.
new Date(L.net).toLocaleString('en-US') with no
timeZone option rendered the launch NET in the user's
local zone — "T-5/19/2026 1:00 AM" with no timezone marker. Pads
and missions universally schedule in UTC. Added
{ timeZone: 'UTC', timeZoneName: 'short' } matching the
earthquake-replay fix in [s14].
if (_satState.records) return [];
on a failed refresh — discarding every subsequent tick rather than
propagating from the still-valid cached records. After the first
Celestrak rate-limit hit (1×/2 h per IP), the layer stayed empty
until a page reload. Now keeps using cached TLEs and backs off the
refresh clock.
earth.js lightning fetch's early-return-on-error path
returned Array.from(_strikeRings.values()) without
running the 25 s purge loop. A single bad response from
/api/lightning.php froze the last batch of strikes on
the map indefinitely. Now purges first, then returns what's left.
data.host as the FQDN
of the route collector ("rrc21.ripe.net"), but the
BGP layer's RRC_GEO coordinate table is keyed by short
ID ("rrc21"). Every lookup in
net.js missed and the layer stayed empty since the day
it was added. Now .split('.')[0] normalizes the key
before incrementing counts.
_subsolarPoint() built new Date(year, 0, 0)
— local time Dec 31 — then subtracted from UTC now
to derive day-of-year. The mix of timezones shifted the declination
term up to a full day either side of UTC, drifting the terminator
band visibly near equinoxes (where the sun's lat moves fastest).
Switched to Date.UTC(year, 0, 0).
chown -R www-data:www-data ran
before the sed -i cache-bust pass. sed -i
writes a temp file as the calling user (root) and renames it over
the original, dropping the previous owner — so every cache-busted
file ended up root:root. Re-ordered so chown is last.
(2) The regex [a-zA-Z_/-]+ for module names skipped
anything with digits or dots (geo2.js,
iss-1.js) — they were never cache-busted and stayed
pinned at the CF edge after each deploy. Widened to
[a-zA-Z0-9_./-]+.
burst=25 nodelay rate limit allows up to 25
concurrent requests through. When the cache file for an expensive
endpoint (cables, satellites — each up to
20 MB) was missing or stale, all 25 fired concurrent curls in
parallel — DoS-amplifying our own server. Added an
flock(LOCK_EX) per-endpoint: only one worker fetches
upstream, the rest wait and re-check the cache (now fresh) on
release. Also added Vary: Origin to prevent any future
multi-origin CORS cache poisoning.
projection: { type: 'globe' } declared in
STYLE_MARS, switching to Mars produced a flat equirectangular
map. Live-debugged with the Chrome MCP and found two roots:
map.setProjection didn't exist as a method, and MapLibre
4.7's setStyle() silently stripped the
projection field from the style object passed in
(getStyle().projection === undefined on every reload). Both
are MapLibre 4.x limitations — globe projection is constructor-only in
that branch and can't survive a style swap. Upgraded to
[email protected] where map.setProjection()
is a first-class runtime API and style spec carries projection
through setStyle(). Mars now ships as an actual sphere
with Arabia Terra, Margaritifer Terra, Terra Sabaea, Acidalia Planitia,
Syrtis Major all visible.
<script> and
<link rel=stylesheet> in index.html to
[email protected]. Recomputed the SHA-384 SRI digests
against the bytes served by unpkg:
sha384-GfxBM9x46BaAFxtCq39Fxir8fNZ4VDnwgfi6Kzi5/F1tAFsm0amuuV8kd+Pxzuf/
for the JS,
sha384-Nq6PQ+9vJPvw7U/VfDELyrWoGQMsy0gi6QShhaSrGzkpF5KkM40csg2leky+YMTd
for the CSS. Verified in-browser that [email protected]'s
MapboxOverlay still attaches cleanly to the 5.x map (it
does — all Earth layers still render).
console.warn('[planet] setProjection:', e)
that fired on every Earth/Mars swap in 4.x. setProjection()
is supported in 5.x and the try/catch around it is now belt-and-braces
only; no need to surface a warning when the path is well-trodden.
Updated the boot-log line from "linking maplibre 4.7" to "5.6" to
match what's actually loaded.
MapboxOverlay defaults to
interleaved: false, which rasterizes deck.gl on its own
2-D canvas using the previous mercator projection matrix and
composites it on top. deck.gl docs are explicit: globe projection
requires interleaved mode so the deck layers share MapLibre's
WebGL context and projection. Flipped to interleaved: true.
Points now stick to the sphere correctly — verified on the Pacific
Ring of Fire (India/Nepal/Philippines/Vanuatu earthquakes all land on
the right continents).
interleaved: true, deck.gl shares MapLibre's depth
buffer but the globe sphere itself isn't a real 3-D mesh — it's a
projected raster surface. Points on the hemisphere facing away from
the camera still render and bleed through the visible side as faint
ghosts past the rim. Tolerated for now: alternative would be a
pre-pass that clips points by angle-from-camera (dot product between
camera-to-point and camera-to-center vectors). Flagged for a future
session; not a blocker since the bleed-through is faint at z ≥ 1.5.
TypeError: Failed to fetch
in the console: the OpenPlanetary tile CDN is hosted at
cartocdn-gusc.global.ssl.fastly.net (Fastly, not
*.cartocdn.com), so the CSP's img-src and
connect-src rejected every tile. Added the host to both
directives → Mars surface now renders (Olympia Planum, Hellas Planitia,
Acidalia, Tempe Terra all show correctly).
pts-mars-rovers deck layer was built
with 12 data points, visible: true, and projected to the
correct screen coords — but radiusUnits: 'meters' with
r: 0.18 meant the dots were sub-pixel at z < 5. Visible
only when zoomed in to z ≥ 7. Converted to type: 'ring'
with pulse: true like the Earth landmark layers
(volcanoes, cyclones), so they're visible at any zoom regardless of
the planet's apparent size.
toLocaleString('en-US', { timeZone: 'UTC', timeZoneName: 'short' })
so the date matches the picker. Same fix applied to BTC blocks.
?v=$TS on
app.js, style.css, and layers.js,
but the per-group sub-modules (layers/earth.js,
_helpers.js, etc.) inherited browser caches indefinitely.
JS changes in those files didn't propagate even after a deploy.
Extended deploy.sh to sed-stamp every
from './…js' import across the module tree.
~5000 fires, on 2015-03-22:
59 earthquakes M≥2.5, 7 NEOs — all geographically resolved.
proxy.php now accepts an optional ?d=YYYY-MM-DD
that flows into the upstream URL builder (via the url_fn
callable). Cache filename includes the date so each replay-day keeps its
own snapshot indefinitely (live = current-day with normal TTL).
/z/y/x.jpg, row-major) which doesn't align with MapLibre's
XYZ scheme past zoom 0. Switched to OpenPlanetary's Mars basemap
(cartocdn-gusc.global.ssl.fastly.net/...) which uses true XYZ
and CORS-friendly headers. Mars planet view now renders.
location = block didn't have an
explicit try_files directive so file lookup was ambiguous,
and (2) Cloudflare was caching the 403→404 response from before the
whitelist was in place. Fix: added try_files /changelog.html =404;
and Cache-Control: no-store + CDN-Cache-Control: no-store
headers so Cloudflare bypasses the edge cache.
space.js:51 — d.altitude.toFixed(0) threw when
the wheretheiss.at API returned a null field (rare but observed). The
whole ISS layer stopped rendering. Fix: (d.altitude ?? 0).toFixed(0)
on all 4 fields (altitude, velocity, latitude, longitude).
energy.js and infra.js — p.mw.toFixed(0)
would throw if WRI shipped a null capacity. Pre-coerce to
mw = p.mw || 0 at the top of the map function so the layer
keeps rendering even on missing values.
planet: 'mars') is hidden in
rebuildDeckLayers when on Earth, but the URL-restore loop
would still call startLayer() + fetch().
The fetch did nothing useful but populated state. Two fixes:
planet doesn't match the active planetfetch() bails early if state.planet !== 'mars'amb.js:13 uses color: '#44aaff88' for submarine
cables to render them semi-transparent. _rgba() only sliced
the first 6 chars, dropping 88 and using the caller's
default alpha 220 instead. Fix: detect 8-digit hex and parse the embedded
alpha. Cables now render at ~0.5 opacity as originally intended.
scheduleURLWrite() serialized layers + viewport but NOT the
active planet. Sharing a Mars URL with someone else loaded Earth basemap
and they had to manually toggle. Added ?p=mars to the URL
and parsed it on boot (URL takes precedence over localStorage so shared
links always reproduce the sender's view).
center.lng past ±180 (e.g. 195°).
scheduleURLWrite writes the raw value. Normalize with
((lng + 540) % 360) - 180 so shared URLs always have
[-180, 180].
setStyle
calls before style.load fired, leaving the map in an
inconsistent state. Added a _planetSwapInFlight guard that
ignores re-entry until the basemap finishes loading.
ACTIVE_VOLCANOES in _data.js had both "Etna"
and "Etna (NE)" at nearly identical coords (37.748 / 37.940, both Sicily).
The map rendered two overlapping pulse rings. Removed the duplicate.
trek.nasa.gov), hides all Earth layers via a planet:
field, and shows 12 rover/lander positions (Curiosity, Perseverance,
Zhurong, InSight, Spirit, Opportunity, Sojourner, Phoenix, Viking 1&2,
Mars 3, Beagle 2). Persisted to localStorage. Theme toggle is
no-op while on Mars (basemap is fixed). Architecture extensible — adding
Venus / Moon = add a new style + set planet: on the layer.
| Hydroelectric | 7 156 plants · 630 KB |
| Solar | 10 665 plants · 988 KB |
| Wind | 5 344 plants · 500 KB |
| Coal | 2 330 plants · 221 KB |
| Gas | 3 998 plants · 391 KB |
proxy.php?t=who-don (6 h cache),
filters to last 365 days, parses country name from the title,
maps to ~200 country centroids. Ring size scales with recency
(≥30 days = small, <30 days = large red). Tooltip + details panel show full
title, publication date, age, and summary excerpt.
Borexino was at (42.45, -71.07) = Boston! → fixed to Gran Sasso (42.45, 13.57)Virgo was in Switzerland → fixed to Cascina, IT (43.63, 10.50)DESY was at Darmstadt → fixed to Hamburg (53.58, 9.89)European XFEL was in Denmark → HamburgAustralian Synchrotron was in Perth → Clayton, MelbournePalomar was in LA, Pic du Midi was in the Atlantic, CHIME was in southern AfricaB-root: lat/lng swapped (lat -117 outside [-90,90])primary_fuel=Nuclear (195 sites). Added 13 sites
WRI excludes (decommissioned / research): Chernobyl, Three Mile Island,
Ignalina, Caorso, Sellafield, Trawsfynydd, Krško, Gösgen, Mühleberg
(shutdown), Fukushima Daini, Onagawa II/III, Point Lepreau, Wolf Creek.
Coverage approaches reactormap.com (which shows reactor-unit level).
all_hour → all_day (6 → 170 events)dateadded < 24hlastReportedAt < 24hactive/total per group
(excluding disabled stubs). Persisted to localStorage so
preferences stick across visits.
map.load fires, so the
planet eases in instead of popping.
onClick never fired. Replaced with a
unified pointerup capture-phase handler that calls
overlay.pickObject({x,y,radius:28}) directly. Drag threshold
relaxed to 14px on touch.
Position
(lat/lng formatted), Source (upstream name), and
Source IP (not IP) for IP-based items.
Volcanoes Latitude+Longitude collapsed to Position. Solar
Subsolar → Position.
layers.js (1483 lines) split into a barrel + 10
modules: _helpers.js, _data.js,
_state.js, earth.js, space.js,
flights.js, net.js, sec.js,
infra.js, amb.js. Each file ≤370 lines.
await fetch + r.ok check + r.json + return []) → replaced
with one helper. Zoom-aware bbox logic duplicated between aircraft and
ships → extracted. hash() defined twice (Tor + Tor-exits) →
deduped. POOL_GEO (26 entries) was rebuilt inside fetch()
every 20s → hoisted to module scope.
globe.pointOfView shim (only referenced by deleted
.bak files), tsunamis proxy endpoint (the layer fetches USGS
direct), orphan /etc/nginx/snippets/und3r-security.conf (never
included). Migrated Cross-Origin-Opener-Policy and
Cross-Origin-Resource-Policy headers (which were stranded in
the dead snippet) into the active nginx config.
toLocaleString('fr-FR') → 'en-US' everywhere.
lang="fr" → lang="en" on both pages.
NASA_FIRMS_KEY (separate from regular NASA key).
Returns ~5000 satellite thermal detections per 24h. Server-parses CSV →
JSON, filters to nominal+high confidence, colors by FRP (fire radiative
power). EONET wildfires as a complementary "notable events" layer with
a 24h cutoff.
wss://ris-live.ripe.net from the browser
(CSP allow-listed). Subscribes to UPDATE messages, aggregates counts per
RRC (Route Collector), renders 23 RRC locations with rings whose
maxRadius is proportional to log(count). Counts decay 50% every 5s for
a "fading activity" feel.
/api/ships.php accepts ?bbox=south,west,north,east&max=N.
Client passes the current viewport when zoom ≥ 2 → server returns only
ships in view (up to 5000). At low zoom the global sample is downsampled
to 800 to avoid blobbing the planet. Cap raised from 600 → 5000.
moveend event re-fetches viewport-aware layers (debounced 350ms).
_boostLightLabels() pass that
adjusts label paint properties after style swap so place names, roads,
and admin boundaries stay legible. Dark theme untouched.
_vel (m/s), _hdg (degrees), and
_baseTime. _extrapolatePos() projects the
position forward based on elapsed time. updateTriggers bound
to a pulse tick so deck.gl re-renders.
pulseFade: true
flag distinguishes lightning (decay) from layers that loop forever
(volcanoes, cyclones).
--ais-token= deploy flag).
proxy.php with file-cache + flock for endpoints that need
CORS / auth / IP geolocation. Per-endpoint TTLs. Stale-while-error.
6 API keys managed via /etc/und3r/keys.conf (root:www-data
0640): NASA, NASA FIRMS, OpenAQ, AbuseIPDB, HoneyDB, Cloudflare Radar.
Each settable via a dedicated --xxx-key= deploy flag.
MapboxOverlay
for the 5000+ GPU-instanced dots and animated pulses. Globe-to-zoom is
bounded to z=18 so users can zoom from continent to individual buildings.
?l=&z=&lat=&lng=) and restored on load. Share
button (↗) copies the current state URL. ?embed=1 drops
the HUD chrome so the page can be iframed on other sites.
/usr/local/sbin/und3r-deploy writes a sudoers
whitelist so the unprivileged deploy user can redeploy and
manage services without a password but cannot inject arbitrary commands.
Atomic site swap, tarball backup, nginx config rollback on
nginx -t failure.