spacemap 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -37,7 +37,17 @@ var DEFAULT_HOST = {
37
37
  layer_nomenclature: () => "Named features",
38
38
  cooperative_wheel: () => "Use ctrl + scroll to zoom the map",
39
39
  cooperative_wheel_mac: () => "Use ⌘ + scroll to zoom the map",
40
- cooperative_touch: () => "Use two fingers to move the map"
40
+ cooperative_touch: () => "Use two fingers to move the map",
41
+ planetary_system_primary: () => "primary",
42
+ planetary_system_retrograde: () => "retrograde",
43
+ planetary_system_axis_unit: () => "primary radii · log",
44
+ tab_rings: () => "Rings",
45
+ system_map_solar_system: () => "Solar System",
46
+ system_map_moons: ({ count }) => count === 1 ? "1 moon" : `${count} moons`,
47
+ system_map_zone_inner: () => "Inner asteroids & comets",
48
+ system_map_zone_outer: () => "Outer asteroids & comets",
49
+ unit_symbol_astronomical_unit: () => "AU",
50
+ system_map_axis_au: () => "AU · log"
41
51
  },
42
52
  bodyHref: () => "",
43
53
  textures: "open"
@@ -6213,6 +6223,9 @@ var BodyIndex = class {
6213
6223
  set orientationVersion(value) {
6214
6224
  set(this.#orientationVersion, value, true);
6215
6225
  }
6226
+ orientationRead = /* @__PURE__ */ new Set();
6227
+ hostRead = /* @__PURE__ */ new Map();
6228
+ hostReadVersion = 0;
6216
6229
  childrenByParent = /* @__PURE__ */ new Map();
6217
6230
  moonMaxAByParent = /* @__PURE__ */ new Map();
6218
6231
  addListeners = /* @__PURE__ */ new Set();
@@ -10311,20 +10324,35 @@ async function loadTierBitmaps(id, tier, priority) {
10311
10324
  }));
10312
10325
  }
10313
10326
  var tierPrefetch = /* @__PURE__ */ new Map();
10327
+ var tierClaims = /* @__PURE__ */ new Map();
10314
10328
  function tierKey(id, tier) {
10315
10329
  return `${id}:${tier}`;
10316
10330
  }
10317
- function tierBitmaps(id, tier, priority) {
10331
+ function tierBitmaps(id, tier, priority, claim = true) {
10318
10332
  const key = tierKey(id, tier);
10319
10333
  let p = tierPrefetch.get(key);
10320
10334
  if (!p) {
10321
- p = loadTierBitmaps(id, tier, priority);
10335
+ const loading = loadTierBitmaps(id, tier, priority);
10336
+ p = loading;
10322
10337
  tierPrefetch.set(key, p);
10338
+ loading.catch(() => {
10339
+ if (tierPrefetch.get(key) !== loading) return;
10340
+ tierPrefetch.delete(key);
10341
+ tierClaims.delete(key);
10342
+ });
10323
10343
  }
10344
+ if (claim) tierClaims.set(key, (tierClaims.get(key) ?? 0) + 1);
10324
10345
  return p;
10325
10346
  }
10326
10347
  function releaseTierBitmaps(id, tier, bitmaps) {
10327
- tierPrefetch.delete(tierKey(id, tier));
10348
+ const key = tierKey(id, tier);
10349
+ const left = (tierClaims.get(key) ?? 1) - 1;
10350
+ if (left > 0) {
10351
+ tierClaims.set(key, left);
10352
+ return;
10353
+ }
10354
+ tierClaims.delete(key);
10355
+ tierPrefetch.delete(key);
10328
10356
  for (const b of bitmaps) b.close();
10329
10357
  }
10330
10358
  function makeCube(bitmaps) {
@@ -10339,10 +10367,10 @@ function pickLowTier(meta) {
10339
10367
  }
10340
10368
  function prefetchSkyboxTiers(meta) {
10341
10369
  const low = pickLowTier(meta);
10342
- if (low) tierBitmaps(meta.id, low).catch(() => tierPrefetch.delete(tierKey(meta.id, low)));
10370
+ if (low) tierBitmaps(meta.id, low, void 0, false).catch(() => {});
10343
10371
  }
10344
10372
  var FULL_TIER_GATE_TIMEOUT_MS = 12e3;
10345
- async function loadFromMeta(scene, renderer, meta) {
10373
+ async function loadFromMeta(scene, renderer, meta, disposed) {
10346
10374
  const tier = pickTier$1(meta, Math.min(renderer.capabilities.maxTextureSize, currentRenderTier().maxSkyboxFace));
10347
10375
  if (!tier) {
10348
10376
  console.warn(`Skybox ${meta.id}: no tiers declared`);
@@ -10355,7 +10383,7 @@ async function loadFromMeta(scene, renderer, meta) {
10355
10383
  };
10356
10384
  let fullInstalled = false;
10357
10385
  if (lowTier && lowTier !== tier) tierBitmaps(meta.id, lowTier).then((bitmaps) => {
10358
- if (fullInstalled) {
10386
+ if (fullInstalled || disposed()) {
10359
10387
  releaseTierBitmaps(meta.id, lowTier, bitmaps);
10360
10388
  return;
10361
10389
  }
@@ -10363,11 +10391,38 @@ async function loadFromMeta(scene, renderer, meta) {
10363
10391
  lowRef.bitmaps = bitmaps;
10364
10392
  scene.background = lowRef.cube;
10365
10393
  performance.mark("sm-skybox-low");
10394
+ if (fullFailed) keepLow();
10366
10395
  }).catch(() => {});
10396
+ let fullFailed = false;
10397
+ const keepLow = () => {
10398
+ if (!lowTier || !lowRef.cube || !lowRef.bitmaps) return;
10399
+ renderer.initTexture(lowRef.cube);
10400
+ releaseTierBitmaps(meta.id, lowTier, lowRef.bitmaps);
10401
+ lowRef.bitmaps = null;
10402
+ };
10367
10403
  if (tier !== lowTier) await Promise.race([eagerMinorsDone, new Promise((resolve) => setTimeout(resolve, FULL_TIER_GATE_TIMEOUT_MS))]);
10368
- const fullBitmaps = await tierBitmaps(meta.id, tier, tier === lowTier ? void 0 : "low");
10369
- const full = makeCube(fullBitmaps);
10404
+ let fullBitmaps;
10405
+ try {
10406
+ fullBitmaps = await tierBitmaps(meta.id, tier, tier === lowTier ? void 0 : "low");
10407
+ } catch (err) {
10408
+ fullFailed = true;
10409
+ if (!disposed()) keepLow();
10410
+ else if (lowTier && lowRef.bitmaps) {
10411
+ lowRef.cube?.dispose();
10412
+ releaseTierBitmaps(meta.id, lowTier, lowRef.bitmaps);
10413
+ lowRef.bitmaps = null;
10414
+ }
10415
+ throw err;
10416
+ }
10370
10417
  await new Promise((resolve) => "requestIdleCallback" in window ? requestIdleCallback(() => resolve(), { timeout: 2e3 }) : setTimeout(resolve, 500));
10418
+ if (disposed()) {
10419
+ fullInstalled = true;
10420
+ lowRef.cube?.dispose();
10421
+ releaseTierBitmaps(meta.id, tier, fullBitmaps);
10422
+ if (lowTier && lowTier !== tier && lowRef.bitmaps) releaseTierBitmaps(meta.id, lowTier, lowRef.bitmaps);
10423
+ return;
10424
+ }
10425
+ const full = makeCube(fullBitmaps);
10371
10426
  renderer.initTexture(full);
10372
10427
  fullInstalled = true;
10373
10428
  const prevBackground = scene.background;
@@ -10378,7 +10433,7 @@ async function loadFromMeta(scene, renderer, meta) {
10378
10433
  releaseTierBitmaps(meta.id, tier, fullBitmaps);
10379
10434
  if (lowTier && lowTier !== tier && lowRef.bitmaps) releaseTierBitmaps(meta.id, lowTier, lowRef.bitmaps);
10380
10435
  }
10381
- async function loadSkybox(scene, renderer, ctx) {
10436
+ async function loadSkybox(scene, renderer, ctx, disposed = () => false) {
10382
10437
  if (ctx?.layers.skipped.has("stars")) return;
10383
10438
  try {
10384
10439
  const meta = await fetchMetadata();
@@ -10390,7 +10445,7 @@ async function loadSkybox(scene, renderer, ctx) {
10390
10445
  attribution: meta.skybox.attribution,
10391
10446
  description: meta.skybox.description
10392
10447
  };
10393
- await loadFromMeta(scene, renderer, meta.skybox);
10448
+ await loadFromMeta(scene, renderer, meta.skybox, disposed);
10394
10449
  } catch (err) {
10395
10450
  console.warn("Failed to load skybox:", err);
10396
10451
  }
@@ -12393,6 +12448,7 @@ function bootThree(canvas, labelContainer, ctx) {
12393
12448
  //#region src/lib/scene/minor-body-position.ts
12394
12449
  function refreshMinorBodyPosition(body, jd, ctx) {
12395
12450
  const d = body.data;
12451
+ body.placedJd = jd;
12396
12452
  const isParabolic = d.q != null;
12397
12453
  let parentPos;
12398
12454
  if (d.parentId === "naif-0") parentPos = [
@@ -12431,6 +12487,47 @@ function refreshMinorBodyPosition(body, jd, ctx) {
12431
12487
  }
12432
12488
  body.positionUnknown = false;
12433
12489
  }
12490
+ function refreshMoonPosition(body, jd, ctx) {
12491
+ const d = body.data;
12492
+ body.placedJd = jd;
12493
+ body.positionUnknown = true;
12494
+ if (d.unplaceable) return;
12495
+ if (d.visibleFromDays !== void 0 && jd - 2451545 < d.visibleFromDays) return;
12496
+ const parent = ctx.getBody(d.parentId);
12497
+ if (!parent || !placeForRead(parent, jd, ctx)) return;
12498
+ const parentPos = parent.position;
12499
+ const offset = ctx.chebStore?.has(d.id) ? ctx.chebStore.positionScene(d.id, jd) : d.a === 0 ? [
12500
+ 0,
12501
+ 0,
12502
+ 0
12503
+ ] : orbitalElementsToPositionJD(d, jd);
12504
+ if (!offset) return;
12505
+ body.position[0] = parentPos[0] + offset[0];
12506
+ body.position[1] = parentPos[1] + offset[1];
12507
+ body.position[2] = parentPos[2] + offset[2];
12508
+ if (body.orbitCenter) {
12509
+ body.orbitCenter[0] = parentPos[0];
12510
+ body.orbitCenter[1] = parentPos[1];
12511
+ body.orbitCenter[2] = parentPos[2];
12512
+ }
12513
+ body.positionUnknown = false;
12514
+ }
12515
+ var HOST_READ_MS = 2e3;
12516
+ function placeForRead(body, jd, ctx) {
12517
+ const d = body.data;
12518
+ const stale = body.placedJd !== jd;
12519
+ if (d.orbitalSource === OrbitalSource.SPICE_PROBE) {
12520
+ const { hostRead } = ctx.bodies;
12521
+ if (!stale && !hostRead.has(d.id)) return !body.positionUnknown;
12522
+ if (!hostRead.has(d.id)) ctx.bodies.hostReadVersion++;
12523
+ hostRead.set(d.id, performance.now());
12524
+ if (stale) return false;
12525
+ } else if (stale) {
12526
+ if (d.objectType === ObjectType.MOON) refreshMoonPosition(body, jd, ctx);
12527
+ else if (!ctx.bodies.bodiesById.has(d.id) && !isSurfaceFeature(body)) refreshMinorBodyPosition(body, jd, ctx);
12528
+ }
12529
+ return !body.positionUnknown;
12530
+ }
12434
12531
 
12435
12532
  //#endregion
12436
12533
  //#region src/lib/fetch/attitude/parse.ts
@@ -16766,6 +16863,7 @@ function applyRadiiToMesh(bo, radii) {
16766
16863
  async function loadBodyTexture(bo, textureLoader, currentJd, scene, maxTextureSize, ctx) {
16767
16864
  if (bo.textureTier || bo.textureLoading) return;
16768
16865
  const detail = await fetchObjectDetail(bo.body.data.id, false);
16866
+ ctx?.bodies.orientationRead.add(bo.body.data.id);
16769
16867
  if (!detail.global) return;
16770
16868
  if (detail.global.orientation && !bo.body.orientation) applyBodyOrientation(bo, detail.global.orientation, ctx);
16771
16869
  if (detail.global.radii && bo.radiusScene > 0 && !bo.radiiApplied && !detail.global.displacement?.absolute_radius) applyRadiiToMesh(bo, detail.global.radii);
@@ -17934,6 +18032,7 @@ async function loadSystemData(barycenterId, bodyObjects, scene, textureLoader, c
17934
18032
  if (surface) ctx?.credits.registerImagery("surface", bodyId, barycenterId, surface);
17935
18033
  const bo = bodyObjects.get(bodyId);
17936
18034
  if (!bo?.mesh) continue;
18035
+ ctx?.bodies.orientationRead.add(bodyId);
17937
18036
  if (bodyMeta.orientation) {
17938
18037
  applyBodyOrientation(bo, bodyMeta.orientation, ctx, barycenterId);
17939
18038
  if (bodyMeta.nut_prec) {
@@ -19098,6 +19197,7 @@ var SMALL_BODY_MAX_OPACITY = EMPHASIS_MAX_OPACITY;
19098
19197
  var PointCloudSystem = class {
19099
19198
  orbitPool = new OrbitWorkerPool();
19100
19199
  pickRegistry = new PickRegistry();
19200
+ disposed = false;
19101
19201
  workerPoints = {
19102
19202
  asteroid: /* @__PURE__ */ new Map(),
19103
19203
  spacecraft: /* @__PURE__ */ new Map()
@@ -19283,7 +19383,7 @@ var PointCloudSystem = class {
19283
19383
  return true;
19284
19384
  }
19285
19385
  async rebuildMinorPass() {
19286
- if (this.ctx.bodies.dirtyAsteroidZones.size === 0 && this.ctx.bodies.dirtySpacecraftGroups.size === 0) return;
19386
+ if (this.disposed || this.ctx.bodies.dirtyAsteroidZones.size === 0 && this.ctx.bodies.dirtySpacecraftGroups.size === 0) return;
19287
19387
  const seedBasis = [
19288
19388
  this.basisPos[0],
19289
19389
  this.basisPos[1],
@@ -19303,6 +19403,7 @@ var PointCloudSystem = class {
19303
19403
  groups: [],
19304
19404
  baseWorker: 0
19305
19405
  };
19406
+ if (this.disposed) return;
19306
19407
  const cloudColor = bucket && groups.length > 0 ? bucket.cloudColor() : "#888888";
19307
19408
  for (let i = 0; i < k; i++) {
19308
19409
  const key = `${zone}#${i}`;
@@ -19330,6 +19431,7 @@ var PointCloudSystem = class {
19330
19431
  this.ctx.bodies.dirtySpacecraftGroups.delete(gid);
19331
19432
  drainedGroups.push(gid);
19332
19433
  const { buckets, baseWorker } = await partitionForWorkersSliced(gid, drawnSpacecraft(this.ctx.layers, gid, bucket ? Array.from(bucket.values()) : []), k);
19434
+ if (this.disposed) return;
19333
19435
  const skip = new Set(this.bodyObjects.keys());
19334
19436
  for (let i = 0; i < k; i++) {
19335
19437
  const key = `${gid}#${i}`;
@@ -19651,6 +19753,7 @@ var PointCloudSystem = class {
19651
19753
  return this.moonPoints;
19652
19754
  }
19653
19755
  dispose() {
19756
+ this.disposed = true;
19654
19757
  this.orbitPool.destroy();
19655
19758
  this.pickRegistry.clear();
19656
19759
  for (const kind of WORKER_CLOUD_KINDS) this.disposeClouds(this.workerPoints[kind]);
@@ -21271,18 +21374,32 @@ function updatePositions(params) {
21271
21374
  for (const body of ctx.bodies.bodiesById.values()) positionMap.set(body.data.id, body.position);
21272
21375
  for (const bo of bodyObjects.values()) if (!ctx.bodies.bodiesById.has(bo.body.data.id)) positionMap.set(bo.body.data.id, bo.body.position);
21273
21376
  const computed = /* @__PURE__ */ new Set();
21377
+ const placeFirst = (id) => {
21378
+ if (computed.has(id)) return;
21379
+ const body = ctx.bodies.bodiesById.get(id) ?? bodyObjects.get(id)?.body;
21380
+ if (body) computePosition(body);
21381
+ };
21382
+ const now = performance.now();
21383
+ const hostReads = (id) => {
21384
+ const at = ctx.bodies.hostRead.get(id);
21385
+ if (at === void 0) return false;
21386
+ if (now - at < 2e3) return true;
21387
+ ctx.bodies.hostRead.delete(id);
21388
+ return false;
21389
+ };
21274
21390
  const computePosition = (body) => {
21275
21391
  if (computed.has(body.data.id)) return;
21276
21392
  computed.add(body.data.id);
21277
21393
  const d = body.data;
21278
21394
  const bo = bodyObjects.get(d.id);
21279
- if (bo && frame !== void 0 && d.id !== focusedId && !bo.group.visible && !bo.trail?.visible && (d.orbitalSource === OrbitalSource.SPICE_PROBE || !isMajorBody(d.objectType))) {
21395
+ if (bo && frame !== void 0 && d.id !== focusedId && !hostReads(d.id) && !bo.group.visible && !bo.trail?.visible && (d.orbitalSource === OrbitalSource.SPICE_PROBE || !isMajorBody(d.objectType))) {
21280
21396
  bo.staggerSlot ??= nextStaggerSlot++ % HIDDEN_STRIDE;
21281
21397
  if (frame % HIDDEN_STRIDE !== bo.staggerSlot) return;
21282
21398
  }
21283
21399
  const hide = (notForNotice = false) => {
21284
21400
  if (bo) bo.outOfRange = true;
21285
21401
  body.positionUnknown = true;
21402
+ body.placedJd = jd;
21286
21403
  if (!notForNotice && d.id === focusedId) oorState.focusedOutOfRange = true;
21287
21404
  };
21288
21405
  if (d.unplaceable) {
@@ -21363,6 +21480,7 @@ function updatePositions(params) {
21363
21480
  const probeLanded = located.probe.landed;
21364
21481
  if (probeLanded && isLandedAt(located.probe, jd)) {
21365
21482
  const flyingFrameId = d.parentId;
21483
+ placeFirst(`naif-${probeLanded.bodyNaifId}`);
21366
21484
  const landedRender = renderLandedProbe(d, located.probe, probeLanded, jd, positionMap, ctx, bodyObjects);
21367
21485
  if (!landedRender) {
21368
21486
  hide();
@@ -21370,6 +21488,7 @@ function updatePositions(params) {
21370
21488
  }
21371
21489
  diagnostics.clear("probe-unavailable", d.id);
21372
21490
  body.positionUnknown = false;
21491
+ body.placedJd = jd;
21373
21492
  if (flyingFrameId !== d.parentId) body.trailBuffer?.clear();
21374
21493
  if (bo) {
21375
21494
  bo.outOfRange = false;
@@ -21449,10 +21568,7 @@ function updatePositions(params) {
21449
21568
  populateProbeTrailBuffer(buf, ctx.probeStore, ctx.chebStore ?? null, d.id, probeParentKey, jd);
21450
21569
  }
21451
21570
  if (probeParentChanged) d.parentId = probeParentKey;
21452
- if (!computed.has(probeParentKey)) {
21453
- const parentBody = ctx.bodies.bodiesById.get(probeParentKey) ?? bodyObjects.get(probeParentKey)?.body;
21454
- if (parentBody) computePosition(parentBody);
21455
- }
21571
+ placeFirst(probeParentKey);
21456
21572
  const probeParentPos = positionMap.get(probeParentKey);
21457
21573
  if (!probeParentPos) {
21458
21574
  hide();
@@ -21507,6 +21623,7 @@ function updatePositions(params) {
21507
21623
  }
21508
21624
  if (bo) bo.outOfRange = false;
21509
21625
  body.positionUnknown = false;
21626
+ body.placedJd = jd;
21510
21627
  body.position[0] = x;
21511
21628
  body.position[1] = y;
21512
21629
  body.position[2] = z;
@@ -23180,7 +23297,7 @@ function upgradeTargets(focus, ctx) {
23180
23297
  //#endregion
23181
23298
  //#region src/lib/scene/probe-coverage-watch.ts
23182
23299
  var STOP_INSET_JD = 1e-6;
23183
- function displayName(body) {
23300
+ function displayName$1(body) {
23184
23301
  return body.data.name ?? body.data.id;
23185
23302
  }
23186
23303
  var ProbeCoverageWatch = class {
@@ -23223,7 +23340,7 @@ var ProbeCoverageWatch = class {
23223
23340
  onHit(body, hitJd, forwardJd) {
23224
23341
  this.notices.notify({
23225
23342
  topic: "coverage-pause",
23226
- name: displayName(body),
23343
+ name: displayName$1(body),
23227
23344
  direction: hitJd >= forwardJd ? "forward" : "backward",
23228
23345
  jd: hitJd
23229
23346
  });
@@ -23299,10 +23416,14 @@ function rotateByQuaternion(q, x, y, z, out, at) {
23299
23416
  out[at + 1] = y + q.w * ty + (q.z * tx - q.x * tz);
23300
23417
  out[at + 2] = z + q.w * tz + (q.x * ty - q.y * tx);
23301
23418
  }
23419
+ function bodyCentre(body, ctx, jd) {
23420
+ return placeForRead(body, jd, ctx) ? body.position : null;
23421
+ }
23302
23422
  function resolveAnchor(anchor, ctx, jd) {
23303
23423
  const body = ctx.getBody(anchor.body);
23304
- if (!body) return null;
23305
- const [cx, cy, cz] = body.position;
23424
+ const centre = body && bodyCentre(body, ctx, jd);
23425
+ if (!centre) return null;
23426
+ const [cx, cy, cz] = centre;
23306
23427
  if (isSurface(anchor)) {
23307
23428
  const distance = kmToScene(effectiveRadiusKm(body.data) + (anchor.altitudeKm ?? 0));
23308
23429
  const [dx, dy, dz] = surfaceDirection(anchor.latitude, anchor.longitude);
@@ -23338,12 +23459,13 @@ function resolveAnchor(anchor, ctx, jd) {
23338
23459
  cz + oz
23339
23460
  ];
23340
23461
  }
23341
- function anchorNormal(anchor, world, ctx) {
23462
+ function anchorNormal(anchor, world, ctx, jd) {
23342
23463
  const body = ctx.getBody(anchor.body);
23343
- if (!body) return null;
23344
- const dx = world[0] - body.position[0];
23345
- const dy = world[1] - body.position[1];
23346
- const dz = world[2] - body.position[2];
23464
+ const centre = body && bodyCentre(body, ctx, jd);
23465
+ if (!centre) return null;
23466
+ const dx = world[0] - centre[0];
23467
+ const dy = world[1] - centre[1];
23468
+ const dz = world[2] - centre[2];
23347
23469
  const length = Math.hypot(dx, dy, dz);
23348
23470
  if (length === 0) return null;
23349
23471
  return [
@@ -23568,9 +23690,14 @@ var SceneRenderer = class SceneRenderer {
23568
23690
  focusDurationMs: FOCUS_DURATION_MS
23569
23691
  };
23570
23692
  lastUpdatedJd = NaN;
23693
+ get placedJd() {
23694
+ return this.lastUpdatedJd;
23695
+ }
23571
23696
  lastUpdatedSystemId = null;
23572
23697
  lastSeatConfigKey = null;
23573
23698
  lastProbeVersion = 0;
23699
+ disposed = false;
23700
+ lastHostReadVersion = 0;
23574
23701
  focusWasOutOfRange = false;
23575
23702
  _positionMapScratch = /* @__PURE__ */ new Map();
23576
23703
  frameIndex = 0;
@@ -23673,7 +23800,7 @@ var SceneRenderer = class SceneRenderer {
23673
23800
  this.orbitPreview = new OrbitPreviewOverlay(this.scene);
23674
23801
  this.orbitPreview.setLayer(SceneRenderer.MAP_LAYER);
23675
23802
  this.skyboxAdjuster.set(0, 0, 0);
23676
- loadSkybox(this.scene, this.renderer, ctx);
23803
+ loadSkybox(this.scene, this.renderer, ctx, () => this.disposed);
23677
23804
  const sunBody = ctx.bodies.majorBodies.find((b) => b.data.id === SUN_ID);
23678
23805
  const resident = ctx.getBody(initialView.id);
23679
23806
  const matchedBody = resident && !resident.positionUnknown ? resident : void 0;
@@ -24542,7 +24669,8 @@ var SceneRenderer = class SceneRenderer {
24542
24669
  const systemId = this.ctx.visibility.focusedSystemId;
24543
24670
  const seatKey = this.focusedSeatConfigKey();
24544
24671
  const probeVersion = this.ctx.probeStore?.version ?? 0;
24545
- if (this.clock.jd === this.lastUpdatedJd && systemId === this.lastUpdatedSystemId && seatKey === this.lastSeatConfigKey && probeVersion === this.lastProbeVersion) {
24672
+ const hostReadVersion = this.ctx.bodies.hostReadVersion;
24673
+ if (this.clock.jd === this.lastUpdatedJd && systemId === this.lastUpdatedSystemId && seatKey === this.lastSeatConfigKey && probeVersion === this.lastProbeVersion && hostReadVersion === this.lastHostReadVersion) {
24546
24674
  this.clock.seeked = false;
24547
24675
  return;
24548
24676
  }
@@ -24552,6 +24680,7 @@ var SceneRenderer = class SceneRenderer {
24552
24680
  this.lastUpdatedSystemId = systemId;
24553
24681
  this.lastSeatConfigKey = seatKey;
24554
24682
  this.lastProbeVersion = probeVersion;
24683
+ this.lastHostReadVersion = hostReadVersion;
24555
24684
  this.ctx.refreshTick(jdToDate(this.clock.jd));
24556
24685
  this.focusController.promotion.onSimTimeChanged();
24557
24686
  const result = updatePositions({
@@ -24887,7 +25016,7 @@ var SceneRenderer = class SceneRenderer {
24887
25016
  this.renderer.setPixelRatio(renderPixelRatio());
24888
25017
  this.composer.setPixelRatio(renderPixelRatio());
24889
25018
  this.resize(this.canvas.clientWidth, this.canvas.clientHeight);
24890
- loadSkybox(this.scene, this.renderer, this.ctx);
25019
+ loadSkybox(this.scene, this.renderer, this.ctx, () => this.disposed);
24891
25020
  this.resume();
24892
25021
  }
24893
25022
  recoverWorkersIfDead(timeoutMs) {
@@ -24918,6 +25047,7 @@ var SceneRenderer = class SceneRenderer {
24918
25047
  setTrailResolution(width, height);
24919
25048
  }
24920
25049
  dispose() {
25050
+ this.disposed = true;
24921
25051
  cancelAnimationFrame(this.rafId);
24922
25052
  this.pointerInteraction.detach();
24923
25053
  this.gpuPick.dispose();
@@ -25339,8 +25469,8 @@ var MarkerExtension = class {
25339
25469
  this.label.visible = this.occlude ? this.facesCamera(world, frame) : true;
25340
25470
  this.object.updateMatrixWorld(true);
25341
25471
  }
25342
- facesCamera(world, { camera, ctx }) {
25343
- const normal = anchorNormal(this.anchor, world, ctx);
25472
+ facesCamera(world, { camera, ctx, jd }) {
25473
+ const normal = anchorNormal(this.anchor, world, ctx, jd);
25344
25474
  if (!normal) return true;
25345
25475
  const { position } = this.label;
25346
25476
  const dx = camera.position.x - position.x;
@@ -26702,16 +26832,17 @@ var SurfaceShapeExtension = class {
26702
26832
  }
26703
26833
  update({ jd, basis, camera, ctx }) {
26704
26834
  const body = this.wanted && this.count >= 2 ? ctx.getBody(this.body) : void 0;
26705
- if (this.line) this.line.visible = body !== void 0;
26706
- if (this.fill) this.fill.visible = body !== void 0;
26707
- if (!body) return;
26835
+ const centre = body && bodyCentre(body, ctx, jd);
26836
+ if (this.line) this.line.visible = !!centre;
26837
+ if (this.fill) this.fill.visible = !!centre;
26838
+ if (!body || !centre) return;
26708
26839
  const radiusKm = effectiveRadiusKm(body.data);
26709
26840
  const scale = kmToScene(radiusKm + (this.altitudeKm ?? radiusKm * DEFAULT_LIFT));
26710
26841
  const q = body.orientation ? bodyQuaternion(body.orientation, jd, body.nutPrec) : null;
26711
26842
  const origin = [
26712
- body.position[0] - basis[0],
26713
- body.position[1] - basis[1],
26714
- body.position[2] - basis[2]
26843
+ centre[0] - basis[0],
26844
+ centre[1] - basis[1],
26845
+ centre[2] - basis[2]
26715
26846
  ];
26716
26847
  if (this.line) {
26717
26848
  this.place(this.dirs, this.count, q, scale, origin, this.scratch);
@@ -26941,6 +27072,50 @@ var MapObjectCollection = class {
26941
27072
  }
26942
27073
  };
26943
27074
 
27075
+ //#endregion
27076
+ //#region src/lib/scene/extensions/measure.ts
27077
+ function anchorOffsetKm(a, b, ctx, jd) {
27078
+ const from = resolveAnchor(a, ctx, jd);
27079
+ const to = resolveAnchor(b, ctx, jd);
27080
+ if (!from || !to) return null;
27081
+ return sceneToEcliptic([
27082
+ to[0] - from[0],
27083
+ to[1] - from[1],
27084
+ to[2] - from[2]
27085
+ ]);
27086
+ }
27087
+ function anchorDistanceKm(a, b, ctx, jd) {
27088
+ const offset = anchorOffsetKm(a, b, ctx, jd);
27089
+ return offset && Math.hypot(...offset);
27090
+ }
27091
+ function subsolarPoint(id, ctx, jd) {
27092
+ const body = ctx.getBody(id);
27093
+ const sun = ctx.getBody(SUN_ID);
27094
+ if (!body || !sun || body === sun) return null;
27095
+ if (!body.orientation && !ctx.bodies.orientationRead.has(id)) return null;
27096
+ const centre = bodyCentre(body, ctx, jd);
27097
+ if (!centre) return null;
27098
+ const turn = body.orientation ? bodyQuaternion(body.orientation, jd, body.nutPrec).toArray() : [
27099
+ 0,
27100
+ 0,
27101
+ 0,
27102
+ 1
27103
+ ];
27104
+ const { latitude, longitude, distance } = cartesianToSpherical([
27105
+ sun.position[0],
27106
+ sun.position[1],
27107
+ sun.position[2]
27108
+ ], [
27109
+ centre[0],
27110
+ centre[1],
27111
+ centre[2]
27112
+ ], turn);
27113
+ return distance > 0 ? {
27114
+ lon: longitude,
27115
+ lat: latitude
27116
+ } : null;
27117
+ }
27118
+
26944
27119
  //#endregion
26945
27120
  //#region src/lib/interaction/gesture.ts
26946
27121
  var GestureHandler = class {
@@ -27121,7 +27296,8 @@ var KEY_ROTATE_RAD = .05;
27121
27296
  var KEY_ZOOM_FACTOR = 1.15;
27122
27297
  var CONTEXT_LOST_PANEL_DELAY_MS = 2e3;
27123
27298
  var RESTORE_PING_TIMEOUT_MS = 4e3;
27124
- var onceTarget$1 = /* @__PURE__ */ new WeakMap();
27299
+ var mapsIn = /* @__PURE__ */ new WeakMap();
27300
+ var onceTarget$2 = /* @__PURE__ */ new WeakMap();
27125
27301
  var SpaceMap = class {
27126
27302
  ctx = new ContextManager();
27127
27303
  clock;
@@ -27222,14 +27398,14 @@ var SpaceMap = class {
27222
27398
  off();
27223
27399
  listener(...args);
27224
27400
  };
27225
- onceTarget$1.set(wrapped, listener);
27401
+ onceTarget$2.set(wrapped, listener);
27226
27402
  const off = this.on(event, wrapped);
27227
27403
  return off;
27228
27404
  }
27229
27405
  off(event, listener) {
27230
27406
  const set = this.listeners[event];
27231
27407
  set.delete(listener);
27232
- for (const held of set) if (onceTarget$1.get(held) === listener) set.delete(held);
27408
+ for (const held of set) if (onceTarget$2.get(held) === listener) set.delete(held);
27233
27409
  }
27234
27410
  emit(event, ...args) {
27235
27411
  for (const listener of this.listeners[event]) listener(...args);
@@ -27247,6 +27423,19 @@ var SpaceMap = class {
27247
27423
  getChildren(id) {
27248
27424
  return [...this.ctx.bodies.getChildren(id) ?? []];
27249
27425
  }
27426
+ get placedJd() {
27427
+ const jd = this.renderer?.placedJd;
27428
+ return jd === void 0 || Number.isNaN(jd) ? this.clock.jd : jd;
27429
+ }
27430
+ distanceKm(from, to) {
27431
+ return anchorDistanceKm(from, to, this.ctx, this.placedJd);
27432
+ }
27433
+ offsetKm(from, to) {
27434
+ return anchorOffsetKm(from, to, this.ctx, this.placedJd);
27435
+ }
27436
+ getSubsolarPoint(id) {
27437
+ return subsolarPoint(id, this.ctx, this.placedJd);
27438
+ }
27250
27439
  load(targetId = this.initialView.id) {
27251
27440
  return this.ctx.load(jdToDate(this.clock.jd), targetId);
27252
27441
  }
@@ -27278,6 +27467,7 @@ var SpaceMap = class {
27278
27467
  if (this.container) throw new Error("SpaceMap is already mounted");
27279
27468
  this.container = container;
27280
27469
  container.classList.add("sm-map");
27470
+ mapsIn.set(container, (mapsIn.get(container) ?? 0) + 1);
27281
27471
  this.controls = new ControlHost(this, container);
27282
27472
  const canvas = document.createElement("canvas");
27283
27473
  canvas.className = "sm-map__canvas";
@@ -27357,7 +27547,9 @@ var SpaceMap = class {
27357
27547
  this.renderer = null;
27358
27548
  canvas.remove();
27359
27549
  this.labelLayer?.remove();
27360
- container.classList.remove("sm-map");
27550
+ const left = (mapsIn.get(container) ?? 1) - 1;
27551
+ mapsIn.set(container, left);
27552
+ if (left === 0) container.classList.remove("sm-map");
27361
27553
  this.container = this.canvas = this.labelLayer = null;
27362
27554
  }
27363
27555
  watch(renderer) {
@@ -27778,7 +27970,7 @@ var SpaceMap = class {
27778
27970
 
27779
27971
  //#endregion
27780
27972
  //#region src/lib/flatmap/layers.ts
27781
- var SVG_NS = "http://www.w3.org/2000/svg";
27973
+ var SVG_NS$1 = "http://www.w3.org/2000/svg";
27782
27974
  function layerInfo(layer) {
27783
27975
  return {
27784
27976
  id: layer.id,
@@ -27796,7 +27988,7 @@ function creditOf(bundle) {
27796
27988
  };
27797
27989
  }
27798
27990
  function pathElement(d, className) {
27799
- const path = document.createElementNS(SVG_NS, "path");
27991
+ const path = document.createElementNS(SVG_NS$1, "path");
27800
27992
  path.setAttribute("d", d);
27801
27993
  path.setAttribute("class", className);
27802
27994
  return path;
@@ -27815,7 +28007,7 @@ function graticuleLayer(label, stepDeg = 30) {
27815
28007
  if (viewport.projection.azimuthal) {
27816
28008
  const centre = viewport.toScreen(0, 0);
27817
28009
  const edge = viewport.toScreen(viewport.projection.extent.maxX, 0);
27818
- const circle = document.createElementNS(SVG_NS, "circle");
28010
+ const circle = document.createElementNS(SVG_NS$1, "circle");
27819
28011
  circle.setAttribute("cx", String(centre[0]));
27820
28012
  circle.setAttribute("cy", String(centre[1]));
27821
28013
  circle.setAttribute("r", String(Math.abs(edge[0] - centre[0])));
@@ -27852,12 +28044,12 @@ function nomenclatureLayer(bodyId, label) {
27852
28044
  for (const shift of shifts) {
27853
28045
  const x = at[0] + shift;
27854
28046
  if (x < 0 || x > viewport.width) continue;
27855
- const dot = document.createElementNS(SVG_NS, "circle");
28047
+ const dot = document.createElementNS(SVG_NS$1, "circle");
27856
28048
  dot.setAttribute("cx", x.toFixed(1));
27857
28049
  dot.setAttribute("cy", at[1].toFixed(1));
27858
28050
  dot.setAttribute("r", "1.6");
27859
28051
  dot.setAttribute("class", "sm-flat__feature-dot");
27860
- const text = document.createElementNS(SVG_NS, "text");
28052
+ const text = document.createElementNS(SVG_NS$1, "text");
27861
28053
  text.setAttribute("x", (x + 4).toFixed(1));
27862
28054
  text.setAttribute("y", (at[1] + 3).toFixed(1));
27863
28055
  text.setAttribute("class", "sm-flat__feature-label");
@@ -27904,12 +28096,12 @@ var PathDrawing = class {
27904
28096
  this.onRemove = onRemove;
27905
28097
  this.onChange = onChange;
27906
28098
  this.points = points;
27907
- this.node = document.createElementNS(SVG_NS, "g");
28099
+ this.node = document.createElementNS(SVG_NS$1, "g");
27908
28100
  applyStyle(this.node, style);
27909
28101
  bindEvents(this.node, style);
27910
- this.area = closed ? document.createElementNS(SVG_NS, "path") : null;
28102
+ this.area = closed ? document.createElementNS(SVG_NS$1, "path") : null;
27911
28103
  this.area?.setAttribute("stroke", "none");
27912
- this.edge = document.createElementNS(SVG_NS, "path");
28104
+ this.edge = document.createElementNS(SVG_NS$1, "path");
27913
28105
  this.edge.setAttribute("fill", "none");
27914
28106
  if (this.area) this.node.append(this.area);
27915
28107
  this.node.append(this.edge);
@@ -28230,27 +28422,33 @@ async function fetchJson(url) {
28230
28422
  return null;
28231
28423
  }
28232
28424
  }
28233
- async function fromBundle(bodyId) {
28234
- const meta = await fetchJson(versionedUrl(`/v1/textures/${bodyId}/metadata.json`, "textures"));
28235
- if (!meta) return {};
28236
- if (!textureAllowed(meta.distribution)) return {};
28237
- const tiers = Object.keys(meta.exports ?? {}).filter((k) => [
28238
- "low",
28239
- "medium",
28240
- "high"
28241
- ].includes(k));
28242
- if (tiers.length === 0) return {};
28243
- return { surface: {
28425
+ async function tiersOf(bundleId) {
28426
+ const found = await Promise.all(TIER_ORDER.map((tier) => fetch(versionedUrl(`/v1/textures/${bundleId}/${tier}.webp`, "textures"), { method: "HEAD" }).then((response) => response.ok).catch(() => false)));
28427
+ return TIER_ORDER.filter((_, i) => found[i]);
28428
+ }
28429
+ async function fromRecord(bodyId, record, tiers = tiersOf) {
28430
+ if (!record?.map_texture_available || !record.texture) return {};
28431
+ const surface = pickTexture([{
28432
+ ...record.texture,
28244
28433
  id: bodyId,
28245
- tiers,
28246
- source: String(meta.source ?? ""),
28247
- organisation: String(meta.organisation ?? ""),
28248
- type: String(meta.type ?? "cylindrical"),
28249
- attribution: meta.attribution,
28250
- description: meta.description,
28251
- license: meta.license,
28252
- distribution: meta.distribution
28253
- } };
28434
+ tiers: void 0
28435
+ }, ...record.alternates ?? []]);
28436
+ if (!surface) return {};
28437
+ const written = surface.tiers ?? await tiers(surface.id);
28438
+ if (written.length === 0) return {};
28439
+ const { frames, ...bundle } = surface;
28440
+ const radii = record.radii;
28441
+ return {
28442
+ surface: {
28443
+ ...bundle,
28444
+ tiers: written,
28445
+ monthlyFrames: frames
28446
+ },
28447
+ radiusKm: radii ? (radii.a + radii.b + radii.c) / 3 : void 0
28448
+ };
28449
+ }
28450
+ async function fromBundle(bodyId) {
28451
+ return fromRecord(bodyId, (await fetchObjectDetail(bodyId, false).catch(() => null))?.global);
28254
28452
  }
28255
28453
  async function loadBodySources(bodyId) {
28256
28454
  const systemId = systemIdFor(bodyId);
@@ -28551,14 +28749,14 @@ var RASTER_LAYERS = [
28551
28749
  function isRaster(layer) {
28552
28750
  return layer.kind === "raster";
28553
28751
  }
28554
- var onceTarget = /* @__PURE__ */ new WeakMap();
28752
+ var onceTarget$1 = /* @__PURE__ */ new WeakMap();
28555
28753
  var FlatMap = class {
28556
28754
  container = null;
28557
28755
  root = document.createElement("div");
28558
28756
  canvas = document.createElement("canvas");
28559
- svg = document.createElementNS(SVG_NS, "svg");
28560
- layerGroup = document.createElementNS(SVG_NS, "g");
28561
- shapeGroup = document.createElementNS(SVG_NS, "g");
28757
+ svg = document.createElementNS(SVG_NS$1, "svg");
28758
+ layerGroup = document.createElementNS(SVG_NS$1, "g");
28759
+ shapeGroup = document.createElementNS(SVG_NS$1, "g");
28562
28760
  markerLayer = document.createElement("div");
28563
28761
  ctx;
28564
28762
  overlay;
@@ -28929,7 +29127,7 @@ var FlatMap = class {
28929
29127
  off();
28930
29128
  listener(...args);
28931
29129
  });
28932
- onceTarget.set(wrapped, listener);
29130
+ onceTarget$1.set(wrapped, listener);
28933
29131
  const off = this.on(event, wrapped);
28934
29132
  return off;
28935
29133
  }
@@ -28937,7 +29135,7 @@ var FlatMap = class {
28937
29135
  const set = this.listeners.get(event);
28938
29136
  if (!set) return;
28939
29137
  set.delete(listener);
28940
- for (const held of set) if (onceTarget.get(held) === listener) set.delete(held);
29138
+ for (const held of set) if (onceTarget$1.get(held) === listener) set.delete(held);
28941
29139
  }
28942
29140
  emit(event, ...args) {
28943
29141
  const set = this.listeners.get(event);
@@ -29141,7 +29339,7 @@ var FlatMap = class {
29141
29339
  this.layerGroup.replaceChildren();
29142
29340
  for (const layer of this.layerList) {
29143
29341
  if (isRaster(layer) || !layer.visible) continue;
29144
- const group = document.createElementNS(SVG_NS, "g");
29342
+ const group = document.createElementNS(SVG_NS$1, "g");
29145
29343
  group.setAttribute("data-layer", layer.id);
29146
29344
  layer.render(viewport, group);
29147
29345
  this.layerGroup.append(group);
@@ -30074,6 +30272,1455 @@ var PanoramaView = class {
30074
30272
  };
30075
30273
  };
30076
30274
 
30275
+ //#endregion
30276
+ //#region src/lib/fetch/groups/planetary-systems-map.ts
30277
+ var fetchPlanetarySystemsMap = memoizedGzJson(() => `${dataBase()}/v1/groups/__planetary_systems_map__.json.gz`, { error: (res) => `Failed to fetch planetary systems map: ${res.status}` });
30278
+
30279
+ //#endregion
30280
+ //#region src/lib/fetch/groups/solar-system-map.ts
30281
+ var fetchSolarSystemMap = memoizedGzJson(() => `${dataBase()}/v1/groups/__solar_system_map__.json.gz`, { error: (res) => `Failed to fetch solar system map: ${res.status}` });
30282
+
30283
+ //#endregion
30284
+ //#region src/lib/charts/scrub.ts
30285
+ var DRAG_SLOP = 8;
30286
+ function createScrub({ onScrub, onEnd }) {
30287
+ let downX = null;
30288
+ let downY = 0;
30289
+ let scrubbing = false;
30290
+ function end() {
30291
+ if (scrubbing) onEnd();
30292
+ downX = null;
30293
+ scrubbing = false;
30294
+ }
30295
+ return {
30296
+ onpointerdown(e) {
30297
+ if (e.pointerType === "mouse") return;
30298
+ downX = e.clientX;
30299
+ downY = e.clientY;
30300
+ scrubbing = false;
30301
+ },
30302
+ onpointermove(e) {
30303
+ if (e.pointerType === "mouse" || downX === null) return;
30304
+ if (!scrubbing && Math.hypot(e.clientX - downX, e.clientY - downY) < 8) return;
30305
+ scrubbing = true;
30306
+ onScrub(e.clientX, e.clientY);
30307
+ },
30308
+ onpointerup: end,
30309
+ onpointercancel: end,
30310
+ onpointerleave: end
30311
+ };
30312
+ }
30313
+
30314
+ //#endregion
30315
+ //#region src/lib/systemmap/layout.ts
30316
+ var VIEW_H = 240;
30317
+ var X_LEFT = 44;
30318
+ var X_RIGHT = 700;
30319
+ var CY = 120;
30320
+ var MIN_R = 2.2;
30321
+ var MAX_OFFSET = 84;
30322
+ var PRIMARY_LIMB_X = 44 - 4;
30323
+ var MOON_GAP = 4;
30324
+ var MOON_SPACING = 3;
30325
+ var HIT_MARGIN = 16;
30326
+ var INSIDE = 1e4;
30327
+ var MOON_ZONE_W = 34;
30328
+ var MOON_ZONE_PAD = 7;
30329
+ var RING_FORESHORTEN = .42;
30330
+ var BAND_Y = 26;
30331
+ var BAND_H = 240 - 60;
30332
+ var BAND_MIN_HIT = 30;
30333
+ function minDistance(cx, cy, placed) {
30334
+ let min = Infinity;
30335
+ for (const p of placed) min = Math.min(min, Math.hypot(p.cx - cx, p.cy - cy));
30336
+ return min;
30337
+ }
30338
+ function foldTilt(tiltDeg) {
30339
+ return tiltDeg > 90 ? 180 - tiltDeg : tiltDeg;
30340
+ }
30341
+ function layout(model) {
30342
+ const logLo = Math.log10(model.domain[0]);
30343
+ const logSpan = Math.log10(model.domain[1]) - logLo;
30344
+ const xOf = (km) => {
30345
+ return 44 + (Math.log10(Math.max(km / model.unitKm, 1e-9)) - logLo) / logSpan * (700 - 44);
30346
+ };
30347
+ const bodies = [];
30348
+ for (const b of [...model.bodies].sort((a, b) => a.aKm - b.aKm)) {
30349
+ const cx = xOf(b.aKm);
30350
+ const r = Math.max(MIN_R, b.radiusKm * model.pxPerKm);
30351
+ let cy = 120;
30352
+ if (b.tiltDeg > 0) {
30353
+ const offset = Math.min(foldTilt(b.tiltDeg) * model.pxPerDeg, MAX_OFFSET);
30354
+ const up = 120 - offset;
30355
+ const down = 120 + offset;
30356
+ cy = minDistance(cx, up, bodies) >= minDistance(cx, down, bodies) ? up : down;
30357
+ }
30358
+ bodies.push({
30359
+ ...b,
30360
+ cx,
30361
+ cy,
30362
+ r
30363
+ });
30364
+ }
30365
+ const moonZones = [];
30366
+ for (const parent of bodies) {
30367
+ if (!parent.satellites?.length) continue;
30368
+ const ringTop = parent.rings ? parent.r * parent.rings.outer * RING_FORESHORTEN : 0;
30369
+ let y = parent.cy - Math.max(parent.r, ringTop) - MOON_GAP;
30370
+ const moons = [];
30371
+ for (const s of [...parent.satellites].sort((a, b) => b.radiusKm - a.radiusKm)) {
30372
+ const r = Math.max(MIN_R, s.radiusKm * model.pxPerKm);
30373
+ y -= r;
30374
+ moons.push({
30375
+ ...s,
30376
+ cx: parent.cx,
30377
+ cy: y,
30378
+ r,
30379
+ hitY: 0,
30380
+ hitHeight: 0
30381
+ });
30382
+ y -= r + MOON_SPACING;
30383
+ }
30384
+ const top = Math.min(...moons.map((mn) => mn.cy - mn.r)) - MOON_ZONE_PAD;
30385
+ const bottom = Math.max(...moons.map((mn) => mn.cy + mn.r)) + MOON_ZONE_PAD;
30386
+ moons.forEach((mn, idx) => {
30387
+ const below = idx === 0 ? bottom : (moons[idx - 1].cy + mn.cy) / 2;
30388
+ const above = idx === moons.length - 1 ? top : (moons[idx + 1].cy + mn.cy) / 2;
30389
+ mn.hitY = above;
30390
+ mn.hitHeight = below - above;
30391
+ });
30392
+ moonZones.push({
30393
+ parent,
30394
+ moons,
30395
+ x: parent.cx - MOON_ZONE_W / 2,
30396
+ y: top,
30397
+ width: MOON_ZONE_W,
30398
+ height: bottom - top,
30399
+ perMoon: !parent.grouped && !parent.satellitesLink && moons.some((mn) => mn.link)
30400
+ });
30401
+ }
30402
+ const bands = model.bands.map((b) => {
30403
+ const x = xOf(b.innerKm);
30404
+ const width = xOf(b.outerKm) - x;
30405
+ const hitWidth = Math.max(width, BAND_MIN_HIT);
30406
+ return {
30407
+ ...b,
30408
+ x,
30409
+ width,
30410
+ hitX: x + (width - hitWidth) / 2,
30411
+ hitWidth
30412
+ };
30413
+ }).sort((a, b) => b.width - a.width);
30414
+ let cloudPath = "";
30415
+ model.cloud?.points.forEach((p, idx) => {
30416
+ const aUnits = p.aKm / model.unitKm;
30417
+ if (aUnits < model.domain[0] || aUnits > model.domain[1]) return;
30418
+ const offset = Math.min(Math.max(foldTilt(p.tiltDeg), 0) * model.pxPerDeg, MAX_OFFSET);
30419
+ const cy = 120 + (idx % 2 === 0 ? -offset : offset);
30420
+ cloudPath += `M${xOf(p.aKm).toFixed(1)} ${cy.toFixed(1)}h0.01`;
30421
+ });
30422
+ const primaryR = model.primary.radiusKm * model.pxPerKm;
30423
+ return {
30424
+ bodies,
30425
+ moonZones,
30426
+ bands,
30427
+ cloudPath,
30428
+ primaryR,
30429
+ primaryCx: PRIMARY_LIMB_X - primaryR,
30430
+ xOf
30431
+ };
30432
+ }
30433
+ function inside(z, vx, vy) {
30434
+ return vx >= z.x && vx <= z.x + z.width && vy >= z.y && vy <= z.y + z.height;
30435
+ }
30436
+ function nearestBody(l, vx, vy, reach) {
30437
+ for (const pickable of [true, false]) {
30438
+ let nearest = null;
30439
+ let gap = reach;
30440
+ for (const b of l.bodies) {
30441
+ if (!!b.link !== pickable) continue;
30442
+ const centre = Math.hypot(vx - b.cx, vy - b.cy);
30443
+ const edge = centre <= b.r ? centre - INSIDE : centre - b.r;
30444
+ if (edge > gap) continue;
30445
+ nearest = b;
30446
+ gap = edge;
30447
+ }
30448
+ if (nearest) return nearest;
30449
+ }
30450
+ return null;
30451
+ }
30452
+ function hitTest(l, vx, vy) {
30453
+ const on = nearestBody(l, vx, vy, 0);
30454
+ if (on) return {
30455
+ kind: "body",
30456
+ id: on.id
30457
+ };
30458
+ for (const z of l.moonZones) {
30459
+ if (!inside(z, vx, vy)) continue;
30460
+ if (z.parent.grouped) return {
30461
+ kind: "body",
30462
+ id: z.parent.id
30463
+ };
30464
+ if (!z.perMoon) return {
30465
+ kind: "stack",
30466
+ id: z.parent.id
30467
+ };
30468
+ const moon = z.moons.find((mn) => vy >= mn.hitY && vy <= mn.hitY + mn.hitHeight);
30469
+ if (moon) return {
30470
+ kind: "moon",
30471
+ parent: z.parent.id,
30472
+ id: moon.id
30473
+ };
30474
+ }
30475
+ const near = nearestBody(l, vx, vy, 16);
30476
+ if (near) return {
30477
+ kind: "body",
30478
+ id: near.id
30479
+ };
30480
+ for (let i = l.bands.length - 1; i >= 0; i--) {
30481
+ const band = l.bands[i];
30482
+ if (vx >= band.hitX && vx <= band.hitX + band.hitWidth && vy >= 26 && vy <= 26 + 180) return {
30483
+ kind: "band",
30484
+ key: band.key
30485
+ };
30486
+ }
30487
+ if (Math.hypot(vx - l.primaryCx, vy - 120) <= l.primaryR) return { kind: "primary" };
30488
+ return null;
30489
+ }
30490
+ function tipFor(model, l, hover) {
30491
+ if (!hover) return null;
30492
+ const { text } = model;
30493
+ switch (hover.kind) {
30494
+ case "primary": return {
30495
+ cx: Math.max(44, l.primaryCx),
30496
+ cy: 120 - Math.min(l.primaryR, 120 - 40),
30497
+ title: model.primary.name,
30498
+ sub: text.primary
30499
+ };
30500
+ case "body": {
30501
+ const b = l.bodies.find((p) => p.id === hover.id);
30502
+ if (!b) return null;
30503
+ const parts = [text.distance(b.aKm)];
30504
+ if (b.tiltDeg > 90) parts.push(text.retrograde);
30505
+ if (b.grouped && b.satellites?.length) parts.push(text.moons(b.satelliteCount ?? b.satellites.length));
30506
+ return {
30507
+ cx: b.cx,
30508
+ cy: b.cy - b.r - 4,
30509
+ title: b.name,
30510
+ sub: parts.join(" · ")
30511
+ };
30512
+ }
30513
+ case "stack": {
30514
+ const zone = l.moonZones.find((z) => z.parent.id === hover.id);
30515
+ if (!zone) return null;
30516
+ const cx = zone.x + zone.width / 2;
30517
+ return zone.parent.satellitesTab ? {
30518
+ cx,
30519
+ cy: zone.y,
30520
+ title: zone.parent.name,
30521
+ sub: text.moons(zone.parent.satelliteCount ?? zone.moons.length)
30522
+ } : {
30523
+ cx,
30524
+ cy: zone.y,
30525
+ title: zone.moons[0].name,
30526
+ sub: zone.parent.name
30527
+ };
30528
+ }
30529
+ case "moon": {
30530
+ const zone = l.moonZones.find((z) => z.parent.id === hover.parent);
30531
+ const moon = zone?.moons.find((mn) => mn.id === hover.id);
30532
+ if (!zone || !moon) return null;
30533
+ return {
30534
+ cx: moon.cx,
30535
+ cy: moon.cy - moon.r - 4,
30536
+ title: moon.name,
30537
+ sub: zone.parent.name
30538
+ };
30539
+ }
30540
+ case "band": {
30541
+ const band = l.bands.find((bd) => bd.key === hover.key);
30542
+ if (!band) return null;
30543
+ return {
30544
+ cx: band.x + band.width / 2,
30545
+ cy: 44,
30546
+ title: band.name ?? band.label,
30547
+ sub: band.sub ?? `${text.distance(band.innerKm)}–${text.distance(band.outerKm)}`
30548
+ };
30549
+ }
30550
+ }
30551
+ }
30552
+
30553
+ //#endregion
30554
+ //#region src/lib/systemmap/chart.ts
30555
+ var SVG_NS = "http://www.w3.org/2000/svg";
30556
+ var PRIMARY_RIM = 10;
30557
+ var RING_TILT = -18;
30558
+ var CLOUD_R = 1.2;
30559
+ var TIP_H = 28;
30560
+ var TIP_MARGIN = 6;
30561
+ var LABEL_MARGIN = 4;
30562
+ var LABEL_SIZE = 18;
30563
+ var LABEL_MIN_SIZE = 12;
30564
+ var LABEL_MAX_W = 190;
30565
+ function svg(tag, attrs = {}, ...children) {
30566
+ const el = document.createElementNS(SVG_NS, tag);
30567
+ for (const [name, value] of Object.entries(attrs)) if (value !== void 0) el.setAttribute(name, String(value));
30568
+ for (const child of children) if (child) el.append(child);
30569
+ return el;
30570
+ }
30571
+ function sameHover(a, b) {
30572
+ return JSON.stringify(a) === JSON.stringify(b);
30573
+ }
30574
+ function linksOf(model, l) {
30575
+ const links = /* @__PURE__ */ new Map();
30576
+ const add = (hover, link) => {
30577
+ if (link?.href || link?.onclick) links.set(JSON.stringify(hover), link);
30578
+ };
30579
+ for (const band of l.bands) add({
30580
+ kind: "band",
30581
+ key: band.key
30582
+ }, band);
30583
+ add({ kind: "primary" }, model.primary.link);
30584
+ for (const b of l.bodies) add({
30585
+ kind: "body",
30586
+ id: b.id
30587
+ }, b.link);
30588
+ for (const z of l.moonZones) {
30589
+ if (z.parent.grouped) continue;
30590
+ if (!z.perMoon) add({
30591
+ kind: "stack",
30592
+ id: z.parent.id
30593
+ }, z.parent.satellitesLink);
30594
+ else for (const mn of z.moons) add({
30595
+ kind: "moon",
30596
+ parent: z.parent.id,
30597
+ id: mn.id
30598
+ }, mn.link);
30599
+ }
30600
+ return links;
30601
+ }
30602
+ var nextUid = 0;
30603
+ var SystemMapChart = class {
30604
+ root = document.createElement("div");
30605
+ svg = svg("svg");
30606
+ tip = document.createElement("div");
30607
+ tipTitle = document.createElement("span");
30608
+ tipSub = document.createElement("span");
30609
+ uid = `sm-system-map-${nextUid++}`;
30610
+ resize = new ResizeObserver(() => {
30611
+ this.fitLabels();
30612
+ this.placeTip();
30613
+ });
30614
+ model = null;
30615
+ placed = null;
30616
+ background = false;
30617
+ hover = null;
30618
+ highlights = /* @__PURE__ */ new Map();
30619
+ targets = /* @__PURE__ */ new Map();
30620
+ links = /* @__PURE__ */ new Map();
30621
+ shape = "";
30622
+ labels = [];
30623
+ constructor() {
30624
+ this.tip.className = "sm-system-map__tip";
30625
+ this.tipTitle.className = "sm-system-map__tip-title";
30626
+ this.tipSub.className = "sm-system-map__tip-sub";
30627
+ this.tip.append(this.tipTitle, this.tipSub);
30628
+ this.tip.hidden = true;
30629
+ this.root.append(this.svg, this.tip);
30630
+ this.resize.observe(this.root);
30631
+ const scrub = createScrub({
30632
+ onScrub: (x, y) => this.setHover(this.hitAt(x, y)),
30633
+ onEnd: () => this.setHover(null)
30634
+ });
30635
+ this.svg.addEventListener("pointerdown", scrub.onpointerdown);
30636
+ this.svg.addEventListener("pointermove", scrub.onpointermove);
30637
+ this.svg.addEventListener("pointerup", scrub.onpointerup);
30638
+ this.svg.addEventListener("pointercancel", scrub.onpointercancel);
30639
+ this.svg.addEventListener("pointerleave", scrub.onpointerleave);
30640
+ let at = [0, 0];
30641
+ const track = (e) => {
30642
+ at = [e.clientX, e.clientY];
30643
+ };
30644
+ this.svg.addEventListener("pointerdown", track);
30645
+ this.svg.addEventListener("pointerup", track);
30646
+ this.svg.addEventListener("pointermove", (e) => {
30647
+ track(e);
30648
+ if (e.pointerType === "mouse") this.setHover(this.hitAt(e.clientX, e.clientY));
30649
+ });
30650
+ this.svg.addEventListener("pointerleave", (e) => {
30651
+ if (e.pointerType === "mouse") this.setHover(null);
30652
+ });
30653
+ this.svg.addEventListener("click", (e) => {
30654
+ if (e.detail === 0) return;
30655
+ const hit = Math.abs(at[0] - e.clientX) < 1 && Math.abs(at[1] - e.clientY) < 1 ? this.hitAt(...at) : this.hitAt(e.clientX, e.clientY);
30656
+ if (!hit) return;
30657
+ const meant = this.targets.get(JSON.stringify(hit));
30658
+ if (meant?.contains(e.target)) return;
30659
+ e.stopPropagation();
30660
+ e.preventDefault();
30661
+ meant?.dispatchEvent(new MouseEvent("click", {
30662
+ bubbles: true,
30663
+ cancelable: true,
30664
+ ctrlKey: e.ctrlKey,
30665
+ metaKey: e.metaKey,
30666
+ shiftKey: e.shiftKey,
30667
+ altKey: e.altKey,
30668
+ button: e.button
30669
+ }));
30670
+ }, true);
30671
+ }
30672
+ destroy() {
30673
+ this.resize.disconnect();
30674
+ this.root.remove();
30675
+ }
30676
+ update(model, options) {
30677
+ this.model = model;
30678
+ this.placed = layout(model);
30679
+ const links = linksOf(model, this.placed);
30680
+ const shape = JSON.stringify([
30681
+ JSON.stringify(model, (key, value) => key === "href" ? void 0 : value),
30682
+ options,
30683
+ [...links].map(([key, link]) => [
30684
+ key,
30685
+ !!link.href,
30686
+ !!link.onclick
30687
+ ])
30688
+ ]);
30689
+ this.links = links;
30690
+ if (shape === this.shape) {
30691
+ for (const [key, el] of this.targets) {
30692
+ const href = links.get(key)?.href;
30693
+ if (href) el.setAttribute("href", href);
30694
+ }
30695
+ return;
30696
+ }
30697
+ this.shape = shape;
30698
+ this.background = options.variant === "background";
30699
+ this.hover = null;
30700
+ this.root.className = this.background ? "sm-system-map sm-system-map--background" : "sm-system-map";
30701
+ this.draw(model, this.placed, options.ariaLabel);
30702
+ this.fitLabels();
30703
+ this.placeTip();
30704
+ }
30705
+ draw(model, l, ariaLabel) {
30706
+ const bg = this.background;
30707
+ this.highlights = /* @__PURE__ */ new Map();
30708
+ this.targets = /* @__PURE__ */ new Map();
30709
+ this.labels = [];
30710
+ const root = this.svg;
30711
+ root.replaceChildren();
30712
+ root.setAttribute("viewBox", bg && model.backgroundView || `0 0 ${720} ${240}`);
30713
+ root.setAttribute("preserveAspectRatio", bg ? model.backgroundFit === "fit" ? "xMidYMid meet" : model.backgroundView ? "xMinYMid slice" : "xMidYMid slice" : "xMidYMid meet");
30714
+ root.setAttribute("class", "sm-system-map__svg");
30715
+ root.setAttribute("role", "group");
30716
+ root.setAttribute("aria-label", ariaLabel);
30717
+ root.setAttribute("data-vaul-no-drag", "");
30718
+ const stop = (offset, opacity) => svg("stop", {
30719
+ offset,
30720
+ "stop-color": model.primary.color,
30721
+ "stop-opacity": opacity
30722
+ });
30723
+ root.append(svg("defs", {}, svg("radialGradient", {
30724
+ id: `${this.uid}-primary`,
30725
+ gradientUnits: "userSpaceOnUse",
30726
+ cx: l.primaryCx,
30727
+ cy: 120,
30728
+ r: l.primaryR
30729
+ }, stop("0%", 1), stop(Math.max(0, 1 - PRIMARY_RIM / l.primaryR), .85), stop("100%", .35)), svg("pattern", {
30730
+ id: `${this.uid}-band`,
30731
+ width: 9,
30732
+ height: 9,
30733
+ patternUnits: "userSpaceOnUse"
30734
+ }, svg("circle", {
30735
+ cx: 2,
30736
+ cy: 2,
30737
+ r: .9,
30738
+ fill: "currentColor",
30739
+ opacity: .55
30740
+ }), svg("circle", {
30741
+ cx: 6.5,
30742
+ cy: 6,
30743
+ r: .9,
30744
+ fill: "currentColor",
30745
+ opacity: .55
30746
+ }))));
30747
+ if (!bg) this.drawAxis(model, l);
30748
+ if (l.cloudPath) root.append(svg("path", {
30749
+ d: l.cloudPath,
30750
+ stroke: model.cloud?.color,
30751
+ "stroke-width": CLOUD_R * 2,
30752
+ "stroke-linecap": "round",
30753
+ fill: "none",
30754
+ opacity: .45,
30755
+ "pointer-events": "none"
30756
+ }));
30757
+ for (const band of l.bands) {
30758
+ const fill = svg("rect", {
30759
+ x: band.x,
30760
+ y: 26,
30761
+ width: band.width,
30762
+ height: 180,
30763
+ fill: `url(#${this.uid}-band)`,
30764
+ class: `sm-system-map__band sm-system-map__band--${band.tone}`
30765
+ });
30766
+ if (bg) {
30767
+ root.append(fill);
30768
+ continue;
30769
+ }
30770
+ const label = svg("text", {
30771
+ x: band.x + band.width / 2,
30772
+ y: 240 - 8,
30773
+ "text-anchor": "middle",
30774
+ "font-size": LABEL_SIZE,
30775
+ class: `sm-system-map__band-label sm-system-map__band-label--${band.tone}`
30776
+ });
30777
+ label.textContent = band.label;
30778
+ this.labels.push(label);
30779
+ root.append(this.target(band, band.name ?? band.label, {
30780
+ kind: "band",
30781
+ key: band.key
30782
+ }, fill, svg("rect", {
30783
+ x: band.hitX,
30784
+ y: 26,
30785
+ width: band.hitWidth,
30786
+ height: 180,
30787
+ fill: "transparent"
30788
+ }), label));
30789
+ }
30790
+ const disc = svg("circle", {
30791
+ cx: l.primaryCx,
30792
+ cy: 120,
30793
+ r: l.primaryR,
30794
+ fill: `url(#${this.uid}-primary)`
30795
+ });
30796
+ root.append(bg ? disc : this.target(model.primary.link, model.primary.name, { kind: "primary" }, disc));
30797
+ for (const b of l.bodies) {
30798
+ if (!b.rings) continue;
30799
+ const mid = b.r * (b.rings.inner + b.rings.outer) / 2;
30800
+ root.append(svg("ellipse", {
30801
+ cx: b.cx,
30802
+ cy: b.cy,
30803
+ rx: mid,
30804
+ ry: mid * RING_FORESHORTEN,
30805
+ transform: `rotate(${RING_TILT} ${b.cx} ${b.cy})`,
30806
+ fill: "none",
30807
+ stroke: "#d9c89f",
30808
+ "stroke-width": b.r * (b.rings.outer - b.rings.inner),
30809
+ opacity: .45
30810
+ }));
30811
+ }
30812
+ const zoneOf = new Map(l.moonZones.map((z) => [z.parent.id, z]));
30813
+ for (const b of l.bodies) root.append(this.body(b, zoneOf.get(b.id)));
30814
+ for (const z of l.moonZones) if (!z.parent.grouped) this.stack(z);
30815
+ }
30816
+ drawAxis(model, l) {
30817
+ for (const t of model.ticks) {
30818
+ const x = l.xOf(t * model.unitKm);
30819
+ const tick = svg("text", {
30820
+ x,
30821
+ y: 16,
30822
+ "text-anchor": "middle",
30823
+ "font-size": 16,
30824
+ fill: "currentColor",
30825
+ opacity: .85
30826
+ });
30827
+ tick.textContent = String(t);
30828
+ this.svg.append(svg("line", {
30829
+ x1: x,
30830
+ x2: x,
30831
+ y1: 20,
30832
+ y2: 240 - 28,
30833
+ stroke: "currentColor",
30834
+ "stroke-width": .5,
30835
+ opacity: .12
30836
+ }), tick);
30837
+ }
30838
+ const unit = svg("text", {
30839
+ x: 700,
30840
+ y: 16,
30841
+ "text-anchor": "end",
30842
+ "font-size": 14,
30843
+ fill: "currentColor",
30844
+ opacity: .65
30845
+ });
30846
+ unit.textContent = model.axisLabel;
30847
+ this.svg.append(unit);
30848
+ }
30849
+ body(b, zone) {
30850
+ const dot = b.reference ? svg("circle", {
30851
+ cx: b.cx,
30852
+ cy: b.cy,
30853
+ r: b.r + 1,
30854
+ fill: "none",
30855
+ stroke: b.color,
30856
+ "stroke-width": 1.2,
30857
+ class: "sm-system-map__reference"
30858
+ }) : svg("circle", {
30859
+ cx: b.cx,
30860
+ cy: b.cy,
30861
+ r: b.r,
30862
+ fill: b.color
30863
+ });
30864
+ if (this.background) {
30865
+ if (!zone || !b.grouped) return dot;
30866
+ return svg("g", {}, dot, ...this.moonDots(zone));
30867
+ }
30868
+ const hover = {
30869
+ kind: "body",
30870
+ id: b.id
30871
+ };
30872
+ const ring = this.highlight(hover, svg("circle", {
30873
+ cx: b.cx,
30874
+ cy: b.cy,
30875
+ r: b.r + 3.5,
30876
+ fill: "none",
30877
+ stroke: "currentColor",
30878
+ "stroke-width": 1,
30879
+ opacity: .8
30880
+ }));
30881
+ const hit = svg("circle", {
30882
+ cx: b.cx,
30883
+ cy: b.cy,
30884
+ r: b.r + 16,
30885
+ fill: "transparent"
30886
+ });
30887
+ if (!zone || !b.grouped) return this.target(b.link, b.name, hover, ring, dot, hit);
30888
+ return this.target(b.link, b.name, hover, this.highlight(hover, this.zoneWash(zone)), ring, dot, hit, ...this.moonDots(zone), this.zoneHit(zone));
30889
+ }
30890
+ stack(z) {
30891
+ if (this.background) {
30892
+ this.svg.append(...this.moonDots(z));
30893
+ return;
30894
+ }
30895
+ const p = z.parent;
30896
+ if (z.perMoon) {
30897
+ this.svg.append(...this.moonDots(z));
30898
+ for (const mn of z.moons) {
30899
+ const hover = {
30900
+ kind: "moon",
30901
+ parent: p.id,
30902
+ id: mn.id
30903
+ };
30904
+ this.svg.append(this.target(mn.link, mn.name, hover, this.highlight(hover, svg("circle", {
30905
+ cx: mn.cx,
30906
+ cy: mn.cy,
30907
+ r: mn.r + 2.5,
30908
+ fill: "none",
30909
+ stroke: "currentColor",
30910
+ "stroke-width": 1,
30911
+ opacity: .8
30912
+ })), svg("rect", {
30913
+ x: z.x,
30914
+ y: mn.hitY,
30915
+ width: z.width,
30916
+ height: mn.hitHeight,
30917
+ fill: "transparent"
30918
+ })));
30919
+ }
30920
+ return;
30921
+ }
30922
+ const hover = {
30923
+ kind: "stack",
30924
+ id: p.id
30925
+ };
30926
+ const first = z.moons[0];
30927
+ this.svg.append(this.highlight(hover, this.zoneWash(z)), ...this.moonDots(z), this.target(p.satellitesLink, p.satellitesTab ? `${p.name} · ${this.model.text.moons(p.satelliteCount ?? z.moons.length)}` : first.name, hover, this.zoneHit(z)));
30928
+ }
30929
+ moonDots(z) {
30930
+ return z.moons.map((mn) => svg("circle", {
30931
+ cx: mn.cx,
30932
+ cy: mn.cy,
30933
+ r: mn.r,
30934
+ fill: mn.color
30935
+ }));
30936
+ }
30937
+ zoneWash(z) {
30938
+ return svg("rect", {
30939
+ x: z.x,
30940
+ y: z.y,
30941
+ width: z.width,
30942
+ height: z.height,
30943
+ rx: 4,
30944
+ fill: "currentColor",
30945
+ opacity: .1,
30946
+ "pointer-events": "none"
30947
+ });
30948
+ }
30949
+ zoneHit(z) {
30950
+ return svg("rect", {
30951
+ x: z.x,
30952
+ y: z.y,
30953
+ width: z.width,
30954
+ height: z.height,
30955
+ fill: "transparent"
30956
+ });
30957
+ }
30958
+ highlight(hover, el) {
30959
+ const key = JSON.stringify(hover);
30960
+ const list = this.highlights.get(key);
30961
+ if (list) list.push(el);
30962
+ else this.highlights.set(key, [el]);
30963
+ el.style.display = "none";
30964
+ return el;
30965
+ }
30966
+ target(link, label, hover, ...children) {
30967
+ const linked = !!(link?.href || link?.onclick);
30968
+ const el = link?.href ? svg("a", {
30969
+ href: link.href,
30970
+ "aria-label": label
30971
+ }) : linked ? svg("g", {
30972
+ role: "button",
30973
+ tabindex: 0,
30974
+ "aria-label": label,
30975
+ class: "sm-system-map__button"
30976
+ }) : svg("g");
30977
+ el.append(...children);
30978
+ if (!linked) return el;
30979
+ const key = JSON.stringify(hover);
30980
+ this.targets.set(key, el);
30981
+ el.addEventListener("focus", () => this.setHover(hover));
30982
+ el.addEventListener("blur", () => {
30983
+ if (sameHover(this.hover, hover)) this.setHover(null);
30984
+ });
30985
+ el.addEventListener("click", (e) => this.links.get(key)?.onclick?.(e));
30986
+ if (!link?.href) el.addEventListener("keydown", (e) => {
30987
+ const key = e.key;
30988
+ if (key !== "Enter" && key !== " ") return;
30989
+ e.preventDefault();
30990
+ el.dispatchEvent(new MouseEvent("click", { bubbles: true }));
30991
+ });
30992
+ return el;
30993
+ }
30994
+ setHover(hover) {
30995
+ if (sameHover(this.hover, hover)) return;
30996
+ for (const el of this.highlights.get(JSON.stringify(this.hover)) ?? []) el.style.display = "none";
30997
+ this.targets.get(JSON.stringify(this.hover))?.removeAttribute("data-near");
30998
+ this.hover = hover;
30999
+ for (const el of this.highlights.get(JSON.stringify(hover)) ?? []) el.style.display = "";
31000
+ const near = this.targets.get(JSON.stringify(hover));
31001
+ near?.setAttribute("data-near", "");
31002
+ this.svg.classList.toggle("sm-system-map__svg--near", !!near);
31003
+ this.placeTip();
31004
+ }
31005
+ hitAt(clientX, clientY) {
31006
+ if (!this.placed || this.background) return null;
31007
+ const rect = this.svg.getBoundingClientRect();
31008
+ if (!rect.width || !rect.height) return null;
31009
+ const vx = (clientX - rect.left) / rect.width * 720;
31010
+ const vy = (clientY - rect.top) / rect.height * 240;
31011
+ return hitTest(this.placed, vx, vy);
31012
+ }
31013
+ fitLabels() {
31014
+ for (const label of this.labels) {
31015
+ let width = 0;
31016
+ label.setAttribute("font-size", String(LABEL_SIZE));
31017
+ try {
31018
+ width = label.getComputedTextLength();
31019
+ } catch {}
31020
+ if (!width) continue;
31021
+ if (width > LABEL_MAX_W) {
31022
+ const size = Math.max(LABEL_MIN_SIZE, LABEL_SIZE * LABEL_MAX_W / width);
31023
+ label.setAttribute("font-size", String(size));
31024
+ width *= size / LABEL_SIZE;
31025
+ }
31026
+ const centre = Number(label.dataset.centre ?? label.getAttribute("x"));
31027
+ label.dataset.centre = String(centre);
31028
+ const half = width / 2;
31029
+ const x = Math.min(Math.max(centre, half + LABEL_MARGIN), Math.max(half, 720 - half - LABEL_MARGIN));
31030
+ label.setAttribute("x", String(x));
31031
+ }
31032
+ }
31033
+ placeTip() {
31034
+ const tip = this.model && this.placed && !this.background ? tipFor(this.model, this.placed, this.hover) : null;
31035
+ this.tip.hidden = !tip;
31036
+ if (!tip) return;
31037
+ this.tipTitle.textContent = tip.title;
31038
+ this.tipSub.textContent = tip.sub ? ` · ${tip.sub}` : "";
31039
+ const containerW = this.root.clientWidth;
31040
+ const scale = containerW / 720;
31041
+ this.tip.style.maxWidth = `${Math.max(0, containerW - 2 * TIP_MARGIN)}px`;
31042
+ const half = this.tip.offsetWidth / 2;
31043
+ const left = Math.min(Math.max(tip.cx * scale, half + TIP_MARGIN), Math.max(half, containerW - half - TIP_MARGIN));
31044
+ this.tip.style.left = `${left}px`;
31045
+ this.tip.style.top = `${Math.max(4, tip.cy * scale - TIP_H)}px`;
31046
+ }
31047
+ };
31048
+
31049
+ //#endregion
31050
+ //#region src/lib/systemmap/planetary.ts
31051
+ function systemFromMapEntry(entry, name) {
31052
+ return {
31053
+ planetId: entry.primary.id,
31054
+ planetName: name,
31055
+ planetRadiusKm: entry.primary.radius_km,
31056
+ planetColor: BODY_COLORS[entry.primary.id] ?? "#cccccc",
31057
+ moons: entry.moons.map((mn) => ({
31058
+ id: mn.id,
31059
+ name: mn.id,
31060
+ aRp: mn.a_rp,
31061
+ tiltDeg: mn.tilt_deg,
31062
+ radiusKm: mn.radius_km,
31063
+ color: BODY_COLORS[mn.id] ?? mn.color ?? "#cccccc"
31064
+ })),
31065
+ rings: entry.rings ? {
31066
+ innerRp: entry.rings.inner_rp,
31067
+ outerRp: entry.rings.outer_rp
31068
+ } : null,
31069
+ moonCount: entry.moon_count
31070
+ };
31071
+ }
31072
+ var TOP_MOON_R = 14;
31073
+ var AT_SURFACE = 1.001;
31074
+ var PX_PER_DEG = .62;
31075
+ var TICK_LADDER = [
31076
+ 1,
31077
+ 2,
31078
+ 3,
31079
+ 5,
31080
+ 10,
31081
+ 20,
31082
+ 30,
31083
+ 50,
31084
+ 100,
31085
+ 200,
31086
+ 300,
31087
+ 500,
31088
+ 1e3,
31089
+ 2e3,
31090
+ 5e3
31091
+ ];
31092
+ var BG_VIEW = "0 96 320 144";
31093
+ var BG_VIEW_WIDE = "0 96 720 144";
31094
+ var EMPTY_DOMAIN = [1, 100];
31095
+ function domainOf(system, bands) {
31096
+ let lo = Math.min(...system.moons.map((mn) => mn.aRp));
31097
+ let hi = Math.max(...system.moons.map((mn) => mn.aRp));
31098
+ for (const b of bands) {
31099
+ lo = Math.min(lo, b.innerKm / system.planetRadiusKm);
31100
+ hi = Math.max(hi, b.outerKm / system.planetRadiusKm);
31101
+ }
31102
+ if (!Number.isFinite(lo) || !Number.isFinite(hi)) return EMPTY_DOMAIN;
31103
+ if (hi <= lo) {
31104
+ lo /= 2;
31105
+ hi *= 2;
31106
+ }
31107
+ lo = lo <= AT_SURFACE ? 1 : lo;
31108
+ return [lo > 1 ? 10 ** (Math.log10(lo) - .12) : 1, 10 ** (Math.log10(hi) + .12)];
31109
+ }
31110
+ function planetarySystemModel(system, parts) {
31111
+ const rKm = system.planetRadiusKm;
31112
+ const domain = domainOf(system, parts.bands);
31113
+ return {
31114
+ primary: {
31115
+ id: system.planetId,
31116
+ name: system.planetName,
31117
+ radiusKm: rKm,
31118
+ color: system.planetColor,
31119
+ link: parts.primaryLink
31120
+ },
31121
+ bodies: system.moons.map((mn) => ({
31122
+ id: mn.id,
31123
+ name: mn.name,
31124
+ aKm: mn.aRp * rKm,
31125
+ tiltDeg: mn.tiltDeg,
31126
+ radiusKm: mn.radiusKm,
31127
+ color: mn.color,
31128
+ link: parts.moonLink?.(mn)
31129
+ })),
31130
+ bands: parts.bands,
31131
+ cloud: parts.cloud,
31132
+ unitKm: rKm,
31133
+ domain,
31134
+ ticks: TICK_LADDER.filter((t) => t >= domain[0] && t <= domain[1]),
31135
+ axisLabel: parts.axisLabel,
31136
+ pxPerKm: TOP_MOON_R / Math.max(...system.moons.map((mn) => mn.radiusKm).filter((r) => r > 0), 1),
31137
+ pxPerDeg: PX_PER_DEG,
31138
+ text: parts.text,
31139
+ backgroundView: parts.wide ? BG_VIEW_WIDE : BG_VIEW,
31140
+ backgroundFit: parts.wide ? "fit" : "slice"
31141
+ };
31142
+ }
31143
+
31144
+ //#endregion
31145
+ //#region src/lib/systemmap/solar.ts
31146
+ var SOLAR_PX_PER_KM = 224e-6;
31147
+ var SOLAR_PX_PER_DEG = 1.8;
31148
+ var DOMAIN = [.3, 50];
31149
+ var TICKS = [
31150
+ .3,
31151
+ 1,
31152
+ 3,
31153
+ 10,
31154
+ 30
31155
+ ];
31156
+ var SUN_COLOR = "#ffdd44";
31157
+ function displayName(name) {
31158
+ const stripped = name.replace(/^\d+\s+/, "");
31159
+ if (stripped === stripped.toUpperCase() && /[A-Z]/.test(stripped)) return stripped.charAt(0) + stripped.slice(1).toLowerCase();
31160
+ return stripped;
31161
+ }
31162
+ function solarColor(o) {
31163
+ return o.color || BODY_COLORS[o.id] || "#cccccc";
31164
+ }
31165
+ function fileStacks(file, name) {
31166
+ const stacks = /* @__PURE__ */ new Map();
31167
+ const planets = new Map(file.objects.map((o) => [o.id, o]));
31168
+ for (const o of file.objects) {
31169
+ if (o.kind !== "moon" || !o.parent) continue;
31170
+ const stack = stacks.get(o.parent) ?? {
31171
+ satellites: [],
31172
+ count: planets.get(o.parent)?.moon_count,
31173
+ tab: false
31174
+ };
31175
+ stack.satellites.push({
31176
+ id: o.id,
31177
+ name: name(o),
31178
+ radiusKm: o.diameter_km / 2,
31179
+ color: solarColor(o)
31180
+ });
31181
+ stack.tab ||= !!o.link_parent;
31182
+ stacks.set(o.parent, stack);
31183
+ }
31184
+ return stacks;
31185
+ }
31186
+ function solarSystemModel(file, parts) {
31187
+ const sun = file.objects.find((o) => o.kind === "star");
31188
+ if (!sun) return null;
31189
+ const listed = fileStacks(file, parts.name);
31190
+ const stackOf = parts.stack ?? ((o) => listed.get(o.id));
31191
+ const bodies = file.objects.filter((o) => o.kind !== "moon" && o.kind !== "star" && (parts.keep?.(o) ?? true)).map((o) => {
31192
+ const stack = stackOf(o);
31193
+ return {
31194
+ id: o.id,
31195
+ name: parts.name(o),
31196
+ aKm: o.a * AU_KM,
31197
+ tiltDeg: o.i,
31198
+ radiusKm: o.diameter_km / 2,
31199
+ color: solarColor(o),
31200
+ rings: o.rings,
31201
+ satellites: stack?.satellites,
31202
+ satelliteCount: stack?.count,
31203
+ satellitesTab: stack?.tab ?? false,
31204
+ ...parts.links(o, stack)
31205
+ };
31206
+ });
31207
+ return {
31208
+ primary: {
31209
+ id: sun.id,
31210
+ name: parts.name(sun),
31211
+ radiusKm: sun.diameter_km / 2,
31212
+ color: SUN_COLOR,
31213
+ link: parts.primaryLink
31214
+ },
31215
+ bodies,
31216
+ bands: file.belts.map((b) => ({
31217
+ key: b.slug,
31218
+ innerKm: b.inner_au * AU_KM,
31219
+ outerKm: b.outer_au * AU_KM,
31220
+ tone: b.kind === "kuiper_belt" ? "sky" : "muted",
31221
+ ...parts.band(b)
31222
+ })),
31223
+ unitKm: AU_KM,
31224
+ domain: DOMAIN,
31225
+ ticks: TICKS,
31226
+ axisLabel: parts.text.axisAu,
31227
+ pxPerKm: SOLAR_PX_PER_KM,
31228
+ pxPerDeg: SOLAR_PX_PER_DEG,
31229
+ text: parts.text
31230
+ };
31231
+ }
31232
+
31233
+ //#endregion
31234
+ //#region src/lib/systemmap/views.ts
31235
+ function extraFromPlace(place) {
31236
+ return {
31237
+ id: place.id,
31238
+ name: place.name ?? place.id,
31239
+ moon: place.moon ?? false,
31240
+ aAu: place.aAu ?? 0,
31241
+ tiltDeg: place.tiltDeg ?? 0,
31242
+ radiusKm: place.radiusKm ?? 0,
31243
+ color: place.color ?? BODY_COLORS[place.id] ?? "#cccccc",
31244
+ parent: place.parent
31245
+ };
31246
+ }
31247
+ var MAX_STACK = 4;
31248
+ var ZONE_TOP_R = 10;
31249
+ var ZONE_DOMAIN = {
31250
+ inner: [.6, 5.5],
31251
+ outer: [4.5, 55]
31252
+ };
31253
+ var ZONE_TICKS = [
31254
+ .1,
31255
+ .2,
31256
+ .3,
31257
+ .5,
31258
+ 1,
31259
+ 2,
31260
+ 3,
31261
+ 5,
31262
+ 10,
31263
+ 20,
31264
+ 30,
31265
+ 50,
31266
+ 100,
31267
+ 200,
31268
+ 500,
31269
+ 1e3
31270
+ ];
31271
+ var LAST_AU_TICK_AT = .9;
31272
+ var LAST_RADII_TICK_AT = .76;
31273
+ function tickAt(tick, domain) {
31274
+ return Math.log(tick / domain[0]) / Math.log(domain[1] / domain[0]);
31275
+ }
31276
+ var JUPITER_ID = "naif-599";
31277
+ function unplacedIds(solar, systems, bodies, placed = []) {
31278
+ if (!bodies) return [];
31279
+ const known = new Set([...solar.objects.map((o) => o.id), ...placed]);
31280
+ for (const entry of Object.values(systems)) {
31281
+ known.add(entry.primary.id);
31282
+ for (const moon of entry.moons) known.add(moon.id);
31283
+ }
31284
+ return [...bodies].filter((id) => !known.has(id));
31285
+ }
31286
+ function extraFromBundle(global) {
31287
+ const moon = global.type === "moon";
31288
+ const parent = global.orbit?.parent_id;
31289
+ const a = parent === void 0 || parent === "naif-10" || parent === "naif-0" ? global.orbit?.a ?? global.orbit?.q : void 0;
31290
+ if (a === void 0 && !moon) return null;
31291
+ const radii = global.radii;
31292
+ return {
31293
+ id: global.id,
31294
+ name: displayName(global.name ?? global.id),
31295
+ moon,
31296
+ aAu: a ?? 0,
31297
+ tiltDeg: global.orbit?.i ?? 0,
31298
+ radiusKm: radii ? (radii.a + radii.b + radii.c) / 3 : (global.sbdb?.diameter ?? 0) / 2,
31299
+ color: BODY_COLORS[global.id] ?? global.sbdb?.color ?? global.color ?? "#cccccc",
31300
+ parent: global.orbit?.parent_id
31301
+ };
31302
+ }
31303
+ var ZONE_CUT = .92;
31304
+ function zoneOf(aAu, jupiterAu) {
31305
+ return aAu < jupiterAu * ZONE_CUT ? "inner" : "outer";
31306
+ }
31307
+ function systemEntry(systems, primaryId) {
31308
+ return Object.values(systems).find((entry) => entry.primary.id === primaryId);
31309
+ }
31310
+ function targetKey(target) {
31311
+ return target.kind === "zone" ? `zone:${target.zone}` : `${target.kind}:${target.id}`;
31312
+ }
31313
+ var Scope = class {
31314
+ targets = /* @__PURE__ */ new Map();
31315
+ constructor(data, options, pick) {
31316
+ this.data = data;
31317
+ this.options = options;
31318
+ this.pick = pick;
31319
+ }
31320
+ has(id) {
31321
+ return !this.options.bodies || this.options.bodies.has(id);
31322
+ }
31323
+ name(id, exported) {
31324
+ return this.options.names[id] ?? (exported ? displayName(exported) : id);
31325
+ }
31326
+ named(id, name) {
31327
+ return this.options.names[id] ?? name;
31328
+ }
31329
+ link(target) {
31330
+ this.targets.set(targetKey(target), target);
31331
+ return { onclick: () => this.pick(target) };
31332
+ }
31333
+ body(id, name) {
31334
+ return this.has(id) ? this.link({
31335
+ kind: "body",
31336
+ id,
31337
+ name
31338
+ }) : void 0;
31339
+ }
31340
+ stack(primaryId, listed) {
31341
+ if (listed && !this.options.bodies) return listed;
31342
+ const moons = (systemEntry(this.data.systems, primaryId)?.moons ?? []).filter((moon) => this.has(moon.id)).map((moon) => ({
31343
+ id: moon.id,
31344
+ name: this.name(moon.id),
31345
+ radiusKm: moon.radius_km,
31346
+ color: BODY_COLORS[moon.id] ?? moon.color ?? "#cccccc"
31347
+ }));
31348
+ if (!moons.length) return void 0;
31349
+ moons.sort((a, b) => b.radiusKm - a.radiusKm);
31350
+ return {
31351
+ satellites: moons.slice(0, MAX_STACK),
31352
+ count: moons.length,
31353
+ tab: true
31354
+ };
31355
+ }
31356
+ links(id, name, stack) {
31357
+ if (!stack) return { link: this.body(id, name) };
31358
+ const system = () => this.link({
31359
+ kind: "system",
31360
+ id,
31361
+ name
31362
+ });
31363
+ if (this.options.grouping === "systems") return {
31364
+ link: system(),
31365
+ grouped: true
31366
+ };
31367
+ const largest = stack.satellites[0];
31368
+ return {
31369
+ link: this.body(id, name),
31370
+ satellitesLink: stack.tab ? system() : this.body(largest.id, largest.name)
31371
+ };
31372
+ }
31373
+ built(model, label) {
31374
+ return {
31375
+ model,
31376
+ targets: [...this.targets.values()],
31377
+ label
31378
+ };
31379
+ }
31380
+ };
31381
+ function buildSolarView(data, options, text, pick) {
31382
+ const scope = new Scope(data, options, pick);
31383
+ const name = (o) => scope.name(o.id, o.name);
31384
+ const listed = fileStacks(data.solar, name);
31385
+ const stacks = new Map(data.solar.objects.map((o) => [o.id, scope.stack(o.id, listed.get(o.id))]));
31386
+ const sun = data.solar.objects.find((o) => o.kind === "star");
31387
+ const zoneBand = (zone) => {
31388
+ const label = zone === "inner" ? text.zoneInner : text.zoneOuter;
31389
+ return {
31390
+ label,
31391
+ sub: "",
31392
+ ...scope.link({
31393
+ kind: "zone",
31394
+ zone,
31395
+ name: label
31396
+ })
31397
+ };
31398
+ };
31399
+ const model = solarSystemModel(data.solar, {
31400
+ text,
31401
+ name,
31402
+ primaryLink: sun && scope.body(sun.id, name(sun)),
31403
+ keep: (o) => scope.has(o.id) || !!stacks.get(o.id),
31404
+ stack: (o) => stacks.get(o.id),
31405
+ links: (o, stack) => scope.links(o.id, name(o), stack),
31406
+ band: (belt) => options.zones === "belts" ? { label: belt.label } : zoneBand(belt.kind === "kuiper_belt" ? "outer" : "inner")
31407
+ });
31408
+ if (!model) throw new Error("spacemap: the Solar System map names no Sun");
31409
+ return scope.built(model, text.solarSystem);
31410
+ }
31411
+ function buildSystemView(data, primaryId, exported, options, text, pick) {
31412
+ const entry = systemEntry(data.systems, primaryId);
31413
+ if (!entry) throw new Error(`spacemap: no planetary system around ${primaryId}`);
31414
+ const scope = new Scope(data, options, pick);
31415
+ const system = systemFromMapEntry(entry, scope.name(primaryId, exported[primaryId]));
31416
+ system.moons = system.moons.filter((moon) => scope.has(moon.id)).map((moon) => ({
31417
+ ...moon,
31418
+ name: scope.name(moon.id, exported[moon.id])
31419
+ }));
31420
+ const rKm = system.planetRadiusKm;
31421
+ const bands = system.rings ? [{
31422
+ key: "rings",
31423
+ label: text.rings,
31424
+ innerKm: system.rings.innerRp * rKm,
31425
+ outerKm: system.rings.outerRp * rKm,
31426
+ tone: "amber"
31427
+ }] : [];
31428
+ const model = planetarySystemModel(system, {
31429
+ text,
31430
+ axisLabel: text.axisRadii,
31431
+ bands,
31432
+ primaryLink: scope.body(primaryId, system.planetName),
31433
+ moonLink: (moon) => scope.body(moon.id, moon.name)
31434
+ });
31435
+ model.ticks = model.ticks.filter((t) => tickAt(t, model.domain) < LAST_RADII_TICK_AT);
31436
+ return scope.built(model, system.planetName);
31437
+ }
31438
+ function buildZoneView(data, zone, options, text, pick) {
31439
+ const scope = new Scope(data, options, pick);
31440
+ const { solar, extras } = data;
31441
+ const sun = solar.objects.find((o) => o.kind === "star");
31442
+ const jupiterAu = solar.objects.find((o) => o.id === JUPITER_ID)?.a;
31443
+ if (!sun || jupiterAu === void 0) throw new Error("spacemap: the Solar System map names no Sun or no Jupiter");
31444
+ const small = solar.objects.filter((o) => o.kind === "dwarf" || o.kind === "asteroid").map((o) => ({
31445
+ id: o.id,
31446
+ name: scope.name(o.id, o.name),
31447
+ aAu: o.a,
31448
+ tiltDeg: o.i,
31449
+ radiusKm: o.diameter_km / 2,
31450
+ color: solarColor(o)
31451
+ }));
31452
+ for (const extra of extras) {
31453
+ if (extra.moon || small.some((s) => s.id === extra.id)) continue;
31454
+ if (!(extra.aAu > 0)) continue;
31455
+ small.push({
31456
+ ...extra,
31457
+ name: scope.named(extra.id, extra.name)
31458
+ });
31459
+ }
31460
+ const bodies = [];
31461
+ for (const s of small) {
31462
+ if (zoneOf(s.aAu, jupiterAu) !== zone) continue;
31463
+ const base = {
31464
+ ...s,
31465
+ aKm: s.aAu * AU_KM
31466
+ };
31467
+ const stack = scope.stack(s.id);
31468
+ if (stack) {
31469
+ bodies.push({
31470
+ ...base,
31471
+ satellites: stack.satellites,
31472
+ satelliteCount: stack.count,
31473
+ satellitesTab: true,
31474
+ ...scope.links(s.id, s.name, stack)
31475
+ });
31476
+ continue;
31477
+ }
31478
+ const moons = extras.filter((extra) => extra.moon && extra.parent === s.id && scope.has(extra.id)).map((extra) => {
31479
+ const name = scope.named(extra.id, extra.name);
31480
+ return {
31481
+ ...extra,
31482
+ name,
31483
+ link: scope.body(extra.id, name)
31484
+ };
31485
+ });
31486
+ if (!scope.has(s.id) && !moons.length) continue;
31487
+ bodies.push({
31488
+ ...base,
31489
+ link: scope.body(s.id, s.name),
31490
+ satellites: moons.length ? moons : void 0
31491
+ });
31492
+ }
31493
+ const [lo, hi] = ZONE_DOMAIN[zone];
31494
+ const domain = [Math.min(lo, ...bodies.map((b) => b.aKm / AU_KM * .85)), Math.max(hi, ...bodies.map((b) => b.aKm / AU_KM * 1.15))];
31495
+ const within = (au) => au >= domain[0] && au <= domain[1];
31496
+ const pxPerKm = ZONE_TOP_R / Math.max(...bodies.map((b) => b.radiusKm), 1);
31497
+ for (const o of solar.objects) {
31498
+ if (o.kind !== "planet" || !within(o.a)) continue;
31499
+ bodies.push({
31500
+ id: o.id,
31501
+ name: scope.name(o.id, o.name),
31502
+ aKm: o.a * AU_KM,
31503
+ tiltDeg: 0,
31504
+ radiusKm: 0,
31505
+ color: solarColor(o),
31506
+ reference: true
31507
+ });
31508
+ }
31509
+ const label = zone === "inner" ? text.zoneInner : text.zoneOuter;
31510
+ return scope.built({
31511
+ primary: {
31512
+ id: sun.id,
31513
+ name: scope.name(sun.id, sun.name),
31514
+ radiusKm: sun.diameter_km / 2 * SOLAR_PX_PER_KM / pxPerKm,
31515
+ color: SUN_COLOR
31516
+ },
31517
+ bodies,
31518
+ bands: solar.belts.filter((belt) => within(belt.inner_au) && within(belt.outer_au)).map((belt) => ({
31519
+ key: belt.slug,
31520
+ label: belt.label,
31521
+ innerKm: belt.inner_au * AU_KM,
31522
+ outerKm: belt.outer_au * AU_KM,
31523
+ tone: belt.kind === "kuiper_belt" ? "sky" : "muted"
31524
+ })),
31525
+ unitKm: AU_KM,
31526
+ domain,
31527
+ ticks: ZONE_TICKS.filter((t) => within(t) && tickAt(t, domain) < LAST_AU_TICK_AT),
31528
+ axisLabel: text.axisAu,
31529
+ pxPerKm,
31530
+ pxPerDeg: SOLAR_PX_PER_DEG,
31531
+ text
31532
+ }, label);
31533
+ }
31534
+
31535
+ //#endregion
31536
+ //#region src/lib/systemmap/system-map.ts
31537
+ var onceTarget = /* @__PURE__ */ new WeakMap();
31538
+ var AU_TO_KM_THRESHOLD = .01;
31539
+ function viewText() {
31540
+ const { messages, locale } = host();
31541
+ const number = new Intl.NumberFormat(locale(), { maximumSignificantDigits: 3 });
31542
+ const kilometres = new Intl.NumberFormat(locale(), {
31543
+ style: "unit",
31544
+ unit: "kilometer",
31545
+ maximumSignificantDigits: 3
31546
+ });
31547
+ const au = messages.unit_symbol_astronomical_unit();
31548
+ return {
31549
+ primary: messages.planetary_system_primary(),
31550
+ retrograde: messages.planetary_system_retrograde(),
31551
+ moons: (count) => messages.system_map_moons({ count }),
31552
+ distance: (km) => {
31553
+ const distance = km / AU_KM;
31554
+ return distance > 0 && distance < AU_TO_KM_THRESHOLD ? kilometres.format(km) : `${number.format(distance)} ${au}`;
31555
+ },
31556
+ axisAu: messages.system_map_axis_au(),
31557
+ solarSystem: messages.system_map_solar_system(),
31558
+ rings: messages.tab_rings(),
31559
+ axisRadii: messages.planetary_system_axis_unit(),
31560
+ zoneInner: messages.system_map_zone_inner(),
31561
+ zoneOuter: messages.system_map_zone_outer()
31562
+ };
31563
+ }
31564
+ var SystemMap = class {
31565
+ chart = new SystemMapChart();
31566
+ listeners = /* @__PURE__ */ new Map();
31567
+ view;
31568
+ bodies;
31569
+ names;
31570
+ grouping;
31571
+ zones;
31572
+ places;
31573
+ targets = [];
31574
+ extras = /* @__PURE__ */ new Map();
31575
+ queue = Promise.resolve();
31576
+ disposed = false;
31577
+ constructor(options = {}) {
31578
+ this.view = options.view ?? { kind: "solar-system" };
31579
+ this.bodies = options.bodies ? new Set(options.bodies) : null;
31580
+ this.names = options.names ?? {};
31581
+ this.grouping = options.grouping ?? "bodies";
31582
+ this.zones = options.zones ?? "belts";
31583
+ this.places = (options.places ?? []).map(extraFromPlace);
31584
+ }
31585
+ mount(container) {
31586
+ container.append(this.chart.root);
31587
+ }
31588
+ remove() {
31589
+ this.disposed = true;
31590
+ this.chart.destroy();
31591
+ }
31592
+ load() {
31593
+ return this.draw();
31594
+ }
31595
+ async setView(view) {
31596
+ await this.draw(view);
31597
+ }
31598
+ getView() {
31599
+ return this.view;
31600
+ }
31601
+ setBodies(ids) {
31602
+ const bodies = ids ? new Set(ids) : null;
31603
+ return this.draw(void 0, bodies).catch(() => {});
31604
+ }
31605
+ getTargets() {
31606
+ return [...this.targets];
31607
+ }
31608
+ draw(view, bodies) {
31609
+ const turn = this.queue.then(() => {
31610
+ if (bodies !== void 0) this.bodies = bodies;
31611
+ return this.drawNow(view ?? this.view);
31612
+ });
31613
+ this.queue = turn.catch(() => {});
31614
+ return turn;
31615
+ }
31616
+ async drawNow(view) {
31617
+ let built;
31618
+ try {
31619
+ built = await this.build(view);
31620
+ } catch (cause) {
31621
+ const error = cause instanceof Error ? cause : new Error(String(cause));
31622
+ if (!this.disposed) this.emit("error", error);
31623
+ throw error;
31624
+ }
31625
+ if (this.disposed) return;
31626
+ const changed = JSON.stringify(view) !== JSON.stringify(this.view);
31627
+ this.view = view;
31628
+ this.targets = built.targets;
31629
+ this.chart.update(built.model, { ariaLabel: built.label });
31630
+ if (changed) this.emit("viewchange", view);
31631
+ }
31632
+ async build(view) {
31633
+ const [solar, systems] = await Promise.all([fetchSolarSystemMap(), fetchPlanetarySystemsMap()]);
31634
+ const options = {
31635
+ bodies: this.bodies,
31636
+ names: this.names,
31637
+ grouping: this.grouping,
31638
+ zones: this.zones
31639
+ };
31640
+ const text = viewText();
31641
+ const pick = (target) => this.emit("select", target);
31642
+ const data = {
31643
+ solar,
31644
+ systems,
31645
+ extras: []
31646
+ };
31647
+ switch (view.kind) {
31648
+ case "solar-system": return buildSolarView(data, options, text, pick);
31649
+ case "system": return buildSystemView(data, view.id, await this.exportedNames(view.id, systems), options, text, pick);
31650
+ case "zone": {
31651
+ const ids = unplacedIds(solar, systems, this.bodies, this.places.map((place) => place.id));
31652
+ const read = (await Promise.all(ids.map((id) => this.extra(id)))).filter((extra) => extra !== null);
31653
+ const placed = [...this.places, ...read];
31654
+ const parents = unplacedIds(solar, systems, new Set(placed.flatMap((extra) => extra.moon && extra.parent ? [extra.parent] : [])), placed.map((extra) => extra.id));
31655
+ const hosts = await Promise.all(parents.map((id) => this.extra(id)));
31656
+ const extras = [...placed, ...hosts.filter((extra) => extra !== null)];
31657
+ return buildZoneView({
31658
+ ...data,
31659
+ extras
31660
+ }, view.zone, options, text, pick);
31661
+ }
31662
+ }
31663
+ }
31664
+ extra(id) {
31665
+ let pending = this.extras.get(id);
31666
+ if (!pending) {
31667
+ pending = fetchObjectDetail(id, false).then((detail) => {
31668
+ if (!detail.global) throw new Error(`spacemap: no such body as ${id}`);
31669
+ return extraFromBundle(detail.global);
31670
+ }).catch((cause) => {
31671
+ this.extras.delete(id);
31672
+ this.emit("error", cause instanceof Error ? cause : new Error(String(cause)));
31673
+ return null;
31674
+ });
31675
+ this.extras.set(id, pending);
31676
+ }
31677
+ return pending;
31678
+ }
31679
+ async exportedNames(primaryId, systems) {
31680
+ if (![primaryId, ...(systemEntry(systems, primaryId)?.moons ?? []).map((m) => m.id)].some((id) => !(id in this.names) && (id === primaryId || !this.bodies || this.bodies.has(id)))) return {};
31681
+ try {
31682
+ const { global, localized } = await fetchObjectDetail(primaryId, true, host().locale());
31683
+ const names = {};
31684
+ for (const moon of global?.notable_moons ?? []) if (moon.id) names[moon.id] = moon.name;
31685
+ Object.assign(names, localized?.notable_moon_names);
31686
+ const name = localized?.name ?? global?.name;
31687
+ if (name) names[primaryId] = name;
31688
+ return names;
31689
+ } catch {
31690
+ return {};
31691
+ }
31692
+ }
31693
+ on(event, listener) {
31694
+ let set = this.listeners.get(event);
31695
+ if (!set) {
31696
+ set = /* @__PURE__ */ new Set();
31697
+ this.listeners.set(event, set);
31698
+ }
31699
+ set.add(listener);
31700
+ return () => set.delete(listener);
31701
+ }
31702
+ once(event, listener) {
31703
+ const wrapped = ((...args) => {
31704
+ off();
31705
+ listener(...args);
31706
+ });
31707
+ onceTarget.set(wrapped, listener);
31708
+ const off = this.on(event, wrapped);
31709
+ return off;
31710
+ }
31711
+ off(event, listener) {
31712
+ const set = this.listeners.get(event);
31713
+ if (!set) return;
31714
+ set.delete(listener);
31715
+ for (const held of set) if (onceTarget.get(held) === listener) set.delete(held);
31716
+ }
31717
+ emit(event, ...args) {
31718
+ const set = this.listeners.get(event);
31719
+ if (!set) return;
31720
+ for (const listener of [...set]) listener(...args);
31721
+ }
31722
+ };
31723
+
30077
31724
  //#endregion
30078
31725
  //#region src/lib/scene/state/attribution.ts
30079
31726
  var DEFAULT_ORBIT_LABELS = Object.fromEntries(ORBIT_SOURCE_ORDER.map((s) => [s, ORBIT_SOURCES[s].label]));
@@ -30414,6 +32061,27 @@ async function createPanorama(options) {
30414
32061
  await view.load();
30415
32062
  return view;
30416
32063
  }
32064
+ async function createSystemMap(options) {
32065
+ const { container, dataUrl, imagesUrl, locale, messages, includeNonCommercial, events, ...rest } = options;
32066
+ const element = resolveContainer(container);
32067
+ applyHost({
32068
+ dataUrl,
32069
+ imagesUrl,
32070
+ locale,
32071
+ messages,
32072
+ includeNonCommercial
32073
+ });
32074
+ const map = new SystemMap(rest);
32075
+ map.mount(element);
32076
+ for (const [event, listener] of Object.entries(events ?? {})) map.on(event, listener);
32077
+ try {
32078
+ await map.load();
32079
+ } catch (error) {
32080
+ map.remove();
32081
+ throw error;
32082
+ }
32083
+ return map;
32084
+ }
30417
32085
  function resolveContainer(container) {
30418
32086
  if (typeof container !== "string") return container;
30419
32087
  const element = document.querySelector(container);
@@ -30431,5 +32099,5 @@ function applyHost(options) {
30431
32099
  }
30432
32100
 
30433
32101
  //#endregion
30434
- export { AttributionControl, FlatAttributionControl, FlatMap, GestureHandler, MAP_LAYERS, PROJECTION_IDS, PanoramaAttributionControl, PanoramaView, SpaceMap, Viewport, bearingDeg, boxRing, circlePoints, configureHost, createFlatMap, createMap, createPanorama, createProjection, dateToJD, defaultSceneSettings, elements, fetchPanoramaIndex, fetchPanoramas, findPanorama, fixed, graticule, groundDistanceM, jdToDate, kmToAu, neighboursOf, panoramaAt, pathFor, planeBasis, projectionAspect, samples, smallCircle, tle };
30435
- (function(){if(typeof document==='undefined')return;var s=document.createElement('style');s.textContent=".scene-label{pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;color:inherit;touch-action:none;z-index:1;width:32px;height:32px;text-decoration:none;display:block;position:relative;left:0}.scene-label__halo{border-radius:50%;width:100%;height:100%;transition:transform .1s}.scene-label__halo--spacecraft,.scene-label__halo--debris{overflow:visible}.scene-label__loader{box-sizing:border-box;pointer-events:none;border:2px solid #fff;border-color:#fff #fff #fff3 #fff3;border-radius:50%;width:32px;height:32px;margin:auto;animation:.9s linear infinite scene-label-loader-spin;position:fixed;inset:0}@keyframes scene-label-loader-spin{to{transform:rotate(360deg)}}.reduce-motion .scene-label__loader{animation-duration:.9s!important;animation-iteration-count:infinite!important}.scene-label__name{white-space:nowrap;pointer-events:auto;cursor:pointer;color:#fff;text-shadow:0 0 4px #000,0 0 4px #000;padding-left:8px;font-size:14px;font-weight:700;text-decoration:none;position:absolute;top:50%;left:32px;transform:translateY(-50%)}.scene-label__name--large{font-size:16px}.scene-label__annotations{top:28px;pointer-events:none;flex-direction:column;padding-inline-start:8px;display:flex;position:absolute;inset-inline-start:32px}.scene-label__annotation{opacity:0;white-space:nowrap;color:#fff9;text-shadow:0 0 3px #000,0 0 3px #000;transition:opacity .15s, display .15s allow-discrete;font-size:11px;line-height:1.3;display:none}.scene-label__annotation--visible{opacity:1;display:block}@starting-style{.scene-label__annotation--visible{opacity:0}}.scene-feature-label{color:#ffffffb3;white-space:nowrap;pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;text-shadow:0 0 2px #000,0 0 2px #000;z-index:0;font-size:12px;transition:color .1s;left:0}.scene-feature-label:hover{color:#fff;text-shadow:0 0 3px #000,0 0 3px #000}.scene-feature-label--active{color:#fff;font-size:14px;font-weight:600}.scene-path-label{color:#fff;font:inherit;white-space:nowrap;text-align:start;text-shadow:0 0 4px #000,0 0 4px #000;pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;touch-action:none;z-index:2;background:0 0;border:0;align-items:center;gap:8px;padding:0;line-height:1.15;display:flex;left:0}.scene-path-label--static{pointer-events:none;cursor:default}.scene-path-label__halo{border-radius:50%;flex-shrink:0;width:32px;height:32px;margin-inline-start:-16px;transition:transform .1s}.scene-path-label:hover .scene-path-label__halo{transform:scale(1.15)}.scene-path-label__text{flex-direction:column;gap:1px;display:flex}.scene-path-label__name{font-size:14px;font-weight:700}.scene-path-label__when{opacity:.8;font-variant-numeric:tabular-nums;font-size:12px}.scene-path-label--dim .scene-path-label__text{display:none}.scene-path-label--dim .scene-path-label__halo{transform:scale(.55)}.scene-path-label--dim:hover .scene-path-label__halo{transform:scale(.75)}.scene-path-label--step .scene-path-label__halo{background:0 0;border:0}.scene-path-label--step:hover .scene-path-label__halo{background:#ffffff1f}.scene-path-label--step .scene-path-label__name{font-size:12px;font-weight:600}.scene-path-label--step .scene-path-label__when{font-size:11px}.scene-path-label__hazard{font-size:11px;font-weight:700}.scene-path-label--lit{color:#fff}.scene-path-label--lit .scene-path-label__halo{transform:scale(1.15)}.scene-path-label--lit.scene-path-label--dim .scene-path-label__halo{transform:scale(.75)}.scene-path-label--faint .scene-path-label__text{display:none}.scene-path-label--faint .scene-path-label__halo{opacity:.45;transform:scale(.5)}.scene-path-label--faint:hover .scene-path-label__halo{opacity:1;transform:scale(.75)}.scene-overlay--no-names .scene-label__name,.scene-overlay--no-names .scene-label__annotations{display:none}.scene-hazard-label{white-space:nowrap;pointer-events:none;-webkit-user-select:none;user-select:none;text-shadow:0 0 4px #000,0 0 4px #000;margin-inline-start:16px;font-size:12px;font-weight:700;position:relative;left:0}.user-location-marker{pointer-events:none;background:#1a73e8;border:2px solid #fff;border-radius:50%;width:16px;height:16px;box-shadow:0 0 6px #0006}.sm-drawing-label{white-space:nowrap;color:#fff;text-shadow:0 0 4px #000,0 0 4px #000;-webkit-user-select:none;user-select:none;pointer-events:none;font:700 14px/1.2 system-ui,sans-serif}.sm-drawing-icon{-webkit-user-select:none;user-select:none;pointer-events:none;display:block}.sm-drawing--interactive{pointer-events:auto;cursor:pointer;touch-action:none}.sm-cooperative-hint{z-index:4;color:#fff;text-align:center;pointer-events:none;opacity:0;background:#0000008c;place-content:center;padding:16px;font:15px/1.4 system-ui,sans-serif;transition:opacity .2s;display:grid;position:absolute;inset:0}.sm-cooperative-hint--shown{opacity:1}@media (prefers-reduced-motion:reduce){.sm-cooperative-hint{transition:none}}.sm-ctrl{z-index:2;pointer-events:none;flex-direction:column;gap:6px;max-width:100%;padding:6px;display:flex;position:absolute}.sm-ctrl>*{pointer-events:auto}.sm-ctrl--top-left{align-items:flex-start;top:0;left:0}.sm-ctrl--top-right{align-items:flex-end;top:0;right:0}.sm-ctrl--bottom-left{align-items:flex-start;bottom:0;left:0}.sm-ctrl--bottom-right{align-items:flex-end;bottom:0;right:0}.sm-map{-webkit-user-select:none;user-select:none;width:100%;height:100%;position:relative}.sm-map__canvas{pointer-events:auto;touch-action:none;width:100%;height:100%;display:block}.sm-map__canvas:focus-visible{outline:2px solid var(--ring,Highlight)}.sm-map .scene-overlay{pointer-events:none;z-index:0;position:absolute;inset:0}.sm-flat{touch-action:none;background:#05070e;width:100%;height:100%;position:relative;overflow:hidden}.sm-flat__raster,.sm-flat__vector,.sm-flat__markers{width:100%;height:100%;position:absolute;inset:0}.sm-flat__raster{display:block}.sm-flat__vector,.sm-flat__markers{pointer-events:none}.sm-flat--interactive{cursor:grab}.sm-flat--cooperative{touch-action:pan-x pan-y}.sm-flat--dragging{cursor:grabbing}.sm-flat__graticule{fill:none;stroke:#ffffff47;stroke-width:1px}.sm-flat__frame{fill:none;stroke:#ffffff73;stroke-width:1px}.sm-flat__feature-dot{fill:#fffc}.sm-flat__feature-label{fill:#ffffffd9;paint-order:stroke;stroke:#000000a6;stroke-width:2.5px;font:10px/1 system-ui,sans-serif}.sm-flat__marker-dot{background:#4ea3ff;border:2px solid #fff;border-radius:50%;width:10px;height:10px;box-shadow:0 0 0 1px #00000073}.sm-ctrl:has(>.sm-attribution:only-child){padding:0}.sm-ctrl--top-left:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 0 var(--sm-attribution-radius) 0}.sm-ctrl--top-right:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 0 0 var(--sm-attribution-radius)}.sm-ctrl--bottom-left:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 var(--sm-attribution-radius) 0 0}.sm-ctrl--bottom-right:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:var(--sm-attribution-radius) 0 0 0}.sm-attribution{--sm-attribution-radius:3px;border-radius:var(--sm-attribution-radius);color:#ffffffbf;white-space:nowrap;background:#0006;align-items:center;gap:.5em;max-width:100%;padding:1px 6px;font:11px/1.45 system-ui,sans-serif;display:flex}.sm-attribution a{color:inherit;text-decoration:none}.sm-attribution a:hover{color:#fff;text-decoration:underline}.sm-attribution__home{font-weight:600}.sm-attribution__group{text-overflow:ellipsis;overflow:hidden}.sm-attribution__label{color:#ffffff80}\n/*$vite$:1*/";document.head.appendChild(s);})();
32102
+ export { AttributionControl, FlatAttributionControl, FlatMap, GestureHandler, MAP_LAYERS, PROJECTION_IDS, PanoramaAttributionControl, PanoramaView, SpaceMap, SystemMap, Viewport, bearingDeg, boxRing, circlePoints, configureHost, createFlatMap, createMap, createPanorama, createProjection, createSystemMap, dateToJD, defaultSceneSettings, elements, fetchPanoramaIndex, fetchPanoramas, findPanorama, fixed, graticule, groundDistanceM, isViewable, jdToDate, kmToAu, neighboursOf, panoramaAt, pathFor, planeBasis, projectionAspect, samples, smallCircle, tle };
32103
+ (function(){if(typeof document==='undefined')return;var s=document.createElement('style');s.textContent=".scene-label{pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;color:inherit;touch-action:none;z-index:1;width:32px;height:32px;text-decoration:none;display:block;position:relative;left:0}.scene-label__halo{border-radius:50%;width:100%;height:100%;transition:transform .1s}.scene-label__halo--spacecraft,.scene-label__halo--debris{overflow:visible}.scene-label__loader{box-sizing:border-box;pointer-events:none;border:2px solid #fff;border-color:#fff #fff #fff3 #fff3;border-radius:50%;width:32px;height:32px;margin:auto;animation:.9s linear infinite scene-label-loader-spin;position:fixed;inset:0}@keyframes scene-label-loader-spin{to{transform:rotate(360deg)}}.reduce-motion .scene-label__loader{animation-duration:.9s!important;animation-iteration-count:infinite!important}.scene-label__name{white-space:nowrap;pointer-events:auto;cursor:pointer;color:#fff;text-shadow:0 0 4px #000,0 0 4px #000;padding-left:8px;font-size:14px;font-weight:700;text-decoration:none;position:absolute;top:50%;left:32px;transform:translateY(-50%)}.scene-label__name--large{font-size:16px}.scene-label__annotations{top:28px;pointer-events:none;flex-direction:column;padding-inline-start:8px;display:flex;position:absolute;inset-inline-start:32px}.scene-label__annotation{opacity:0;white-space:nowrap;color:#fff9;text-shadow:0 0 3px #000,0 0 3px #000;transition:opacity .15s, display .15s allow-discrete;font-size:11px;line-height:1.3;display:none}.scene-label__annotation--visible{opacity:1;display:block}@starting-style{.scene-label__annotation--visible{opacity:0}}.scene-feature-label{color:#ffffffb3;white-space:nowrap;pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;text-shadow:0 0 2px #000,0 0 2px #000;z-index:0;font-size:12px;transition:color .1s;left:0}.scene-feature-label:hover{color:#fff;text-shadow:0 0 3px #000,0 0 3px #000}.scene-feature-label--active{color:#fff;font-size:14px;font-weight:600}.scene-path-label{color:#fff;font:inherit;white-space:nowrap;text-align:start;text-shadow:0 0 4px #000,0 0 4px #000;pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;touch-action:none;z-index:2;background:0 0;border:0;align-items:center;gap:8px;padding:0;line-height:1.15;display:flex;left:0}.scene-path-label--static{pointer-events:none;cursor:default}.scene-path-label__halo{border-radius:50%;flex-shrink:0;width:32px;height:32px;margin-inline-start:-16px;transition:transform .1s}.scene-path-label:hover .scene-path-label__halo{transform:scale(1.15)}.scene-path-label__text{flex-direction:column;gap:1px;display:flex}.scene-path-label__name{font-size:14px;font-weight:700}.scene-path-label__when{opacity:.8;font-variant-numeric:tabular-nums;font-size:12px}.scene-path-label--dim .scene-path-label__text{display:none}.scene-path-label--dim .scene-path-label__halo{transform:scale(.55)}.scene-path-label--dim:hover .scene-path-label__halo{transform:scale(.75)}.scene-path-label--step .scene-path-label__halo{background:0 0;border:0}.scene-path-label--step:hover .scene-path-label__halo{background:#ffffff1f}.scene-path-label--step .scene-path-label__name{font-size:12px;font-weight:600}.scene-path-label--step .scene-path-label__when{font-size:11px}.scene-path-label__hazard{font-size:11px;font-weight:700}.scene-path-label--lit{color:#fff}.scene-path-label--lit .scene-path-label__halo{transform:scale(1.15)}.scene-path-label--lit.scene-path-label--dim .scene-path-label__halo{transform:scale(.75)}.scene-path-label--faint .scene-path-label__text{display:none}.scene-path-label--faint .scene-path-label__halo{opacity:.45;transform:scale(.5)}.scene-path-label--faint:hover .scene-path-label__halo{opacity:1;transform:scale(.75)}.scene-overlay--no-names .scene-label__name,.scene-overlay--no-names .scene-label__annotations{display:none}.scene-hazard-label{white-space:nowrap;pointer-events:none;-webkit-user-select:none;user-select:none;text-shadow:0 0 4px #000,0 0 4px #000;margin-inline-start:16px;font-size:12px;font-weight:700;position:relative;left:0}.user-location-marker{pointer-events:none;background:#1a73e8;border:2px solid #fff;border-radius:50%;width:16px;height:16px;box-shadow:0 0 6px #0006}.sm-drawing-label{white-space:nowrap;color:#fff;text-shadow:0 0 4px #000,0 0 4px #000;-webkit-user-select:none;user-select:none;pointer-events:none;font:700 14px/1.2 system-ui,sans-serif}.sm-drawing-icon{-webkit-user-select:none;user-select:none;pointer-events:none;display:block}.sm-drawing--interactive{pointer-events:auto;cursor:pointer;touch-action:none}.sm-cooperative-hint{z-index:4;color:#fff;text-align:center;pointer-events:none;opacity:0;background:#0000008c;place-content:center;padding:16px;font:15px/1.4 system-ui,sans-serif;transition:opacity .2s;display:grid;position:absolute;inset:0}.sm-cooperative-hint--shown{opacity:1}@media (prefers-reduced-motion:reduce){.sm-cooperative-hint{transition:none}}.sm-ctrl{z-index:2;pointer-events:none;flex-direction:column;gap:6px;max-width:100%;padding:6px;display:flex;position:absolute}.sm-ctrl>*{pointer-events:auto}.sm-ctrl--top-left{align-items:flex-start;top:0;left:0}.sm-ctrl--top-right{align-items:flex-end;top:0;right:0}.sm-ctrl--bottom-left{align-items:flex-start;bottom:0;left:0}.sm-ctrl--bottom-right{align-items:flex-end;bottom:0;right:0}.sm-map{-webkit-user-select:none;user-select:none}:where(.sm-map){width:100%;height:100%;position:relative}.sm-map__canvas{pointer-events:auto;touch-action:none;width:100%;height:100%;display:block}.sm-map__canvas:focus-visible{outline:2px solid var(--ring,Highlight)}.sm-map .scene-overlay{pointer-events:none;z-index:0;position:absolute;inset:0}.sm-flat{touch-action:none;background:#05070e;width:100%;height:100%;position:relative;overflow:hidden}.sm-flat__raster,.sm-flat__vector,.sm-flat__markers{width:100%;height:100%;position:absolute;inset:0}.sm-flat__raster{display:block}.sm-flat__vector,.sm-flat__markers{pointer-events:none}.sm-flat--interactive{cursor:grab}.sm-flat--cooperative{touch-action:pan-x pan-y}.sm-flat--dragging{cursor:grabbing}.sm-flat__graticule{fill:none;stroke:#ffffff47;stroke-width:1px}.sm-flat__frame{fill:none;stroke:#ffffff73;stroke-width:1px}.sm-flat__feature-dot{fill:#fffc}.sm-flat__feature-label{fill:#ffffffd9;paint-order:stroke;stroke:#000000a6;stroke-width:2.5px;font:10px/1 system-ui,sans-serif}.sm-flat__marker-dot{background:#4ea3ff;border:2px solid #fff;border-radius:50%;width:10px;height:10px;box-shadow:0 0 0 1px #00000073}.sm-system-map{border-radius:var(--sm-system-map-radius,6px);background:var(--sm-system-map-background,#ffffff0d);width:100%;color:var(--sm-system-map-ink,#ffffff9e);position:relative;overflow:hidden}.sm-system-map--background{pointer-events:none;background:0 0;border-radius:0;height:100%}.sm-system-map__svg{touch-action:pan-y;width:100%;height:auto;display:block}.sm-system-map--background .sm-system-map__svg{height:100%}.sm-system-map__svg--near :is(a,.sm-system-map__button):not([data-near]){pointer-events:none}.sm-system-map__button{cursor:pointer}.sm-system-map__button:focus{outline:none}.sm-system-map__reference{opacity:.6}.sm-system-map__band--muted{color:var(--sm-system-map-ink,#ffffff9e)}.sm-system-map__band--sky{color:var(--sm-system-map-sky,oklch(74.6% .16 232.661/.7))}.sm-system-map__band--amber{color:var(--sm-system-map-amber,oklch(92.4% .12 95.746/.8))}.sm-system-map__band-label{font-weight:600}.sm-system-map__band-label--muted{fill:var(--sm-system-map-ink,#ffffff9e)}.sm-system-map__band-label--sky{fill:var(--sm-system-map-sky-label,oklch(74.6% .16 232.661))}.sm-system-map__band-label--amber{fill:var(--sm-system-map-amber-label,oklch(92.4% .12 95.746/.9))}.sm-system-map__tip{z-index:10;background:var(--sm-system-map-tip-background,#0a0c14e6);width:max-content;color:var(--sm-system-map-tip-ink,#fffffff2);white-space:nowrap;pointer-events:none;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-radius:.25rem;padding:.25rem .5rem;font-size:.75rem;line-height:1.33333;position:absolute;transform:translate(-50%);box-shadow:0 1px 3px #0000001a,0 1px 2px -1px #0000001a}.sm-system-map__tip[hidden]{display:none}.sm-system-map__tip-title{font-weight:500}.sm-system-map__tip-sub{color:var(--sm-system-map-ink,#ffffff9e)}.sm-ctrl:has(>.sm-attribution:only-child){padding:0}.sm-ctrl--top-left:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 0 var(--sm-attribution-radius) 0}.sm-ctrl--top-right:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 0 0 var(--sm-attribution-radius)}.sm-ctrl--bottom-left:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 var(--sm-attribution-radius) 0 0}.sm-ctrl--bottom-right:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:var(--sm-attribution-radius) 0 0 0}.sm-attribution{--sm-attribution-radius:3px;border-radius:var(--sm-attribution-radius);color:#ffffffbf;white-space:nowrap;background:#0006;align-items:center;gap:.5em;max-width:100%;padding:1px 6px;font:11px/1.45 system-ui,sans-serif;display:flex}.sm-attribution a{color:inherit;text-decoration:none}.sm-attribution a:hover{color:#fff;text-decoration:underline}.sm-attribution__home{font-weight:600}.sm-attribution__group{text-overflow:ellipsis;overflow:hidden}.sm-attribution__label{color:#ffffff80}\n/*$vite$:1*/";document.head.appendChild(s);})();