diff --git a/css/style.css b/css/style.css index cb3bd1e..1860ee0 100644 --- a/css/style.css +++ b/css/style.css @@ -254,6 +254,47 @@ html, body { /* Keep Cesium's info box on-theme without fighting it */ .cesium-infoBox { font-family: ui-monospace, monospace; } +/* ---- Radio Garden player ---- */ +#radio-player { + position: absolute; + bottom: 140px; + left: 14px; + width: 250px; + max-height: 320px; + display: flex; + flex-direction: column; + z-index: 11; + padding: 12px; + background: var(--panel-solid); + border: 1px solid var(--border); + border-left: 2px solid #7de3a0; + border-radius: 10px; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + font: 11px ui-monospace, Menlo, monospace; + color: var(--text); + box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5); +} +#radio-player[hidden] { display: none; } +#radio-player .rp-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; } +#radio-player .rp-title { font-weight: 700; color: #fff; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +#radio-player .rp-close { cursor: pointer; color: var(--dim); font-size: 15px; flex: none; } +#radio-player .rp-close:hover { color: var(--text); } +#radio-player .rp-list { overflow-y: auto; display: flex; flex-direction: column; gap: 2px; } +#radio-player .rp-row { display: flex; align-items: center; gap: 8px; padding: 3px 2px; border-radius: 5px; } +#radio-player .rp-row:hover { background: rgba(255, 255, 255, 0.04); } +#radio-player .rp-row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +#radio-player .rp-play { + flex: none; width: 22px; height: 22px; padding: 0; + font: 10px ui-monospace, monospace; color: #7de3a0; + background: rgba(125, 227, 160, 0.1); + border: 1px solid rgba(125, 227, 160, 0.35); + border-radius: 5px; cursor: pointer; +} +#radio-player .rp-play:hover { background: rgba(125, 227, 160, 0.25); } +#radio-player .rp-hint { color: var(--dim); padding: 4px 2px; } +#radio-player .rp-foot { margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--border); color: var(--dim); font-size: 9px; } + /* ---- Solar System mode ---- */ #ss-btn { position: absolute; diff --git a/js/layers/geojson-layer.js b/js/layers/geojson-layer.js index 840b1c3..1923f63 100644 --- a/js/layers/geojson-layer.js +++ b/js/layers/geojson-layer.js @@ -131,7 +131,10 @@ export function createGeoJsonLayer(ctx, spec) { } let json; try { - json = await res.json(); // never gate on Content-Type (EONET lies rss+xml) + // spec.parse lets non-JSON feeds (e.g. InciWeb RSS/XML) supply their own + // text → features parser; default path never gates on Content-Type + // (EONET lies rss+xml but is JSON). + json = spec.parse ? spec.parse(await res.text()) : await res.json(); } catch { ui.setStatus(spec.id, 'bad response — retrying', 'err'); return; diff --git a/js/layers/radiogarden.js b/js/layers/radiogarden.js new file mode 100644 index 0000000..c5ca8f3 --- /dev/null +++ b/js/layers/radiogarden.js @@ -0,0 +1,135 @@ +// Radio Garden layer (Wave 6) — ~8,000 live radio stations pinned to their real +// cities. Click a dot → pick a station → it streams live through a small player. +// Place/channel JSON comes via the same-origin proxy (proxy/rg/…, no CORS +// upstream); the AUDIO itself streams straight from radio.garden —