* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  width: 100%;
  height: 100%;
  background: #000;
  overflow: hidden;
}

#scene {
  display: block;
  width: 100%;
  height: 100%;
}

#ui-root {
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  pointer-events: none;
}

/* v1.8.2 — fallback clearance other bottom-left panels reserve above the
   time-controls bar, before app.js's ResizeObserver measures its real
   height (see the .time-controls / .left-column comments below). */
:root {
  --time-bar-clearance: 100px;
}

/* v1.8.2 — was a borderless bar stretched to the full viewport width;
   redesigned as a centered floating card matching every other panel's
   look (dark background, 1px border, rounded corners), width shrunk to
   its content instead of the screen. */
.time-controls {
  pointer-events: auto;
  display: flex;
  width: fit-content;
  margin: 0 auto 12px;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
  font: 13px/1.4 system-ui, sans-serif;
}

.time-controls button,
.time-controls select,
.time-controls input {
  font: inherit;
  padding: 4px 8px;
  background: #222;
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
}

.time-controls .current-date {
  font-variant-numeric: tabular-nums;
}

/* v1.8.1 — the left column's panels (view-mode, surface controls, observer)
   used to each hardcode their own `position: fixed; top: Npx`, computed by
   guessing every sibling's height above it. That guess went stale whenever
   a later version added content to an earlier panel (v1.3's surface-controls
   hint, wrapped buttons) without anyone re-measuring every top offset below
   it — the exact bug this reported (.observer-panel silently overlapping
   and eating clicks meant for .surface-controls's "Stand Here" button). A
   flex column lets each panel's real rendered height push the next one
   down automatically, so no offset here can ever go stale again. `bottom`
   is set (not just `top`) so the column's height is viewport-bounded,
   giving flex children something concrete to size `flex: 1 1 auto` against
   for their own internal scrolling instead of running off-screen.
   v1.8.2 — `bottom` changed from a flat 12px to `--time-bar-clearance`:
   since .left-column is later in the DOM than .time-controls, an
   expanded Observer Mode panel was painting over the time-controls bar
   (and, at the old fixed offset, over .body-info-panel too) — the same
   stale-offset bug class this comment already describes, just at the
   bottom edge instead of the top.
   v1.12.2 — `--time-bar-clearance` alone wasn't enough: .settings-button
   is a separate `position: fixed` circle at `bottom: 120px` (see its own
   comment), never counted in this column's height budget, so a tall
   .body-info-panel could still render text underneath it once the
   column's real bottom edge (viewport height minus clearance, which can
   be well past 156px on a short time bar) reached down that far —
   confirmed happening on a real desktop viewport. `max()` with 164px
   (settings-button's own bottom:120 + its 36px height + an 8px gap —
   the same numbers that already size the button, not a fresh guess)
   guarantees the column stops above the button regardless of how small
   --time-bar-clearance is, while still growing past it via
   --time-bar-clearance whenever the time bar itself needs more room. */
.left-column {
  position: fixed;
  top: 12px;
  left: 12px;
  bottom: max(var(--time-bar-clearance), 164px);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  pointer-events: none; /* children opt back in individually, same convention as #ui-root */
}

.view-mode-controls {
  pointer-events: auto;
  display: flex;
  gap: 6px;
  flex: 0 0 auto;
}

/* v1.11.1 — mirrors .left-column: .event-toolkit used to hardcode
   `top: 120px` as a guess at .ephemeris-hud's height, the same
   stale-offset bug class .left-column was built to eliminate in v1.8.1,
   except this side was never migrated. A flex column makes
   .ephemeris-hud's real rendered height (which varies — see its own
   comment) push .event-toolkit down automatically; no guess needed. */
.right-column {
  position: fixed;
  top: 12px;
  right: 12px;
  bottom: var(--time-bar-clearance);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  pointer-events: none;
}

.ephemeris-hud {
  pointer-events: auto;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 14px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
  font: 13px system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
}

.ephemeris-hud-title {
  font-weight: 600;
}

.ephemeris-hud-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Same fix as .observer-panel-body[hidden] above — the plain class rule's
   explicit `display` otherwise defeats the `hidden` attribute entirely. */
.ephemeris-hud-body[hidden] {
  display: none;
}

.view-mode-controls button {
  font: 13px system-ui, sans-serif;
  padding: 6px 10px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
}

.view-mode-controls button:disabled {
  color: #666;
  cursor: not-allowed;
}

.view-mode-controls button.active {
  border-color: #6cf;
  color: #6cf;
}

.surface-controls {
  pointer-events: auto;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  max-width: 300px;
  flex: 0 0 auto;
}

.surface-controls-hint {
  flex-basis: 100%;
  font-size: 11px;
  color: #ccc;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.9);
}

.surface-controls select,
.surface-controls input,
.surface-controls button {
  font: 13px system-ui, sans-serif;
  padding: 6px 8px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
}

.surface-controls input {
  width: 56px;
}

.observer-panel {
  pointer-events: auto;
  width: 300px;
  /* max-height:100% (not a hardcoded px value) caps this at whatever room
     is actually left in .left-column's fixed viewport-derived height,
     after view-mode-controls/surface-controls above it — collapsed (title
     only) sizes to its small content instead of stretching to fill the
     column (flex-grow:0), while a long expanded result still can't push
     past the column's bottom edge (max-height:100%, with overflow-y
     scrolling internally instead). min-height:0 lets flexbox actually
     shrink it below content size when the column is short. */
  flex: 0 1 auto;
  min-height: 0;
  max-height: 100%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 14px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
  font: 13px system-ui, sans-serif;
}

.observer-panel-title {
  font-weight: 600;
}

.observer-panel-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* The class rule above sets `display` directly, which otherwise beats the
   browser's default `[hidden] { display: none }` UA-stylesheet rule (author
   CSS always wins over UA CSS, regardless of selector order) — this is why
   collapsing previously had no visible effect. Combined class+attribute
   selector outranks the plain class rule, so this restores it. */
.observer-panel-body[hidden] {
  display: none;
}

.observer-panel-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.observer-panel-field {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.observer-panel-field span {
  font-size: 12px;
  color: #aaa;
}

.observer-panel-field input,
.observer-panel-field select {
  font: 13px system-ui, sans-serif;
  padding: 4px 6px;
  background: #222;
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
  width: 130px;
}

.observer-panel-form button {
  font: 13px system-ui, sans-serif;
  padding: 4px 6px;
  background: #222;
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
}

.observer-panel-hint {
  font-size: 11px;
  color: #888;
}

.observer-panel-error {
  margin: 0;
  padding: 4px 0;
  white-space: pre-wrap;
  font: 12px system-ui, sans-serif;
  color: #f66;
}

.observer-panel-results {
  margin: 0;
  padding: 6px 0;
  white-space: pre-wrap;
  font: 12px system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  color: #eee;
}

.observer-panel-canvas {
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid #444;
  border-radius: 4px;
  align-self: center;
}

/* v1.8.3 — used to be its own `position: fixed` stack anchored to the
   bottom-left corner, growing UPWARD, independently of .left-column
   (view-mode/surface/observer) growing DOWNWARD from the top-left corner
   — two independently-sized stacks sharing the same corner with no
   combined height budget, so they could still collide in the middle of
   the screen when both were large (an expanded Observer Mode result +
   a shown Planet Info Panel). Now a plain flex item inside .left-column
   (app.js appends it there), sharing that column's single bounded
   height and its siblings' `min-height: 0` shrink-to-fit behavior.
   `order: 1` + `margin-top: auto` keep it visually last (bottom-most)
   regardless of where app.js actually creates/appends it in the DOM. */
.body-info-panel {
  pointer-events: auto;
  order: 1;
  margin-top: auto;
  width: 300px;
  flex: 0 1 auto;
  min-height: 0;
  max-height: 100%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 14px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
  font: 13px system-ui, sans-serif;
}

.body-info-panel-title {
  font-weight: 600;
}

.body-info-panel-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Same fix as .observer-panel-body[hidden] above — the plain class rule's
   explicit `display` otherwise defeats the `hidden` attribute entirely. */
.body-info-panel-body[hidden] {
  display: none;
}

.body-info-panel-results {
  margin: 0;
  padding: 6px 0;
  white-space: pre-wrap;
  font: 12px system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  color: #eee;
}

.body-info-panel-hint {
  font-size: 11px;
  color: #888;
}

.attribution-footer {
  pointer-events: auto;
  position: fixed;
  right: 8px;
  bottom: 4px;
  font: 11px system-ui, sans-serif;
  color: #888;
}

.attribution-footer a {
  color: #99b8d6;
}

/* v1.14 — used to be a flat `bottom: 120px` on every viewport ("one
   position works everywhere", clearing .touch-joystick-base's 20+90px
   with margin to spare). On a mouse/trackpad desktop that joystick never
   renders at all (touch-controls.js is a no-op without a coarse pointer),
   so the 120px gap was pure dead space floating the gear visibly above
   .time-controls' own bottom edge instead of sitting flush with it.
   bottom:12px here matches .time-controls' own margin-bottom from
   #ui-root's bottom-anchored edge (see .time-controls below) — same
   baseline, visually aligned. Touch devices restore the original 120px
   clearance below, via the same `(pointer: coarse)` signal
   touch-controls.js already uses to detect them. */
.settings-button {
  pointer-events: auto;
  position: fixed;
  left: 20px;
  bottom: 12px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  font-size: 16px;
  cursor: pointer;
}

@media (pointer: coarse) {
  .settings-button {
    bottom: 120px; /* clears .touch-joystick-base (bottom:20px, 90px tall) + 8px gap, unchanged from before */
  }
}

/* v1.12.1 — `.settings-panel[hidden]` must come first (or otherwise win
   on specificity) so the JS `panel.hidden` toggle actually hides it: an
   author `display: flex` on a plain class selector overrides the
   browser's default `[hidden] { display: none }` UA rule regardless of
   source order (author origin always beats user-agent origin at equal
   specificity) — the panel was rendering permanently open on every load,
   click-to-close included, contributing real clutter on top of the
   overlap issues below. */
.settings-panel[hidden] {
  display: none;
}

/* v1.14 — bottom tracks .settings-button's own default/coarse-pointer
   split above (button bottom + 36px button height + 8px gap): 12+36+8=56
   by default, 120+36+8=164 on touch — same math as before (the old flat
   170px), just now matching wherever the button actually moved to. */
.settings-panel {
  pointer-events: auto;
  position: fixed;
  left: 20px;
  bottom: 56px;
  width: 220px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 14px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
  font: 13px system-ui, sans-serif;
}

@media (pointer: coarse) {
  .settings-panel {
    bottom: 164px;
  }
}

.settings-panel-title {
  font-weight: 600;
}

.settings-panel-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.settings-panel-hint {
  font-size: 11px;
  color: #888;
}

.event-toolkit {
  pointer-events: auto;
  flex: 0 1 auto;
  min-height: 0;
  max-height: 100%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 340px;
  background-color: rgba(10, 10, 20, 0.75); /* .event-toolkit-title/-lab already paint their own opaque backgrounds; this is only the scroll-shadow trick's own backdrop, see the shared rule below */
}

/* v1.13 — .observer-panel/.event-toolkit/.body-info-panel are each their
   own overflow-y:auto scroll region (v1.8.1-v1.12.2's shared-column-
   budget design, see .left-column's own comment above) so a tall result
   never overlaps a sibling panel — but with no visual cue that a panel
   has more content below, a browser's overlay scrollbar (invisible until
   hovered, the default on macOS and increasingly elsewhere) makes it
   look like content is simply missing rather than scrollable. Confirmed
   on a real 1366x768 viewport: Observer Mode ~157px, Event Toolkit
   ~168px, and even the static body-info-panel ~51px of content sits
   below the fold by default.
   Fix: the classic pure-CSS "scrolling shadows without JavaScript"
   technique (Lea Verou) — two gradients that scroll WITH the content
   (background-attachment: local) mask a shadow at whichever end is
   already fully scrolled into view, while two shadow gradients stay
   fixed to the element (background-attachment: scroll) and only show
   through where the mask doesn't cover them. No JS, and it tracks scroll
   position exactly (no separate "am I scrolled to the end" state to keep
   in sync). The masking gradients match each panel's own flat background
   color so they blend in rather than adding a visible seam. */
.observer-panel,
.event-toolkit,
.body-info-panel {
  background-image:
    linear-gradient(rgba(10, 10, 20, 0.75) 30%, rgba(10, 10, 20, 0)),
    linear-gradient(rgba(10, 10, 20, 0), rgba(10, 10, 20, 0.75) 70%),
    radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0));
  background-position: top, bottom, top, bottom;
  background-repeat: no-repeat;
  background-size: 100% 24px, 100% 24px, 100% 10px, 100% 10px;
  background-attachment: local, local, scroll, scroll;
}

.event-toolkit-title {
  padding: 8px 12px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
  font: 600 13px system-ui, sans-serif;
}

.event-toolkit-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Same fix as .observer-panel-body[hidden] above — the plain class rule's
   explicit `display` otherwise defeats the `hidden` attribute entirely. */
.event-toolkit-body[hidden] {
  display: none;
}

.event-toolkit-type-select {
  font: 13px system-ui, sans-serif;
  padding: 6px 8px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
}

.event-toolkit-lab {
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 14px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
  font: 13px system-ui, sans-serif;
}

.event-toolkit-lab-title {
  font-weight: 600;
}

.event-toolkit-lab-fixed {
  font-size: 11px;
  color: #888;
}

.event-toolkit-lab-form {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.event-toolkit-lab-form input,
.event-toolkit-lab-form select,
.event-toolkit-lab-form button {
  font: 13px system-ui, sans-serif;
  padding: 4px 6px;
  background: #222;
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
}

.event-toolkit-lab-form input[type='date'] {
  width: 130px;
}

.event-toolkit-lab-form input[type='number'] {
  width: 56px;
}

.event-toolkit-lab-error {
  margin: 0;
  padding: 4px 0;
  white-space: pre-wrap;
  font: 12px system-ui, sans-serif;
  color: #f66;
}

.event-toolkit-lab-results {
  margin: 0;
  padding: 6px 0;
  white-space: pre-wrap;
  font: 12px system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  color: #eee;
}

.event-toolkit-lab-canvas {
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid #444;
  border-radius: 4px;
  align-self: center;
}

.event-toolkit-lab-scrub {
  width: 100%;
}

.event-toolkit-export {
  display: flex;
  gap: 6px;
}

.event-toolkit-export button {
  font: 12px system-ui, sans-serif;
  padding: 4px 8px;
  background: #222;
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
}

.event-toolkit-export button:disabled {
  color: #666;
  cursor: not-allowed;
}

.touch-joystick-base {
  pointer-events: auto;
  touch-action: none;
  position: fixed;
  bottom: 20px;
  left: 20px;
  width: 90px;
  height: 90px;
  border-radius: 50%;
  background: rgba(10, 10, 20, 0.5);
  border: 1px solid #444;
}

.touch-joystick-stick {
  pointer-events: none;
  position: absolute;
  top: 50%;
  left: 50%;
  width: 40px;
  height: 40px;
  margin: -20px 0 0 -20px;
  border-radius: 50%;
  background: rgba(150, 180, 220, 0.6);
  border: 1px solid #99b8d6;
}

.touch-vertical-buttons {
  pointer-events: auto;
  position: fixed;
  bottom: 20px;
  right: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.touch-vertical-buttons button {
  touch-action: none;
  font: 20px system-ui, sans-serif;
  width: 48px;
  height: 48px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
}

.touch-cycle-buttons {
  pointer-events: auto;
  position: fixed;
  bottom: 20px;
  right: 12px;
  display: flex;
  gap: 8px;
}

.touch-cycle-buttons button {
  touch-action: none;
  font: 20px system-ui, sans-serif;
  width: 48px;
  height: 48px;
  background: rgba(10, 10, 20, 0.75);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
}

/* Same fix as .observer-panel-body[hidden] above, missed here: the plain
   class rules' own `display: flex` otherwise beats the browser's default
   `[hidden] { display: none }` UA rule, so touch-controls.js's
   `.hidden = true/false` mode-switching had no visual effect — both
   groups rendered fixed in the same bottom-right spot on every touch
   device regardless of view mode, overlapping each other (confirmed:
   Top-Down mode on a phone showed a stray ▲ poking out above the ◀▶ row,
   the vertical buttons' ▼ hidden behind it). */
.touch-vertical-buttons[hidden],
.touch-cycle-buttons[hidden] {
  display: none;
}

.hover-label {
  position: fixed;
  pointer-events: none;
  z-index: 10;
  padding: 3px 8px;
  background: rgba(10, 10, 20, 0.85);
  color: #eee;
  border: 1px solid #444;
  border-radius: 4px;
  font: 12px system-ui, sans-serif;
}

.collapsible-title {
  cursor: pointer;
  user-select: none;
}

.constellation-labels {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1; /* under hover-label/panels, above the canvas */
}

.analysis-target-marker {
  position: fixed;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 2; /* above constellation labels, matches hover-label's tier */
  padding: 2px 8px 2px 20px;
  border: 1px solid #f80;
  border-radius: 10px;
  background: rgba(10, 10, 20, 0.4);
  color: #f80;
  font: 11px system-ui, sans-serif;
  white-space: nowrap;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.9);
}

.analysis-target-marker::before {
  content: '';
  position: absolute;
  left: 6px;
  top: 50%;
  width: 10px;
  height: 10px;
  transform: translateY(-50%);
  border: 1px solid #f80;
  border-radius: 50%;
}

.constellation-label {
  position: absolute;
  transform: translate(-50%, -50%);
  color: #7f93b8;
  font: 11px system-ui, sans-serif;
  letter-spacing: 0.5px;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.9);
  white-space: nowrap;
}

/* v1.11.1 — first responsive breakpoint in this stylesheet. 700px is the
   smallest width that comfortably fits .left-column's 300px panels next
   to .right-column's 340px .event-toolkit with margin (300+340+12*3=676,
   asserted in scripts/smoke-test.js so this number can't silently drift
   out of sync with those widths). Below it, side-by-side panels
   mathematically cannot fit a typical 375-414px phone screen without
   full overlap — full-width single-column panels instead.
   v1.12.1 — that width fix was necessary but not sufficient: it widened
   .observer-panel/.body-info-panel/.event-toolkit to fill the viewport,
   but .left-column and .right-column themselves are still two
   independent `position: fixed` boxes each anchored to their own corner
   and shrink-wrapped to their widest child — once that child is
   (100vw - 24px) wide, BOTH columns individually become nearly full
   viewport width and land on IDENTICAL bounding boxes (verified: on a
   390px-wide viewport both measured exactly left:12/right:378/top:12/
   bottom:582 — completely coincident, not just visually close). Every
   panel in one column then overlaps whatever panel happens to occupy the
   same vertical range in the other, regardless of how narrow any single
   panel's own width fix made it. This is the same "two independently-
   sized stacks sharing one corner with no combined height budget" bug
   class .left-column's own v1.8.1 fix and .right-column's own v1.11.1
   fix already eliminated *within* each column — it was just never
   applied *between* the two columns. Fix: at this width, un-fix both
   columns (`position: static`) so they fall into normal document flow as
   plain stacked siblings (left-column's children first, then right-
   column's — both are already `display: flex; flex-direction: column`
   internally, so this only changes how the two columns relate to each
   other, not their own internal layout) — each one's real rendered
   height now pushes the next down automatically, the same principle
   extended one level up. Collapsible sections (.event-toolkit,
   .observer-panel) default to collapsed, keeping the combined stack
   short enough to fit typical 660-900px-tall phone viewports in the
   common case, but several panels expanded at once (or a short
   landscape viewport) can still exceed it — confirmed: with nothing to
   scroll, the stack simply clipped at the top, hiding the 3D scene
   behind solid UI with no way to see or reach it. Fix: make #ui-root
   itself the scroll container on mobile (see its own rule below)
   instead of leaving it a dead, unscrollable overlay. */
@media (max-width: 700px) {
  #ui-root {
    /* Trades the ~24px side margin's drag-to-look-through-empty-space
       (pointer-events: none's whole point, desktop-only concern — at
       this width panels already fill nearly the full viewport width)
       for the ability to scroll past a UI stack taller than the
       viewport. top:0 (was unset/bottom-anchored-only) turns the auto-
       height box into an actual scroll region. */
    top: 0;
    overflow-y: auto;
    pointer-events: auto;
    /* v1.14 — now that body-info-panel/ephemeris-hud collapse by default
       too (see their own CSS comments), the full stack is often much
       shorter than the viewport, and without this it simply sits at the
       top: the settings/time-controls/attribution cluster floated in the
       middle of the screen with empty scene below it, not touching the
       bottom edge the way it does on desktop. Flex + the `order`/
       `margin-top: auto` trio below pushes that cluster down to the real
       bottom whenever there's slack — and falls back to plain top-to-
       bottom stacking (this scroll container's existing behavior) once
       content is tall enough to fill the viewport on its own. */
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .observer-panel,
  .body-info-panel,
  .event-toolkit {
    width: calc(100vw - 24px);
  }

  .left-column,
  .right-column {
    position: static;
    width: calc(100vw - 24px);
    max-width: calc(100vw - 24px);
    max-height: none;
    bottom: auto;
    margin: 0 auto 8px;
  }

  /* .time-controls is `width: fit-content` sized to its full desktop
     button row (measured 546px), wider than a 390px phone — it overflowed
     the viewport with the rightmost buttons pushed off-screen and
     unreachable. flex-wrap lets it fold onto a second line instead of
     spilling sideways, capped to the viewport like every other panel here. */
  .time-controls {
    flex-wrap: wrap;
    justify-content: center;
    max-width: calc(100vw - 24px);
    order: 2;
  }

  /* .attribution-footer is `position: fixed; bottom: 4px` — a fine corner
     on desktop, where .time-controls is a short one-line bar nowhere near
     it. Wrapping .time-controls to 2 lines above makes its box (anchored
     to #ui-root's bottom edge, growing upward) tall enough to reach that
     same corner and print over this text (measured: 18px overlap).
     Putting it back in normal flow sidesteps that. v1.14 — `order: 3`
     moves it after .time-controls (was DOM order: footer, button,
     time-controls — attribution printed above the time bar, not below
     it); #ui-root's new flex order lets this file say where things go
     without touching app.js's DOM append order. */
  .attribution-footer {
    position: static;
    display: block;
    width: fit-content;
    margin: 0 0 8px;
    text-align: center;
    order: 3;
  }

  /* v1.14 — `order: 1` + `margin-top: auto` make this the first item of
     the settings/time-controls/attribution trio (see their own `order`
     above) and the one that absorbs #ui-root's leftover flex space,
     pushing all three down to the real bottom edge together — see
     #ui-root's own comment for why that space exists now. position:static
     (overriding the base rule's `position: fixed; bottom: 12px`, itself a
     v1.14 change — see that rule's own comment) is unchanged from before:
     this button still needs to join the mobile stack's normal flow, not
     float independently. */
  .settings-button {
    position: static;
    order: 1;
    margin-top: auto;
    margin-bottom: 8px;
  }
}
