/* ============================================================================
   MARS DECK — deck.css
   Zero dependencies. No webfonts (no render-blocking external request), no
   frameworks. One accent colour: Mars rust. Everything else is signal grey.
   ========================================================================== */

:root {
  --bg:         #05060a;
  --bg-lift:    #0a0c13;
  --panel:      rgba(13, 16, 23, 0.88);
  --panel-solid:#0d1017;
  --border:     rgba(255, 255, 255, 0.085);
  --border-lift:rgba(255, 255, 255, 0.16);

  --accent:     #e2673c;   /* Mars rust — the single accent */
  --accent-hot: #ff8a55;
  --accent-dim: rgba(226, 103, 60, 0.16);

  --earth:      #4a9fd9;   /* used only for "you are here" cues */

  --text:       #e8eaf0;
  --text-mid:   #9aa3b4;
  --text-dim:   #656d7e;

  --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Mono', Consolas, monospace;
  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0; height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  overflow: hidden;
  color-scheme: dark;
}

/* --- the canvas --------------------------------------------------------- */

#deck {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  display: block;
  cursor: grab;
  touch-action: none;
}
#deck.dragging { cursor: grabbing; }

/* --- chrome shared ------------------------------------------------------ */

.chrome {
  position: fixed;
  z-index: 10;
  pointer-events: none;
}
.chrome > * { pointer-events: auto; }

.label {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* --- top bar ------------------------------------------------------------ */

#topbar {
  top: 0; left: 0; right: 0;
  display: flex; align-items: center; gap: 24px;
  padding: 11px 22px 10px;
  background: linear-gradient(180deg, rgba(5,6,10,0.92) 0%, rgba(5,6,10,0) 100%);
}

/* --- one home per feature --------------------------------------------------
   On desktop the rail is always open, so Ambient and Ask each had TWO front
   doors: a toolbar chip and a card. Stephan's read was right — two entry
   points to one feature is not twice as findable, it is ambiguous. The card
   wins both times because it is strictly bigger than the chip: the Radio card
   holds the whole player, and the Ask card leads with a written question
   rather than the word "Ask".

   HIDDEN, NOT DELETED. The markup stays so the phone keeps both chips (where
   the rail is a closed sheet and the chip is the only one-press access), and
   so deck.js keeps one code path for both layouts — no branch, nothing to
   drift. The Ambient chip's level dot therefore still works where it earns
   its keep, which is the small screen. */
@media (min-width: 821px) {
  #t-ambient, #t-ask { display: none; }
}

.wordmark {
  display: flex; align-items: baseline; gap: 9px;
  font-family: var(--mono);
  font-size: 15px; font-weight: 600;
  letter-spacing: 0.24em; text-transform: uppercase;
  white-space: nowrap;
}
.wordmark .mars { color: var(--accent); }
.wordmark .deck { color: var(--text); }
.wordmark .tag {
  font-size: 9px; letter-spacing: 0.2em;
  color: var(--text-dim); padding-left: 6px;
  border-left: 1px solid var(--border);
}

#clock {
  font-family: var(--mono);
  font-size: 11px; letter-spacing: 0.1em;
  color: var(--text-mid);
  white-space: nowrap;
}
#clock .live {
  color: var(--accent);
  margin-right: 8px;
}
#clock .live::before {
  content: '';
  display: inline-block;
  width: 5px; height: 5px; margin-right: 6px;
  border-radius: 50%;
  background: var(--accent);
  vertical-align: middle;
  animation: pulse 2.6s var(--ease) infinite;
}
#clock.scrubbing .live { color: var(--text-dim); }
#clock.scrubbing .live::before { animation: none; background: var(--text-dim); }

@keyframes pulse {
  0%, 100% { opacity: 1;   box-shadow: 0 0 0 0 rgba(226,103,60,0.5); }
  50%      { opacity: 0.5; box-shadow: 0 0 0 5px rgba(226,103,60,0); }
}

/* Empty, but .chrome > * grants pointer-events, so this stretch of nothing
   was swallowing clicks and drags across the top of the canvas. */
.spacer { flex: 1; pointer-events: none; }

/* --- scrollbars ------------------------------------------------------------
   The rail and the dossier both scroll, and a default Windows scrollbar is a
   fat grey slab that breaks the noir the moment the rail gets long enough to
   need one. This makes it a hairline in the deck's own palette: invisible
   until you hover the panel, then a thin rust thumb.

   Firefox gets scrollbar-width/color; WebKit and Chromium get the ::-webkit
   pseudo-elements. Both are ignored where unsupported, which is the correct
   failure — a browser that styles nothing still scrolls fine. */
#rail, #dossier {
  scrollbar-width: thin;
  scrollbar-color: rgba(226,103,60,0.28) transparent;
}
#rail::-webkit-scrollbar,
#dossier::-webkit-scrollbar { width: 7px; }

#rail::-webkit-scrollbar-track,
#dossier::-webkit-scrollbar-track { background: transparent; }

#rail::-webkit-scrollbar-thumb,
#dossier::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,0.10);
  border-radius: 4px;
  border: 2px solid transparent;      /* inset, so the thumb reads as a hairline */
  background-clip: content-box;
  transition: background .2s var(--ease);
}
#rail:hover::-webkit-scrollbar-thumb,
#dossier:hover::-webkit-scrollbar-thumb { background: rgba(226,103,60,0.45); background-clip: content-box; }

#rail::-webkit-scrollbar-thumb:hover,
#dossier::-webkit-scrollbar-thumb:hover { background: var(--accent); background-clip: content-box; }

/* Corner where two bars would meet — never styled by default, always ugly. */
#rail::-webkit-scrollbar-corner,
#dossier::-webkit-scrollbar-corner { background: transparent; }

/* --- buttons ------------------------------------------------------------ */

.btn {
  display: inline-block;      /* so an <a class="btn"> matches a <button> */
  text-decoration: none;
  font-family: var(--mono);
  font-size: 10px; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--text-mid);
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 7px 11px;
  cursor: pointer;
  white-space: nowrap;
  transition: color .18s var(--ease), border-color .18s var(--ease),
              background .18s var(--ease);
}
.btn:hover { color: var(--text); border-color: var(--border-lift); background: rgba(255,255,255,0.06); }
.btn.on {
  color: var(--accent);
  border-color: rgba(226,103,60,0.5);
  background: var(--accent-dim);
}
/* A control that cannot work must not look like one that can. The only case
   today is Ambient on a browser with no Web Audio; the title says why. */
.btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
  background: transparent;
}

/* --- Ask the Deck ----------------------------------------------------------
   Everything here lives INSIDE the existing dossier. No new panel, no new
   breakpoint: the dossier is already mobile-tuned and already collapses the
   bottom sheet when it opens, so the map never ends up behind two stacked
   surfaces. That is the whole reason Ask renders here and not in a chat
   window of its own. */
/* The panel's own label. Quiet and small on purpose — the input below it is
   the subject, and a 30px rhetorical question above a field is filler that
   also happened to wrap onto two ragged lines in a 320px rail. */
.ask-eyebrow {
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--text-dim); margin: 0 0 11px;
  display: flex; align-items: center; gap: 9px;
}
.ask-eyebrow::before {
  content: ''; width: 15px; height: 1px; flex: 0 0 auto; background: var(--accent);
}

/* ⛔ A SECOND .ask-q USED TO LIVE HERE AND IT WAS BOTH REDUNDANT AND USELESS.
   One already existed further down doing the job properly (9px mono, quiet —
   context, not a headline), and this one fought it. Worse, NEITHER won: the
   error and "thinking" states set `class="dos-name ask-q"`, and .dos-name is
   declared later at equal specificity, so 30px beat both every time. That is
   why Stephan's quota message rendered a three-line shout.
   The fix was not a stronger selector — it was to stop putting an
   object-name class on a sentence. See openAsk/askError in deck.js. */

/* --- your earlier questions -------------------------------------------------
   A list of doors, not a transcript. Each row shows the question at full
   weight and the answer clamped to two lines underneath — enough to recognise
   which one you want without the panel turning into the chat log the spec
   rules out. */
.ask-hist {
  display: block; width: 100%; text-align: left;
  background: none; border: none; border-bottom: 1px solid var(--border);
  padding: 10px 2px; cursor: pointer;
  transition: background .16s var(--ease), padding-left .16s var(--ease);
}
.ask-hist:hover { background: rgba(226,103,60,0.07); padding-left: 8px; }
.ask-hist:last-of-type { border-bottom: none; }
.ah-q {
  display: block; font-size: 12.5px; line-height: 1.4; color: var(--text);
}
.ah-a {
  display: block; margin-top: 4px;
  font-size: 11px; line-height: 1.45; color: var(--text-dim);
  /* Two lines, then ellipsis. -webkit-line-clamp is the only thing that does
     this reliably and it is supported everywhere the deck runs. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.ask-hist:hover .ah-q { color: var(--accent); }
.ask-hist-note {
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border);
  font-family: var(--mono); font-size: 9px; line-height: 1.7;
  letter-spacing: 0.05em; color: var(--text-dim);
}
.ask-hist-note a { color: var(--accent); text-decoration: none; }
.ask-hist-note a:hover { text-decoration: underline; }

.ask-input {
  width: 100%;
  box-sizing: border-box;
  margin: 4px 0 2px;
  padding: 11px 12px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text);
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  transition: border-color .18s var(--ease), background .18s var(--ease);
}
.ask-input::placeholder { color: var(--text-dim); opacity: .65; }
.ask-input:focus {
  outline: none;
  border-color: rgba(226,103,60,0.55);
  background: rgba(255,255,255,0.06);
}

/* The rail invitation. A chip at the end of a scrolling toolbar is not how you
   introduce the headline feature — this shows a real question so it is obvious
   what the thing is FOR, and reads as a prompt rather than a button. */
.ask-open {
  display: flex; align-items: center; gap: 10px; width: 100%;
  text-align: left; cursor: pointer;
  margin: 2px 0 10px;
  padding: 11px 12px;
  background: rgba(226,103,60,0.07);
  border: 1px solid rgba(226,103,60,0.28);
  border-radius: 3px;
  color: var(--text);
  font-family: var(--mono);
  font-size: 11px; line-height: 1.5; letter-spacing: 0.02em;
  transition: background .18s var(--ease), border-color .18s var(--ease);
}
.ask-open:hover, .ask-open:focus-visible {
  background: rgba(226,103,60,0.13);
  border-color: rgba(226,103,60,0.5);
  outline: none;
}
.ask-open-q { flex: 1 1 auto; color: var(--text-dim); }
.ask-open:hover .ask-open-q { color: var(--text); }
.ask-open-go { flex: 0 0 auto; color: var(--accent); font-size: 13px; }

/* --- tours -----------------------------------------------------------------
   The list lives in the rail like every other card. The CAPTION is created and
   destroyed by the tour itself and exists nowhere else — a tour is the map
   filling the screen while something else drives, so a permanent surface would
   defeat the entire point. */
.tour-btn {
  display: block; width: 100%; text-align: left; cursor: pointer;
  margin-bottom: 6px; padding: 9px 11px;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border); border-radius: 3px;
  transition: background .18s var(--ease), border-color .18s var(--ease);
}
.tour-btn:last-child { margin-bottom: 10px; }
.tour-btn:hover, .tour-btn:focus-visible {
  background: rgba(226,103,60,0.09);
  border-color: rgba(226,103,60,0.4);
  outline: none;
}
.tour-t { display: block; font-family: var(--mono); font-size: 10px;
          letter-spacing: 0.12em; text-transform: uppercase;
          color: var(--text); margin-bottom: 3px; }
.tour-btn:hover .tour-t { color: var(--accent); }
.tour-b { display: block; font-size: 11px; line-height: 1.45; color: var(--text-dim); }

/* --- centre the map --------------------------------------------------------
   Was a five-button d-pad in a 56px rail. Two things were wrong with that:
   the pad was 82px wide so its left arrow was clipped off the screen, and
   four arrows is a great deal of chrome to compensate for a MISSING GESTURE.
   Two-finger drag now pans on touch, desktop already had shift-drag and arrow
   keys, so the only control still worth drawing is the one that fixes the
   actual bug — there was no way back to centre after a tour or a shared link.

   One small mark, bottom-left, in the space the scale label used to occupy.
   Quiet by default; it lights only while the view is actually off-centre, so
   it announces itself exactly when it is the thing you need. */
#cam-home {
  left: 20px; bottom: 74px;
  display: flex; align-items: center; gap: 8px;
  padding: 7px 11px 7px 9px;
  cursor: pointer;
  font-size: 12px; line-height: 1;
  color: var(--text-dim);
  background: rgba(10,12,18,0.6);
  border: 1px solid var(--border); border-radius: 3px;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  pointer-events: auto;
  transition: color .16s var(--ease), border-color .16s var(--ease),
              background .16s var(--ease), opacity .3s var(--ease);
  -webkit-tap-highlight-color: transparent;
  opacity: 0.5;
}
#cam-home span {
  font-family: var(--mono); font-size: 8.5px;
  letter-spacing: 0.16em; text-transform: uppercase;
}
#cam-home:hover, #cam-home:focus-visible {
  color: var(--accent); border-color: rgba(226,103,60,0.45);
  background: rgba(226,103,60,0.10); opacity: 1; outline: none;
}
/* Lit and fully opaque only while the map is actually displaced. */
#cam-home.off {
  color: var(--accent); border-color: rgba(226,103,60,0.5); opacity: 1;
}
body.sky-mode #cam-home { display: none; }

/* Stowed while the Navigator card is open and holding Centre for us. The class
   is only ever set above 820px (see paintCam), so the phone keeps this button
   unconditionally — there the rail is a closed sheet and this is the only way
   back. Never make this a media query: the state depends on whether the card
   is FOLDED, which CSS cannot see. */
#cam-home.stowed {
  opacity: 0; pointer-events: none; transform: translateY(4px);
}

@media (max-width: 820px) {
  /* Above the time strip, and the word drops so it stays a small mark. */
  #cam-home { left: 12px; bottom: 62px; padding: 9px 10px; }
  #cam-home span { display: none; }
}
@media (max-width: 820px) and (max-height: 480px) { #cam-home { display: none; } }

#tourBar {
  position: fixed; left: 50%; bottom: 86px; transform: translateX(-50%);
  z-index: 30; pointer-events: auto;
  display: flex; align-items: center; gap: 14px;
  max-width: min(720px, calc(100vw - 32px));
  padding: 14px 16px 14px 18px;
  background: rgba(10,12,18,0.86);
  border: 1px solid var(--border);
  border-radius: 4px;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  animation: tour-in .5s var(--ease);
}
@keyframes tour-in { from { opacity: 0; transform: translate(-50%, 10px); } }
#tourBar .tour-k { font-family: var(--mono); font-size: 9.5px;
                   letter-spacing: 0.16em; text-transform: uppercase;
                   color: var(--accent); white-space: nowrap; flex: 0 0 auto; }
#tourBar .tour-n { font-size: 15px; line-height: 1.5; color: var(--text);
                   flex: 1 1 auto; }
#tourBar .tour-x {
  flex: 0 0 auto; cursor: pointer;
  background: none; border: 1px solid var(--border); border-radius: 3px;
  color: var(--text-dim); font-family: var(--mono); font-size: 8.5px;
  letter-spacing: 0.14em; text-transform: uppercase; padding: 5px 9px;
  transition: color .18s var(--ease), border-color .18s var(--ease);
}
#tourBar .tour-x:hover { color: var(--accent); border-color: rgba(226,103,60,0.45); }

/* On a phone the bar sits above the time strip and the caption gets the room;
   the chapter marker is the first thing to go. */
@media (max-width: 820px) {
  #tourBar { bottom: 112px; padding: 12px 14px; gap: 10px;
             max-width: calc(100vw - 24px); }
  #tourBar .tour-k { display: none; }
  #tourBar .tour-n { font-size: 14px; }
}
@media (prefers-reduced-motion: reduce) { #tourBar { animation: none; } }

/* --- the Ambient chip's level dot ------------------------------------------
   The chip answers one question — "is something happening?" — with a dot that
   breathes on the real audio level.

   NOT a waveform. The chip is ten pixels tall and a wave in there is a grey
   smudge; that is the same lesson the favicon taught at 16px, where the mark
   had to become a disc and a ring rather than detail. And two waveforms on one
   screen would compete.

   The folded Radio card answers the other question — "what?" — by naming the
   track. Two controls, two different answers, no duplication. */
#ambDot {
  display: none;
  width: 5px; height: 5px; border-radius: 50%;
  margin-right: 6px; vertical-align: middle;
  background: var(--accent);
  transform: scale(var(--amb, 1));
  transition: transform .09s linear;
}
.btn.on #ambDot { display: inline-block; }
@media (prefers-reduced-motion: reduce) {
  #ambDot { transform: none; transition: none; }
}

/* --- navigator -------------------------------------------------------------
   A wide band, not a square, for the same reason the player is: the deck is a
   landscape view, so a square minimap of a landscape viewport wastes its top
   and bottom on nothing. 16:9 matches what it is describing.

   It carries the accent border only while the view is off-centre — the panel
   lights up exactly when it is the thing you need, which is the rule #cam-home
   already followed. */
#navStage {
  position: relative;
  width: 100%; aspect-ratio: 16 / 9;
  border: 1px solid var(--border); border-radius: 3px;
  background: radial-gradient(ellipse at 50% 50%,
              rgba(226,103,60,0.05), rgba(5,6,10,0) 70%),
              rgba(255,255,255,0.02);
  overflow: hidden;
  cursor: crosshair;
  touch-action: none;             /* drag pans the map, never the page */
  transition: border-color .3s var(--ease);
}
#navStage.adrift { border-color: rgba(226,103,60,0.4); }
#navMap { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* Centre lives INSIDE the map, because that is where you can see you need it.
   Hidden while centred rather than dimmed: a control that does nothing should
   not be occupying the corner of a picture this small. */
#navHome {
  position: absolute; right: 6px; bottom: 6px;
  padding: 5px 8px;
  font-family: var(--mono); font-size: 8px;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-mid);
  background: rgba(10,12,18,0.72);
  border: 1px solid var(--border); border-radius: 2px;
  cursor: pointer;
  opacity: 0; pointer-events: none;
  transition: opacity .22s var(--ease), color .16s var(--ease),
              border-color .16s var(--ease), background .16s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
#navStage.adrift #navHome { opacity: 1; pointer-events: auto; }
#navHome:hover, #navHome:focus-visible {
  color: var(--accent); border-color: rgba(226,103,60,0.5);
  background: rgba(226,103,60,0.14); outline: none;
}

/* --- radio player ----------------------------------------------------------
   A wave BAND, not a square. A waveform is a horizontal object; a 1:1 stage
   spent most of its height on empty space and cost the rail 250px for the
   privilege. Same information, a third of the room.

   The wave inside is the REAL signal, read off an AnalyserNode that every
   sound passes through. Worth saying because we have shipped the other kind:
   the Safehouse Lab stream card draws a synthetic envelope, since a web page
   has no microphone. Here we own the audio. */
#playerStage {
  position: relative;
  width: 100%; height: 68px;
  border: 1px solid var(--border); border-radius: 3px;
  background: linear-gradient(180deg, rgba(226,103,60,0.05), rgba(5,6,10,0) 80%),
              rgba(255,255,255,0.02);
  overflow: hidden;
  transition: border-color .3s var(--ease);
}
#playerStage.live { border-color: rgba(226,103,60,0.4); }
#playerViz { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* Title only. The subtitle repeated the label on every row, which the card
   note already says once, and repetition is not information. */
#playerTitle {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-mid); margin: 8px 0 9px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color .2s var(--ease);
}
#playerTitle.live { color: var(--accent); }

#playerBar { display: flex; align-items: center; gap: 5px; margin-bottom: 10px; }
.pbtn {
  flex: 1 1 auto; cursor: pointer;
  padding: 8px 6px;
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.1em;
  color: var(--text-mid);
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border); border-radius: 3px;
  transition: color .16s var(--ease), border-color .16s var(--ease),
              background .16s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.pbtn:hover, .pbtn:focus-visible {
  color: var(--accent); border-color: rgba(226,103,60,0.45);
  background: rgba(226,103,60,0.09); outline: none;
}
.pbtn.play { flex: 1.5 1 auto; color: var(--text); font-size: 11px; }
.pbtn.tone { flex: 0 0 auto; font-size: 8px; padding: 8px 9px; }

/* --- volume, as a level meter ----------------------------------------------
   Segments rather than a slider, so it reads as instrumentation instead of a
   web form. The native range input is still there at full size and fully
   operable - only the paint is custom, so keyboard control and screen readers
   keep working exactly as before.

   Dragging to zero IS mute. A separate mute button was one control too many,
   which is precisely what Stephan spotted. */
#volRow { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }

#volIcon {
  flex: 0 0 auto; font-size: 9px; line-height: 1;
  color: var(--text-dim);
  transition: color .2s var(--ease);
}
#volRow.silent #volIcon { color: #3d434f; }
#volRow.loud   #volIcon { color: var(--accent); }

#volMeter { position: relative; flex: 1 1 auto; height: 16px; }

/* The segments: one gradient painted to the current level, masked into ticks
   so the unfilled part stays as a dim ghost of the same rhythm. --vol is set
   from JS on each change. */
#volMeter::before {
  content: ''; position: absolute; left: 0; right: 0; top: 5px; height: 6px;
  background: linear-gradient(90deg,
              var(--accent) 0 var(--vol, 70%),
              rgba(255,255,255,0.10) var(--vol, 70%) 100%);
  -webkit-mask: repeating-linear-gradient(90deg, #000 0 3px, transparent 3px 6px);
          mask: repeating-linear-gradient(90deg, #000 0 3px, transparent 3px 6px);
  pointer-events: none;
}
#volMeter:hover::before { filter: brightness(1.3); }
#volMeter:focus-within::before { filter: brightness(1.5); }

#vol {
  position: absolute; inset: 0; width: 100%; height: 100%;
  -webkit-appearance: none; appearance: none;
  background: none; margin: 0; cursor: pointer;
  opacity: 0;                     /* invisible, still fully operable */
}

#volPct {
  flex: 0 0 auto; min-width: 20px; text-align: right;
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.08em;
  color: var(--text-dim);
  transition: color .2s var(--ease);
}
#volRow.loud #volPct { color: var(--text-mid); }

@media (max-width: 820px) {
  #playerStage { height: 76px; }
  .pbtn { padding: 11px 6px; font-size: 10px; }
  #volMeter { height: 22px; }
  #volMeter::before { top: 8px; }
}

.ask-eg { display: flex; flex-direction: column; gap: 6px; }
/* Examples wrap onto their own lines and read left-aligned — they are prompts,
   not toolbar chips, and centring them made them look like buttons to press
   rather than questions to borrow. */
.ask-eg-btn {
  text-align: left;
  white-space: normal;
  line-height: 1.45;
  text-transform: none;
  letter-spacing: 0.02em;
  font-size: 11px;
}

/* The waiting state breathes rather than spins — a spinner in a noir ops panel
   reads as a web page, and this is meant to read as a console thinking. */
.ask-thinking { animation: ask-pulse 1.4s var(--ease) infinite; }
@keyframes ask-pulse { 0%,100% { opacity: .45 } 50% { opacity: .95 } }
@media (prefers-reduced-motion: reduce) {
  .ask-thinking { animation: none; opacity: .7; }
}
.btn-group { display: flex; gap: 6px; }

/* --- site drawer -----------------------------------------------------------
   News / About / Support / Privacy, in a panel that slides in from the right.

   ⚠️ THE MARKUP LIVES AT BODY LEVEL, NOT IN #topbar. .chrome puts z-index:10
   on both #topbar and #rail, which makes each one a STACKING CONTEXT — so a
   dropdown inside the topbar paints UNDERNEATH the rail cards no matter what
   z-index it carries, because the rail is a later sibling at the same level.
   That is exactly what "the menu opens behind the transfer window card" was.
   No number fixes it from in there; it has to be a sibling of the chrome.

   A drawer rather than a dropdown because these are whole pages, not options:
   a small card popping over a big card reads as an accident, while a panel
   that takes the right edge reads as leaving the map on purpose. */

.drawer-scrim {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(3, 4, 8, 0.62);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  opacity: 0;
  transition: opacity .32s var(--ease);
}
.drawer-scrim[hidden] { display: none; }
.drawer-scrim.in { opacity: 1; }

.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 201;
  width: min(340px, 86vw);
  display: flex; flex-direction: column;
  padding: 18px 0 16px;
  background: linear-gradient(180deg, #0b0e15 0%, #070910 100%);
  border-left: 1px solid var(--border);
  box-shadow: -28px 0 60px rgba(0,0,0,0.55);
  transform: translateX(100%);
  transition: transform .36s var(--ease);
  overflow-y: auto;
}
.drawer[hidden] { display: none; }
.drawer.in { transform: translateX(0); }

/* Just the close control. The text wordmark that used to sit here was a second
   brand mark in a panel that now carries the real one below — one logo, in the
   place there is room for it. */
.drawer-top {
  display: flex; align-items: center; justify-content: flex-end;
  padding: 0 18px 14px;
  border-bottom: 1px solid var(--border);
}

.drawer-x {
  background: none; border: 0; cursor: pointer;
  color: var(--text-dim); font-size: 22px; line-height: 1;
  padding: 2px 4px;
  transition: color .18s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.drawer-x:hover, .drawer-x:focus-visible { color: var(--accent); outline: none; }

/* The nav takes only what it needs; the LOGO block takes the slack, so the mark
   sits in the middle of the empty half rather than being pushed around by it. */
.drawer-nav { display: flex; flex-direction: column; padding: 10px 0; flex: 0 0 auto; }

.drawer-logo {
  flex: 1 1 auto;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 16px; padding: 26px 22px;
  min-height: 120px;
}
.drawer-logo img {
  display: block; width: 100%; max-width: 190px; height: auto;
  /* Held back from full strength so it reads as a mark on a panel rather than
     as a picture demanding attention — the links are what this panel is for. */
  opacity: 0; filter: drop-shadow(0 6px 22px rgba(226,103,60,0.14));
}
.drawer.in .drawer-logo img {
  animation: drawerLogoIn .55s var(--ease) .26s forwards;   /* after the last link */
}
@keyframes drawerLogoIn { to { opacity: 0.92; } }

.drawer-tag {
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--text-dim); text-align: center;
  opacity: 0;
}
.drawer.in .drawer-tag { animation: drawerLogoIn .5s var(--ease) .38s forwards; }
.drawer-nav a {
  position: relative;
  display: block; text-decoration: none;
  padding: 15px 20px 14px 52px;
  font-family: var(--mono); font-size: 13px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--text);
  border-left: 2px solid transparent;
  transition: background .18s var(--ease), border-color .18s var(--ease),
              color .18s var(--ease);
  -webkit-tap-highlight-color: transparent;

  /* Each item arrives a beat after the one above it. The stagger is what makes
     the panel feel built rather than dropped — and it is 4 items, so it never
     becomes a wait. */
  opacity: 0; transform: translateX(14px);
}
.drawer.in .drawer-nav a { animation: drawerIn .34s var(--ease) forwards; }
.drawer.in .drawer-nav a:nth-child(1) { animation-delay: .07s; }
.drawer.in .drawer-nav a:nth-child(2) { animation-delay: .12s; }
.drawer.in .drawer-nav a:nth-child(3) { animation-delay: .17s; }
.drawer.in .drawer-nav a:nth-child(4) { animation-delay: .22s; }
@keyframes drawerIn { to { opacity: 1; transform: translateX(0); } }

.drawer-nav a .i {
  position: absolute; left: 20px; top: 17px;
  font-size: 9px; letter-spacing: 0.14em; color: var(--text-dim);
  transition: color .18s var(--ease);
}
.drawer-nav a em {
  display: block; margin-top: 5px;
  font-family: var(--sans); font-style: normal;
  font-size: 11px; letter-spacing: 0; text-transform: none;
  color: var(--text-dim);
}
.drawer-nav a:hover, .drawer-nav a:focus-visible {
  background: rgba(226,103,60,0.07);
  border-left-color: var(--accent);
  color: var(--accent); outline: none;
}
.drawer-nav a:hover .i, .drawer-nav a:focus-visible .i { color: var(--accent); }

.drawer-foot {
  display: flex; flex-direction: column; gap: 4px;
  padding: 16px 20px 0; margin-top: auto;
  border-top: 1px solid var(--border);
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--text-dim);
}

/* The burger itself. Its bars converge into a cross while the panel is open,
   so the one control says both "open this" and "close this". */
.menu-btn { padding: 8px 9px; line-height: 0; }
.menu-bars { display: inline-flex; flex-direction: column; gap: 3px; }
.menu-bars i {
  display: block; width: 13px; height: 1px; background: currentColor;
  transition: transform .28s var(--ease), opacity .2s var(--ease);
}
.menu-btn[aria-expanded="true"] {
  color: var(--accent); border-color: rgba(226,103,60,0.45);
  background: rgba(226,103,60,0.09);
}
/* Signed-in tell. The drawer is shut most of the time, so "am I signed in"
   has to be answerable without opening it — one accent dot, no text, no
   layout shift. Positioned on the button rather than added to it, so the
   burger geometry above is untouched. */
.menu-btn { position: relative; }
.menu-btn.signed-in::after {
  content: ''; position: absolute; top: 4px; right: 4px;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 6px rgba(226,103,60,0.9);
}
/* --- the one thing on the deck that asks for something -------------------
   ⚠️ ONE SMALL CONTROL, NEVER A BANNER. The interface discipline here is that
   panels do not eat the map; a persistent marketing bar over a live solar
   system would be the first thing that makes this feel like a product trying
   to sell you something rather than an instrument you are using. It is a
   button the same size as every other button, wearing the accent so it reads
   as the one different thing in the row.

   ⛔ IT DOES NOT SCROLL. It is a sibling of #toolbar rather than a child,
   because that strip scrolls sideways on a phone and a CTA you have to swipe
   to find is not a CTA. */
#t-join {
  flex: 0 0 auto; margin-left: 8px; text-decoration: none;
  color: var(--accent); border-color: rgba(226,103,60,0.5);
  background: var(--accent-dim);
  transition: color .18s var(--ease), border-color .18s var(--ease),
              background .18s var(--ease);
}
#t-join:hover {
  color: var(--accent-hot); border-color: rgba(226,103,60,0.8);
  background: rgba(226,103,60,0.22);
}
/* ⛔ A LIVE BUG, AND IT WAS SELLING TO PEOPLE WHO HAD ALREADY BOUGHT.
   deck.js hides this button for anyone on the paid tier — correctly, and the
   comment on that code says never sell to somebody who has already bought.
   It did not work. `hidden` is a single user-agent rule,
   `[hidden] { display: none }`, and an author rule setting display beats it
   by ORIGIN before specificity is even considered: `.btn` sets
   `display: inline-block`, so every `<a class="btn" hidden>` on this page was
   painted anyway. A founding member has been looking at "Sign up".

   ⚠️ It cannot be fixed by removing display from `.btn` — that line is what
   makes an <a class="btn"> the same shape as a <button class="btn">, and the
   whole toolbar depends on it. The override is the fix. */
#t-join[hidden] { display: none !important; }

/* A quiet pulse the first time it appears, and once only. Enough to be seen
   on a dark bar; nowhere near enough to nag. */
#t-join[data-new] { animation: joinIn .5s var(--ease) 1; }
@keyframes joinIn { from { opacity: 0; transform: translateY(-3px); } }
@media (prefers-reduced-motion: reduce) { #t-join[data-new] { animation: none; } }
/* On a narrow phone the label alone is the whole point — keep it, drop the
   padding rather than the words. */
@media (max-width: 560px) { #t-join { padding: 7px 9px; margin-left: 6px; } }

/* --- the account chip ------------------------------------------------------
   Your mark and how many things you follow, pinned beside the CTA. It is
   deliberately NOT a second sell: it carries no accent fill and no label,
   because it is a door back to something you already own rather than an
   invitation. The accent belongs to #t-join, and one row may only have one
   loudest thing in it. */
/* ⛔ IT IS A ROUND AVATAR NOW, AND THAT IS WHAT FIXED "CRAMPED".
   It used to be a .btn rectangle holding a 20px ring and a 10px number, with
   its padding cut to 5px/6px so the two would fit — the only control in the
   row not at the row's own 7px/11px, sitting beside a full-size Sign-up
   button. Two small things wedged into a box that had been shrunk to hold
   them. Stephan: "it looks very tiny, very cramped together."

   The count moved to the drawer, so there is one thing in here, and one thing
   in a rectangle is a rectangle for no reason. A circle the size of the mark
   itself is the shape this always wanted: it reads as an avatar, which is a
   pattern nobody has to learn. */
#t-me {
  flex: 0 0 auto; margin-left: 10px; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  border-radius: 50%;
  background: rgba(255,255,255,0.03);
}
/* ⛔ THIS LINE IS LOAD-BEARING AND IT IS EASY TO DELETE BY ACCIDENT.
   `hidden` is only a UA rule (`[hidden] { display: none }`), so ANY author
   rule that sets display beats it — and the declaration above sets
   inline-flex. Without this the chip renders for everybody signed out, as an
   empty ring with no name, which is exactly the "second sign in" it was
   written not to be. The markup says hidden and the script sets hidden; both
   are true and neither is sufficient. `.drawer[hidden]` and
   `.drawer-scrim[hidden]` are in this file for the same reason. */
#t-me[hidden] { display: none !important; }
#t-me:hover { border-color: rgba(226,103,60,0.5); }
/* The ring is the chip now, so it fills it rather than floating inside it. */
.me-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; border-radius: 50%;
  color: var(--accent);            /* the sigils are currentColor by design */
}
.me-mark svg { width: 15px; height: 15px; display: block; }
#t-me:hover .me-mark { border-color: rgba(226,103,60,0.55); }
/* ⚠️ THE DASH THAT USED TO LIVE HERE MOVED WITH THE NUMBER. Following
   nothing is a real state and it must never render as "0" — a zero beside
   your own mark reads as a score you are losing at. The drawer says
   "Watchlist / Nothing followed yet" in words instead, which is better than a
   dash was, because a dash needs decoding.
   ⚠️ On a phone the chip stays: it is one circle, it costs 30px, and it is
   the only sign you are signed in at all. */
@media (max-width: 560px) {
  #t-me { margin-left: 8px; }
}

/* --- the account card ------------------------------------------------------
   ⚠️ THIS USED TO BE ITEM 05 IN THE NUMBERED LIST, alongside News and Privacy,
   which quietly claimed the account is just another page on the site. It is
   the only entry in the drawer that is about YOU, and the only route back to a
   watchlist once you have one. A card above the rule says so without copy —
   and gives the mark somewhere to live. */
/* ⚠️ THE GEOMETRY IS THE NAV'S GEOMETRY, NOT A NEW ONE.
   Inset as a floating box it sat 28px right of everything below it, so the
   panel read as two columns that nearly lined up — and a near-miss looks like
   a bug in a way a deliberate offset never does. So it is full-bleed like the
   nav rows, and the numbers land the callsign on the SAME x as NEWS and
   PRIVACY: 12 + 30 + 10 = 52px, which is exactly .drawer-nav a's padding-left.
   Change one, change the other — the suite does that sum.

   ⚠️ AND THE LEFT PADDING IS 12px, NOT 20px, WHICH LOOKS WRONG AND IS RIGHT.
   Matching the nav's 20px put the ring's LEFT EDGE under the index numbers but
   its CENTRE 9px right of theirs, and a circle reads by its centre while text
   reads by its edge — so the mark sat visibly right of the column it belongs
   to. At 12px the ring is centred on 27px and the "01"–"04" column on ~26.5px:
   the mark now occupies the index column instead of leaning out of it.
   Optical alignment, not numerical. */
/* ⛔ THE SAME TRAP, CAUGHT TWELVE HOURS LATER BY THE GUARD WRITTEN FOR IT.
   `hidden` is one user-agent rule, and the `display: flex` below beats it by
   ORIGIN. This element now ships hidden and is revealed only for somebody
   signed in — without this override it would render "Sign in" to every
   stranger regardless, which is exactly what #t-join was doing this morning.
   Any element that ships `hidden` AND sets `display` needs its own override. */
#t-account[hidden] { display: none !important; }

.drawer-acct {
  display: flex; align-items: center; gap: 10px;
  margin: 0; padding: 15px 20px 15px 12px;
  text-decoration: none;
  background: rgba(255,255,255,0.022);
  border: 0; border-bottom: 1px solid var(--border);
  transition: border-color .18s var(--ease), background .18s var(--ease);
  opacity: 0; transform: translateX(14px);
}
/* First in, before the numbered links — it is the top of the panel. */
.drawer.in .drawer-acct { animation: drawerIn .34s var(--ease) .04s forwards; }
.drawer-acct:hover, .drawer-acct:focus-visible {
  background: rgba(226,103,60,0.07);
  border-bottom-color: rgba(226,103,60,0.4); outline: none;
}
.da-mark {
  flex: 0 0 30px; display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(0,0,0,0.3);
  color: var(--accent);              /* the marks are drawn in currentColor */
}
/* ⚠️ Signed out the slot KEEPS ITS WIDTH but shows nothing. display:none would
   shift "Sign in" 32px left of every label below it — the exact misalignment
   this block exists to remove, appearing only in the state nobody tests. */
.drawer-acct:not(.is-in) .da-mark { border-color: transparent; background: none; }
.drawer-acct.is-in .da-mark {
  border-color: rgba(226,103,60,0.42); background: var(--accent-dim);
}
.da-mark svg { display: block; }
.da-who { flex: 1 1 auto; min-width: 0; }
.da-name {
  display: block;
  /* Same type as the nav labels below it, or the alignment is only half done. */
  font-family: var(--mono); font-size: 13px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.drawer-acct.is-in .da-name { color: var(--accent); }
.da-who em {
  display: block; margin-top: 5px;
  font-family: var(--sans); font-style: normal;
  font-size: 11px; letter-spacing: 0; text-transform: none;
  color: var(--text-dim);
}
.da-go { flex: 0 0 auto; color: var(--text-dim); font-size: 17px; line-height: 1; }
.drawer-acct:hover .da-go { color: var(--accent); }

.menu-btn[aria-expanded="true"] .menu-bars i:nth-child(1) { transform: translateY(4px) rotate(45deg); }
.menu-btn[aria-expanded="true"] .menu-bars i:nth-child(2) { opacity: 0; }
.menu-btn[aria-expanded="true"] .menu-bars i:nth-child(3) { transform: translateY(-4px) rotate(-45deg); }

@media (max-width: 820px) {
  .drawer { width: min(320px, 88vw); }
  .drawer-nav a { padding: 17px 20px 16px 52px; font-size: 14px; }
}

/* A slide is decoration. Someone who has asked for less motion still gets the
   panel, immediately, with no animation to sit through. */
@media (prefers-reduced-motion: reduce) {
  .drawer, .drawer-scrim { transition: none; }
  .drawer.in .drawer-nav a { animation: none; opacity: 1; transform: none; }
  /* Same trap as the logo below: the card starts at opacity 0 and is revealed
     BY the animation, so cancelling it without restoring opacity would delete
     the account card for anyone who asked for less motion. */
  .drawer.in .drawer-acct { animation: none; opacity: 1; transform: none; }
  /* ⚠️ The logo and tagline START at opacity 0 and are revealed BY the
     animation. Cancelling the animation without restoring opacity would leave
     them permanently invisible — a "respects your settings" branch that
     silently deletes the brand mark. */
  .drawer.in .drawer-logo img { animation: none; opacity: 0.92; }
  .drawer.in .drawer-tag { animation: none; opacity: 1; }
  .menu-bars i { transition: none; }
}

.btn.accent {
  color: var(--accent);
  border-color: rgba(226,103,60,0.42);
  background: var(--accent-dim);
}
.btn.accent:hover {
  color: var(--accent-hot);
  border-color: rgba(226,103,60,0.7);
}

/* the shareable link, made obviously selectable */
.share-url {
  font-family: var(--mono); font-size: 10px;
  line-height: 1.6; word-break: break-all;
  color: var(--text-mid);
  background: rgba(0,0,0,0.35);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 10px 11px;
  margin-top: 4px;
  user-select: all; -webkit-user-select: all;
}

/* --- the thesis readout: Earth -> Mars ---------------------------------- */

#rail {
  /* The bar is ~49px tall (11px padding + a 28px button + 10px) since Ambient
     and Ask moved out of it, so the rail follows it up by the same 11px rather
     than leaving a band of dead space where the chips used to be. Clearing the
     bar by a hair invites exactly the overlap this once had — keep the gap. */
  top: 67px; right: 22px;
  width: 248px;
  pointer-events: auto;   /* .chrome sets none; the rail itself must scroll */
  display: flex; flex-direction: column; gap: 12px;
  max-height: calc(100vh - 149px);
  overflow-y: auto;
}

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 14px 16px;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  flex-shrink: 0;
}

/* the hook card carries the accent — nothing else on the rail does */
#window { border-color: rgba(226,103,60,0.28); }

/* Generalised from #window: the Layers card uses the same pending state while
   the 305 KB catalog parses. It used to be display:none until ready, which
   made a control panel MATERIALISE a third of a second in — a card that
   appears is easy to miss, and it shoved everything below it down. A card that
   is present and says what it is waiting for costs nothing and jumps nothing. */
.card .pending {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.14em; color: var(--text-dim);
  padding: 8px 0 4px;
}

#window .count {
  font-family: var(--mono);
  font-size: 25px; font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--accent);
  line-height: 1.1;
  margin-bottom: 3px;
}
#window .count.open { color: #6ede9a; }

#window .count-label {
  font-family: var(--mono); font-size: 8px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 14px;
  padding-bottom: 13px;
  border-bottom: 1px solid var(--border);
}

.card-actions {
  display: flex; gap: 6px; margin-top: 14px;
}
.card-actions .btn { flex: 1; text-align: center; padding: 7px 4px; }

/* --- mode visibility ---------------------------------------------------- */

.sky-only { display: none; }
body.sky-mode .sky-only { display: block; }
body.sky-mode .deck-only { display: none; }
body.sky-mode #scale,
body.sky-mode .view-btn,
body.sky-mode #t-scale,
body.sky-mode #t-traj { display: none; }
body.sky-mode #deck { cursor: default; }

/* --- sky card ----------------------------------------------------------- */

.sky-obj {
  font-size: 24px; font-weight: 300;
  letter-spacing: -0.01em; margin-bottom: 5px;
}
.sky-status {
  font-family: var(--mono); font-size: 8px;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 13px; padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
  line-height: 1.5;
}
.sky-status.good { color: #6ede9a; }

.sky-note {
  font-family: var(--mono); font-size: 8px;
  line-height: 1.7; letter-spacing: 0.03em;
  color: #565d6d;
  margin-top: 13px; padding-top: 12px;
  border-top: 1px solid var(--border);
}
.sky-note strong { color: var(--text-dim); font-weight: 500; }

/* --- the Following card ----------------------------------------------------
   ⛔ A LIST OF DOORS, NOT A TABLE. Every row is a camera move, so each one is
   a real <button> — it does something, and a div with a click handler tells a
   keyboard or a screen reader nothing. The rows carry no border and no fill at
   rest: six framed boxes inside a card that already has a frame is the mistake
   /settings was rebuilt to undo.

   The name and the distance are pushed apart so the figures form their own
   column. Tabular numerals, so a digit changing under a live clock does not
   shuffle the text beside it — this repaints every sixth frame and any width
   jitter would read as the panel twitching. */
.fol-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; width: 100%;
  padding: 6px 0; background: none; border: 0; text-align: left;
  cursor: pointer; font: inherit;
  border-bottom: 1px solid rgba(255,255,255,0.045);
}
.fol-row:last-child { border-bottom: 0; }
.fol-name {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.05em;
  color: var(--text); transition: color .16s var(--ease);
}
.fol-au {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.06em;
  color: var(--text-dim); font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.fol-row:hover .fol-name, .fol-row:focus-visible .fol-name { color: var(--accent); }
.fol-row:hover .fol-au,   .fol-row:focus-visible .fol-au   { color: var(--text); }
/* ⚠️ THE SAME DISTINCTION THE DECK AND THE DEEP BAND MAKE. Pioneer 10 and 11
   stopped transmitting in 2003 and 1995; their positions are arithmetic, not
   telemetry, and a row that looked identical to Voyager's would imply we are
   hearing them. */
.fol-row.quiet .fol-name { color: #767e8c; }

/* --- Ask: where the numbers came from -----------------------------------
   ⊙ computed by our engine  ·  ◇ Grok's general knowledge.
   These must never look alike. The badge is the mechanism that lets Ask
   answer questions outside the catalog without borrowing authority it has
   not earned, so the visual difference is load-bearing, not decorative. */

/* The question, small and quiet — context, not the headline. */
.ask-q {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--text-dim); line-height: 1.6;
  margin-bottom: 12px;
}

/* The answer IS the card. Everything else defers to it. */
.ask-a {
  font-size: 16px; line-height: 1.6; font-weight: 300;
  color: var(--text); margin: 0 0 14px;
}

/* One quiet line: provenance and quota together, or nothing at all. */
.ask-foot {
  font-family: var(--mono); font-size: 8px;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: #565d6d; line-height: 1.7;
}
.ask-ref { color: #7f8899; }

/* The waiting line reports real work, so it reads as instrumentation rather
   than a spinner. Slow pulse only — nothing frantic. */
.ask-thinking {
  font-family: var(--mono); font-size: 9.5px;
  letter-spacing: 0.1em; color: var(--text-dim);
  animation: askpulse 1.9s var(--ease) infinite;
}
@keyframes askpulse { 0%,100% { opacity: 0.45; } 50% { opacity: 0.9; } }
@media (prefers-reduced-motion: reduce) { .ask-thinking { animation: none; } }

/* --- catalog chips ------------------------------------------------------ */

/* --- layer chips -----------------------------------------------------------
   A GRID, not a wrapping flex row. Six labels of different lengths wrapping
   freely made a ragged staircase of boxes that read as clutter rather than as
   a panel — which is exactly what Stephan meant by the boxes looking confusing.
   Two equal columns, and the longest label (JUPITER TROJANS / TRANS-NEPTUNIAN)
   still fits at this size.

   The ON state used to fill the whole box with accent, so six enabled layers
   were six rust rectangles shouting at once. Now the STATE lives in a small dot
   carrying that population's ACTUAL MAP COLOUR — which does a second job the
   old chip could not: it tells you which dots on the deck belong to which
   switch. The box itself stays quiet. */
.chips {
  display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
  margin-top: 10px;
}
.chip {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--mono);
  font-size: 8px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-dim);
  background: rgba(255,255,255,0.02);
  border: 1px solid var(--border);
  border-radius: 2px;
  padding: 6px 7px;
  cursor: pointer;
  text-align: left; white-space: nowrap; overflow: hidden;
  transition: color .18s var(--ease), border-color .18s var(--ease),
              background .18s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.chip .dot {
  flex: 0 0 auto; width: 5px; height: 5px; border-radius: 50%;
  background: #262c39;
  transition: background .18s var(--ease), box-shadow .18s var(--ease);
}
.chip:hover, .chip:focus-visible {
  color: var(--text); border-color: var(--border-lift); outline: none;
}
.chip.on { color: var(--text); border-color: rgba(255,255,255,0.14); }
.chip.on .dot {
  background: var(--c, var(--accent));
  box-shadow: 0 0 5px var(--c, var(--accent));
}

/* Provenance, not a readout. This used to be a hero row in accent at 17px —
   the treatment the launch countdown uses — plus a three-line note. It is a
   footnote, and the card is a control panel, so it now reads like one. */
.cat-meta {
  margin-top: 9px;
  font-family: var(--mono); font-size: 8px; letter-spacing: 0.06em;
  line-height: 1.7; color: #4a5162;
}
#catStatus { color: #4a5162; }

/* --- location card ------------------------------------------------------ */

.loc-label { font-size: 15px; font-weight: 400; margin-bottom: 4px; }
.loc-source {
  font-family: var(--mono); font-size: 8px;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-dim);
}
body.no-site .loc-label { color: var(--accent); }

.loc-row { display: flex; gap: 6px; margin: 10px 0 6px; }
.loc-row input {
  flex: 1; width: 100%;
  background: rgba(0,0,0,0.35);
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text);
  font-family: var(--mono); font-size: 11px;
  padding: 7px 8px;
  min-width: 0;
}
.loc-row input:focus {
  outline: none; border-color: rgba(226,103,60,0.5);
}
.loc-msg {
  font-family: var(--mono); font-size: 8px;
  letter-spacing: 0.1em; color: var(--accent);
  margin-top: 9px; min-height: 10px; line-height: 1.5;
}

.card h3 {
  margin: 0 0 12px;
  font-family: var(--mono);
  font-size: 9px; font-weight: 500;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--text-dim);
  display: flex; align-items: center; gap: 7px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color .16s var(--ease);
}
.card h3:hover, .card h3:focus-visible { color: var(--text); outline: none; }
.card h3::before {
  content: ''; width: 14px; height: 1px;
  background: var(--accent);
  flex: 0 0 auto;
}

/* --- the folding rail ------------------------------------------------------
   Every card folds to its title bar, and a folded card still carries the one
   number worth glancing at — collapsing costs a tap, never information. That
   is the same bargain the mobile sheet handle already makes.

   ⚠️ Folding is FREE and stays free. Pro buys control later — reordering,
   pinning, saved layouts — never the ability to tidy up a rail we filled. */
.card-sum {
  margin-left: auto;
  font-size: 8.5px; letter-spacing: 0.08em;
  color: var(--text-mid);
  text-transform: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 60%;
}
.card-chev {
  flex: 0 0 auto;
  font-size: 7px; line-height: 1;
  color: var(--text-dim);
  transition: transform .22s var(--ease), color .16s var(--ease);
}
.card h3:hover .card-chev { color: var(--accent); }
.card.folded .card-chev { transform: rotate(180deg); }
.card.folded h3 { margin-bottom: 0; }
.card.folded .card-body { display: none; }
/* A folded card is a single line; drop the vertical padding with it. */
.card.folded { padding-top: 11px; padding-bottom: 11px; }

/* The summary has no room beside a long title on a narrow rail. */
@media (max-width: 820px) { .card-sum { max-width: 45%; font-size: 8px; } }
@media (prefers-reduced-motion: reduce) { .card-chev { transition: none; } }
.readout { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 9px; }
.readout:last-child { margin-bottom: 0; }
.readout .k {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--text-dim);
}
.readout .v { font-family: var(--mono); font-size: 13px; color: var(--text); }
.readout .v .u { font-size: 9px; color: var(--text-dim); margin-left: 3px; }
.readout.hero .v { color: var(--accent); font-size: 17px; }

/* --- bottom bar --------------------------------------------------------- */

#bottombar {
  bottom: 0; left: 0; right: 0;
  display: flex; align-items: center; gap: 18px;
  padding: 18px 22px 20px;
  background: linear-gradient(0deg, rgba(5,6,10,0.94) 20%, rgba(5,6,10,0) 100%);
  flex-wrap: wrap;
}

#scrub {
  flex: 1 1 120px;
  min-width: 90px;   /* was 180px — it refused to shrink and pushed the scale off-screen */
  -webkit-appearance: none; appearance: none;
  height: 2px; border-radius: 2px;
  background: var(--border-lift);
  outline: none; cursor: pointer;
}
#scrub::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
  cursor: grab;
  box-shadow: 0 0 10px rgba(226,103,60,0.6);
}
#scrub::-moz-range-thumb {
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg);
  cursor: grab;
}

/* --- scale legend ------------------------------------------------------- */

/* --- scale, inside the bottom status strip ---------------------------------
   It used to float loose above the bottom-left corner, where it collided with
   the camera pad and read as a stray label. Folded into the strip, the bottom
   of the screen becomes one continuous instrument rail: time on the left,
   scale on the right, nothing hovering.

   Still measures the HORIZONTAL axis — the one the projection does not
   foreshorten. A vertical ruler cannot be honest here: y is scaled by sin(tilt),
   so the 2 AU spanning 120px across spans 86px down at the default camera and
   7px once the view is tilted flat. */
#scale {
  flex: 0 0 auto;
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.1em; color: var(--text-dim);
  line-height: 1.7; text-align: right;
  padding-left: 6px;
  border-left: 1px solid var(--border);
  margin-left: 2px;
}
#scale .bar {
  height: 1px; background: var(--text-dim);
  position: relative; margin: 0 0 5px auto;
}
#scale .bar::before, #scale .bar::after {
  content: ''; position: absolute; top: -3px;
  width: 1px; height: 7px; background: var(--text-dim);
}
#scale .bar::before { left: 0; }
#scale .bar::after  { right: 0; }
#scale .srow { display: flex; gap: 10px; justify-content: flex-end; white-space: nowrap; }
#scale .note { color: #4a5162; text-transform: uppercase; }

/* --- dossier panel ------------------------------------------------------ */

#dossier {
  top: 0; right: 0; bottom: 0;
  width: 340px;
  pointer-events: auto;   /* .chrome sets none; the panel itself must scroll */
  background: var(--panel);
  border-left: 1px solid var(--border);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  padding: 22px 24px;
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform .42s var(--ease);
  z-index: 20;
}
#dossier.open { transform: translateX(0); }

#dossier .close {
  position: absolute; top: 18px; right: 20px;
  background: none; border: none; color: var(--text-dim);
  font-size: 20px; line-height: 1; cursor: pointer; padding: 4px;
  transition: color .18s var(--ease);
}
#dossier .close:hover { color: var(--text); }

.dos-kind {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 8px;
}
.dos-name {
  margin: 0 0 4px;
  font-size: 30px; font-weight: 300; letter-spacing: -0.01em;
}
.dos-approx {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.08em; color: #6b5a3a;
  background: rgba(180,140,60,0.1);
  border: 1px solid rgba(180,140,60,0.22);
  border-radius: 3px; padding: 5px 8px;
  margin: 12px 0; line-height: 1.5;
}
.dos-blurb {
  font-size: 13px; line-height: 1.68; color: var(--text-mid);
  margin: 16px 0 22px;
}
/* --- follow / watchlist ---------------------------------------------------
   One line inside the dossier every object already opens. No panel, no new
   breakpoint, no second layout — the deck's interface budget is one button
   per feature and this feature spends it here. */
.dos-track {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  /* The negative top margin closes the gap the blurb paragraph leaves below
     itself on a planet card, where this row always follows one. */
  margin: -8px 0 22px;
}
/* ⚠️ AND ON A CARD WITH NO BLURB IT HAS NOTHING TO CLOSE.
   A small body has no blurb text, so the row sits directly under the name —
   and -8px pulled it up INTO the heading, which is exactly what it looked
   like. A margin tuned for one arrangement has to say which arrangement. */
.dos-name + .dos-track { margin-top: 14px; }
.trk-btn {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-dim); background: transparent;
  border: 1px solid var(--border); border-radius: 3px;
  padding: 7px 11px; cursor: pointer; text-decoration: none;
  transition: color .18s var(--ease), border-color .18s var(--ease), background .18s var(--ease);
}
.trk-btn:hover { color: var(--accent); border-color: rgba(226,103,60,0.45); }
.trk-btn.on {
  color: var(--accent); border-color: rgba(226,103,60,0.5);
  background: rgba(226,103,60,0.09);
}
.trk-btn[disabled] { opacity: 0.5; cursor: default; }
.trk-count {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.1em; color: #565d6d;
}
.trk-wait { font-family: var(--mono); font-size: 9px; color: #444b58; }
/* A refusal is an answer, not an error — it explains the limit in the place
   the limit was hit, rather than throwing an alert over the map. */
.trk-msg {
  flex-basis: 100%;
  font-family: var(--mono); font-size: 9px; line-height: 1.7;
  letter-spacing: 0.04em; color: var(--accent);
}

.dos-grid { border-top: 1px solid var(--border); }
.dos-row {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 14px; padding: 11px 0;
  border-bottom: 1px solid var(--border);
}
.dos-row .k {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--text-dim); white-space: nowrap;
}
.dos-row .v {
  font-family: var(--mono); font-size: 11px;
  color: var(--text); text-align: right;
}
.dos-section {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--text-dim);
  margin: 26px 0 10px;
  display: flex; align-items: center; gap: 9px;
}
.dos-section::after {
  content: ''; flex: 1; height: 1px; background: var(--border);
}

/* --- hint --------------------------------------------------------------- */

#hint {
  bottom: 74px; right: 22px;
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: #454c5c; text-align: right; line-height: 1.9;
  transition: opacity .6s var(--ease);
}
#hint.fade { opacity: 0; }

/* --- responsive --------------------------------------------------------- */

/* --- desktop chrome layout ---------------------------------------------- */

/* Sizing here is load-bearing. A flex item defaults to min-width:auto, so
   #toolbar will NOT shrink below its buttons — which means whatever else can
   shrink, does. Giving #brand min-width:0 let it collapse to nothing while the
   wordmark and clock kept white-space:nowrap, so their text overflowed and
   piled up on the right. Brand is now immovable and the toolbar is the thing
   that yields. */
#brand {
  display: flex; align-items: center; gap: 24px;
  flex: 0 0 auto;             /* never collapse the identity */
}
#toolbar {
  display: flex; align-items: center; gap: 6px;
  flex: 0 1 auto; min-width: 0;
  overflow-x: auto;           /* scroll rather than overflow the bar */
  /* Explicit: with overflow-x set and overflow-y left visible, browsers
     promote overflow-y to auto and can add a vertical scrollbar that changes
     the bar's height — which is what would push the rail into it. */
  overflow-y: hidden;
  scrollbar-width: none;
}
#toolbar::-webkit-scrollbar { display: none; }
#toolbar > * { flex: 0 0 auto; }   /* buttons keep their size */

/* Reclaim width on smaller laptops before anything has to scroll. */
@media (max-width: 1400px) { .wordmark .tag { display: none; } }
@media (max-width: 1180px) {
  #topbar { gap: 14px; }
  #brand { gap: 14px; }
  #clock { font-size: 10px; letter-spacing: 0.05em; }
}

/* The handle only exists on phones. */
#sheetHandle { display: none; }

/* ==========================================================================
   MOBILE
   The map is the product. On a phone every panel competes with it for the
   only thing in short supply, which is vertical space — so the rail stops
   being a rail and becomes a bottom sheet that is CLOSED by default and
   still shows the two numbers worth knowing while closed.
   ========================================================================== */

@media (max-width: 820px) {

  /* --- top: brand on one line, tools on a scrolling strip --- */
  #topbar {
    flex-direction: column; align-items: stretch;
    gap: 9px; padding: 10px 12px 8px;
  }
  #topbar .spacer { display: none; }          /* would break the scroll strip */
  #brand { gap: 12px; justify-content: space-between; }
  .wordmark { font-size: 13px; letter-spacing: 0.2em; }
  .wordmark .tag { display: none; }
  #clock { font-size: 9px; }

  #toolbar {
    overflow-x: auto; overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 2px;
    /* Bleed to the screen edges so it reads as swipeable, not clipped. */
    margin: 0 -12px; padding-left: 12px; padding-right: 12px;
  }
  #toolbar::-webkit-scrollbar { display: none; }
  #toolbar .btn, #toolbar .btn-group { flex: 0 0 auto; }

  .btn {
    padding: 9px 11px; font-size: 9px;        /* a real touch target */
    -webkit-tap-highlight-color: transparent;
  }

  /* --- the scrim behind the open sheet ---
     ⛔ THE PANELS WERE LOSING A FIGHT WITH THE MAP. --panel is already 88%
     opaque, so the cards themselves were never really the problem — it is the
     8px GAPS BETWEEN THEM, each one a window onto a moving starfield, which
     shatters seven panels into seven floating fragments instead of one sheet.
     Plus the last 12% over a bright orbit line.

     ⚠️ THIS IS CHEAPER THAN WHAT IT REPLACES, NOT AN ADDITION. With the sheet
     open a phone was compositing EIGHT separate blur(14px) passes — seven
     cards and the handle — every frame, and every one of them was blurring a
     backdrop about to be covered anyway. One scrim, and the eight come off.
     The site drawer has worked this way since it was built; this is the same
     treatment, not a new idea.

     ⚠️ THE LEGIBILITY IS IN THE ALPHA, NOT THE BLUR. A browser without
     backdrop-filter still gets a flat dark wash and a perfectly readable
     sheet; it just loses the sense of depth. Blur is never load-bearing.

     ⚠️ pointer-events: none, DELIBERATELY. A scrim that swallowed taps would
     turn this into a modal — but the map underneath stays live today (tapping
     an object opens its dossier, which closes the sheet on its way past), and
     silently removing that is a behaviour change nobody asked for. This is a
     visual treatment. It changes what you can see, never what you can do.

     A pseudo-element on <body>, so there is no markup to add, nothing to add
     to a manifest, and nothing that can be forgotten. */
  body::before {
    content: ''; position: fixed; inset: 0;
    z-index: 9;                       /* under .chrome (10) and the handle (12) */
    pointer-events: none;
    background: rgba(4, 5, 9, 0.76);
    opacity: 0; visibility: hidden;
    transition: opacity .26s var(--ease), visibility .26s;
  }
  body.sheet-open::before {
    opacity: 1; visibility: visible;
    /* Applied only while open, so the filter does not exist at all the rest
       of the time — an always-on backdrop-filter at opacity 0 is a bill some
       browsers still pay. */
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
  }
  /* The eight passes come off. They were blurring a backdrop that is now a
     flat wash, which is work with nothing to show for it. */
  body.sheet-open #rail .card,
  body.sheet-open #sheetHandle {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  /* --- the sheet ---
     ⛔ THE HANDLE'S HEIGHT AND THE RAIL'S POSITION WERE TWO MAGIC NUMBERS
     THAT HAD TO AGREE (62 + handle = 106) AND NOTHING ENFORCED IT. Growing
     the handle by one line would have slid the panels underneath it, on
     phones only, where nothing in the suite looks. deck.js measures the
     handle and writes --sheet-h, so the rail is positioned FROM the thing it
     has to clear rather than from a number somebody once worked out. The
     fallback below is only for the frame before that runs. */
  :root { --sheet-y: 62px; --sheet-h: 52px; --sheet-gap: 9px; }

  #sheetHandle {
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px;
    position: fixed; left: 8px; right: 8px; bottom: var(--sheet-y);
    z-index: 12;
    background: var(--panel);
    border: 1px solid rgba(226,103,60,0.34);
    border-radius: 4px;
    padding: 10px 11px 10px 13px;
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    font-family: var(--mono);
    color: var(--text-mid); text-align: left;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  /* The two lines: what it does, then what it is showing you while shut.
     min-width:0 is load-bearing — without it a flex child refuses to shrink
     below its content and the ellipsis on the summary never engages, so the
     chevron gets pushed off the edge instead. */
  .sh-rows { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

  /* ⚠️ A VERB, IN THE ACCENT, ABOVE THE DATA. The accent is what says "this
     one is different from the readouts" on a bar full of dim grey figures —
     and it is the only accent in this component, so it stays the loudest
     thing rather than competing with anything. */
  .sh-label {
    font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase;
    color: var(--accent);
  }
  #sheetSummary {
    font-size: 8.5px; letter-spacing: 0.09em; text-transform: uppercase;
    color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* The chevron in a ring. On its own at 8px it was a mark ON a panel; inside
     a bordered circle it is a part OF a button, which is the whole difference
     between something you read and something you press. */
  #sheetHandle .chev {
    display: flex; align-items: center; justify-content: center;
    flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
    border: 1px solid rgba(226,103,60,0.45);
    background: var(--accent-dim);
    color: var(--accent); font-size: 9px; line-height: 1;
    transition: transform .3s var(--ease);
  }
  body.sheet-open #sheetHandle .chev { transform: rotate(180deg); }

  #rail {
    position: fixed;
    top: auto;
    bottom: calc(var(--sheet-y) + var(--sheet-h) + var(--sheet-gap));
    left: 8px; right: 8px; width: auto;
    max-height: calc(100vh - var(--sheet-y) - var(--sheet-h)
                           - var(--sheet-gap) - 104px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    gap: 8px;
    /* Closed by default — this is the whole fix. */
    opacity: 0; visibility: hidden;
    transform: translateY(10px);
    transition: opacity .26s var(--ease), transform .26s var(--ease),
                visibility .26s;
  }
  body.sheet-open #rail {
    opacity: 1; visibility: visible; transform: translateY(0);
  }

  /* Both cards are wanted on mobile now that they cost nothing when closed. */
  #tether { display: block; }
  body.sky-mode #tether { display: none; }

  #window .count { font-size: 20px; }
  .card { padding: 12px 13px; }
  .card h3 { margin-bottom: 10px; }

  /* --- time controls --- */
  #bottombar {
    padding: 9px 12px 11px; gap: 8px;
    flex-wrap: nowrap; overflow-x: auto;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  #bottombar::-webkit-scrollbar { display: none; }
  #bottombar > * { flex: 0 0 auto; }
  #scrub { flex: 1 1 90px; min-width: 90px; }

  /* --- the rest --- */
  #dossier { width: 100%; padding: 20px 18px 40px; }
  #hint { display: none; }
  /* #scale stays: it is part of the status strip now, not a floating label. */
  #scale { font-size: 8px; line-height: 1.5; padding-left: 8px; }
  #scale .srow { flex-direction: column; gap: 0; }
}

/* Short landscape phones: the sheet would cover everything, so cap it hard. */
@media (max-width: 820px) and (max-height: 480px) {
  #rail { max-height: calc(100vh - 150px); }
  #topbar { gap: 6px; padding: 6px 12px 5px; }
}

@media (prefers-reduced-motion: reduce) {
  #clock .live::before { animation: none; }
  #dossier { transition: none; }
}
