1// ui.js 2// UI utility functions for loading overlays, animated bars, and Cesium lighting. 3export let showLabels = false; 4export let showOrbits = false; 5 6// Shared orbit type labels â single source of truth (used by operatorPanel, filterNav, etc.) 7export const ORBIT_TYPE_LABELS = { 8 LEO: 'Low Earth Orbit', 9 MEO: 'Medium Earth Orbit', 10 GEO: 'Geostationary Orbit', 11 HEO: 'Highly Elliptical Orbit' 12}; 13 14export function getOrbitType(h) { 15 if (h < 2000) return "LEO"; 16 if (h < 35786 - 300) return "MEO"; 17 if (Math.abs(h - 35786) <= 300) return "GEO"; 18 return "HEO"; 19} 20 21// ââ Loading overlay lifecycle ââââââââââââââââââââââââââââââââââââââââââââââ 22// While the overlay is visible we add `or-booting` to <html>; CSS uses it to hide the 23// bottom #orbitalradar-branding watermark so "Orbital Radar" isn't shown twice (loader hero 24// + footer watermark). The flag is also set pre-paint in index.html (covers first paint); 25// we keep it in sync here so mid-session reloads (e.g. toggling debris) hide it too. 26function _setBooting(on) { 27 try { document.documentElement.classList.toggle("or-booting", !!on); } catch {} 28} 29 30// First display (page boot) uses the full-page scene; every display AFTER the first 31// dismissal (debris toggle, satellite reloads) uses the compact card (.or-loader-mid) 32// at the classic footprint with the live globe dimly visible behind. Boot itself also 33// passes through startFakeLoadingBar (loadSatellitesFromServer reason:'initial'), so 34// the mode is keyed off "dismissed at least once", never off the entry function. 35let _overlayDismissedOnce = false; 36// Bumped on every show; delayed hide steps bail out if a newer show has happened 37// (guards the "debris toggled right as the boot fade finishes" race). 38let _overlayShowSeq = 0; 39function _applyOverlayMode(o) { 40 if (_overlayDismissedOnce) o.classList.add("or-loader-mid"); 41} 42 43export function showLoadingOverlay() { 44 const o = document.getElementById("loadingOverlay"); 45 if (!o) return; 46 _overlayShowSeq++; 47 _setBooting(true); 48 _applyOverlayMode(o); 49 o.style.opacity = "1"; 50 o.style.display = "flex"; 51} 52export function hideLoadingOverlay() { 53 const o = document.getElementById("loadingOverlay"); 54 _setBooting(false); // reveals the branding watermark (fades in via CSS transition) 55 _overlayDismissedOnce = true; // any later show is a mid-session reload â compact card 56 if (!o) return; 57 const seq = _overlayShowSeq; 58 o.style.opacity = "0"; // soft fade (CSS transition on .or-loading-overlay) 59 setTimeout(() => { if (seq === _overlayShowSeq) o.style.display = "none"; }, 420); 60} 61 62// ââ Loader context: themed telemetry messages + live object counter âââââââââ 63// Counter target comes from the serve-time totals injected into window.__OR_COUNTS 64// (__TRACKED_TOTAL_EXACT__ / __ACTIVE_SATS_EXACT__ / __PAYLOADS_IN_ORBIT_EXACT__). 65// Boot counts to the TRACKED total â deliberately the same figure the globe headline 66// shows on arrival, so the loader and the globe can never disagree. Since the 2026-09 67// basis (b) split, `active` is the OPERATIONAL estimate and `payloads` is the full 68// payload count; non-payload arithmetic (the debris view) must use `payloads`, because 69// tracked â operational would count dead-but-orbiting payloads as debris. No hardcoded 70// numbers: if the tokens weren't injected (raw static serve) we fall back to a generic 71// label with no count. 72let _loaderCtx = "satellites"; 73let _loaderTarget = null; 74 75const _LOADER_MSGS = { 76 satellites: [[0, "Loading satellitesâ¦"], [22, "Fetching TLE catalogâ¦"], [48, "Propagating orbitsâ¦"], [74, "Calibrating ground tracksâ¦"]], 77 debris: [[0, "Loading debrisâ¦"], [22, "Scanning debris fieldâ¦"], [48, "Propagating fragment orbitsâ¦"], [74, "Mapping collision riskâ¦"]] 78}; 79 80function _parseCount(s) { 81 const n = parseInt(String(s == null ? "" : s).replace(/[^0-9]/g, ""), 10); 82 return Number.isFinite(n) && n > 0 ? n : null; 83} 84 85function _telemetryFor(percent) { 86 const set = _LOADER_MSGS[_loaderCtx] || _LOADER_MSGS.satellites; 87 let msg = set[0][1]; 88 for (const [threshold, text] of set) { if (percent >= threshold) msg = text; } 89 return msg; 90} 91 92// Infers satellites vs debris from the start message and wires up the counter label/target. 93function setLoaderContext(message) { 94 const counts = (typeof window !== "undefined" && window.__OR_COUNTS) || {}; 95 const active = _parseCount(counts.active); 96 const payloads = _parseCount(counts.payloads) || active; // pre-split pages carry no payloads key 97 const tracked = _parseCount(counts.tracked); 98 const isDebris = /debris/i.test(message || ""); 99 _loaderCtx = isDebris ? "debris" : "satellites"; 100 101 // Debris context recolours the loader accents amber (CSS .or-loader-debris). 102 try { document.getElementById("loadingOverlay")?.classList.toggle("or-loader-debris", isDebris); } catch {} 103 104 const labelEl = document.getElementById("loadingCountLabel"); 105 const countEl = document.getElementById("loadingCount"); 106 const unitEl = document.getElementById("loadingCountUnit"); 107 108 if (isDebris) { 109 // debris view layers debris + rocket bodies on top of the satellites â (tracked â payloads). 110 // Must be the PAYLOAD count, not the operational estimate: tracked â operational would 111 // relabel every dead-but-orbiting payload as a debris object. 112 _loaderTarget = (tracked && payloads && tracked > payloads) ? (tracked - payloads) : tracked; 113 } else { 114 // Boot counts to the tracked total â matches the globe's own headline figure. 115 _loaderTarget = tracked || active; 116 } 117 118 if (_loaderTarget) {
119 const totalStr = _loaderTarget.toLocaleString("en-US"); 120 if (labelEl) labelEl.textContent = isDebris ? "Cataloguing" : "Acquiring"; 121 if (unitEl) unitEl.textContent = `of ${totalStr} ${isDebris ? "debris objects" : "tracked objects"}`; 122 if (countEl) countEl.textContent = "0"; 123 } else { 124 if (labelEl) labelEl.textContent = "Syncing live orbital dataâ¦"; 125 if (unitEl) unitEl.textContent = ""; 126 if (countEl) countEl.textContent = ""; 127 } 128} 129 130export function setLoadingBarPercent(percent, text) { 131 percent = Math.max(0, Math.min(100, percent)); 132 133 const bar = document.getElementById("loadingBar"); 134 const rocket = document.getElementById("rocket"); 135 const pctEl = document.getElementById("loadingPct"); 136 137 if (bar) { 138 bar.style.width = percent + "%"; 139 } 140 141 if (rocket) { 142 const parent = bar?.parentElement; 143 const totalWidth = parent ? parent.offsetWidth : 320; 144 const rocketWidth = 40; 145 const px = Math.floor((percent / 100) * (totalWidth - rocketWidth)); 146 rocket.style.left = px + "px"; 147 } 148 149 if (pctEl) { 150 pctEl.textContent = `${Math.round(percent)}%`; 151 } 152 153 // Live object counter climbs with the bar toward the injected total. 154 if (_loaderTarget) { 155 const countEl = document.getElementById("loadingCount"); 156 if (countEl) countEl.textContent = Math.round((percent / 100) * _loaderTarget).toLocaleString("en-US"); 157 } 158 159 // Status line: explicit text wins (e.g. the final "â¦loaded!"); otherwise themed telemetry by %. 160 const loadingText = document.getElementById("loadingText"); 161 if (loadingText) loadingText.textContent = text || _telemetryFor(percent); 162} 163 164 165// --- Optionally: Retain this for initial startup sequence --- 166export function runSmoothLoadingStages(stageList, onComplete) { 167 let idx = 0; 168 let percent = 0; 169 function nextStage() { 170 if (idx >= stageList.length) { 171 setLoadingBarPercent(100, "Link establishedâvisualizing satellitesâ¦"); 172 setTimeout(onComplete, 850); 173 return; 174 } 175 const { msg, pct, dur } = stageList[idx]; 176 let start = percent; 177 let startTime = performance.now(); 178 function animate(now) { 179 let elapsed = now - startTime; 180 let localPct = Math.min(1, elapsed / dur); 181 let cur = Math.round(start + (pct - start) * localPct); 182 setLoadingBarPercent(cur, msg); 183 if (localPct < 1) { 184 requestAnimationFrame(animate); 185 } else { 186 percent = pct; 187 idx++; 188 nextStage(); 189 } 190 } 191 animate(performance.now()); 192 } 193 nextStage(); 194} 195let fakeLoadingInterval = null; 196 197/** 198 * Starts a smooth, fake loading bar that animates up to 90%, with themed telemetry 199 * messages and a live object counter. Context (satellites vs debris) is inferred from 200 * the message, so existing call sites need no changes. 201 */ 202export function startFakeLoadingBar(message = "Loadingâ¦") { 203 const overlay = document.getElementById("loadingOverlay"); 204 const bar = document.getElementById("loadingBar"); 205 if (!overlay || !bar) return; 206 207 _overlayShowSeq++; 208 _applyOverlayMode(overlay); // compact card iff this is a mid-session reload 209 _setBooting(true); 210 setLoaderContext(message); 211 212 let percent = 10; 213 overlay.style.opacity = "1"; 214 overlay.style.display = "flex"; 215 setLoadingBarPercent(percent); 216 clearInterval(fakeLoadingInterval); 217 fakeLoadingInterval = setInterval(() => { 218 if (percent < 90) { 219 percent += Math.random() * 3 + 0.5; 220 setLoadingBarPercent(Math.min(percent, 90)); 221 } 222 }, 120); 223} 224 225/** 226 * Completes the bar, then fades the overlay out and reveals the branding watermark 227 * (clears the boot flag). 228 */ 229export function finishFakeLoadingBar(finalMessage = "Complete!") { 230 const overlay = document.getElementById("loadingOverlay"); 231 clearInterval(fakeLoadingInterval); 232 setLoadingBarPercent(100, finalMessage); // also snaps the counter to its full target 233 234 const seq = _overlayShowSeq; 235 setTimeout(() => { 236 if (seq !== _overlayShowSeq) return; // a newer show superseded this dismiss 237 _setBooting(false); // reveal branding (fades in via CSS transition) 238 _overlayDismissedOnce = true; // any later show is a mid-session reload â compact card 239 if (overlay) { 240 overlay.style.opacity = "0"; // soft fade-out (CSS transition on .or-loading-overlay) 241 setTimeout(() => { if (seq === _overlayShowSeq) overlay.style.display = "none"; }, 420); 242 } 243 }, 650); 244} 245 246export function enableLighting(viewer) { 247 viewer.scene.globe.enableLighting = true; 248}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.