feat(mobile): weather and storm layers, card compositor, pins and nearest eye

Radar (nowCOAST, with replay), satellite IR clouds, lightning density,
NHC cones and 34/50/64-kt wind radii, 10 m and 250 hPa wind on the
repo's GPU renderer, air quality, GNSS interference and transit.

situations() now yields 25-30 cards across orbit, sky and ground with
band, archetype, telemetry, headline, real series and live camera media;
genres whose feed fails are omitted and reported by omitted().
thumbnail() composites each card in an offscreen canvas from the layers
that describe it. addPin() draws posts, witnesses and live view
frustums; nearestEye() finds the closest public camera.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VVYRAqiQj9Y1V9ZfG1RcDH
This commit is contained in:
Claude committed 2026-10-19 02:57:24 +00:00
1 parent a644e797a7
commit 2ef9dd7448
6 files changed
+73 -9

No files matched your search

+2
View File
@@ -969,6 +969,7 @@ export async function createGlobe(container, options = {}) {
// Semantic zoom for words too: from orbit only the headline layers.
if (planetary && !PLANETARY_LABELS.has(layer.id)) continue;
if (layer.id === 'lightning') continue; // the glow is the label
const crowd = layer.id === 'transit'; // hundreds of buses: selected only
const rank = LABEL_PRIORITY.indexOf(layer.id);
for (let i = 0; i < glyphs.length; i++) {
const bb = glyphs.get(i);
@@ -977,6 +978,7 @@ export async function createGlobe(container, options = {}) {
if (!p || p.x < 0 || p.y < 0 || p.x > w || p.y > h) continue;
const isSelected =
selected?.layer === layer.id && selected?.key === bb.id.key;
if (crowd && !isSelected) continue;
const d = Math.hypot(p.x - w / 2, p.y - h / 2) / Math.max(w, h);
candidates.push({
layer,