/* ==========================================================================
   Type. Two voices, both deterministic.
   --------------------------------------------------------------------------
   The first draft shipped `"Archivo", "Helvetica Neue", system-ui, Arial` and
   `ui-serif, Georgia, serif` and loaded neither, so every visitor saw whatever
   their operating system happened to own: DejaVu here, Arial and Georgia on
   Windows, Helvetica and Georgia on a Mac. Three different pieces, no
   typographic decision surviving any of them.

   Noto Sans is self-hosted at two weights, under the Apache 2.0 licence the
   font ships with (assets/fonts/NOTO-SANS-LICENSE.txt). The serif is gone
   entirely: prose, display
   and controls are one family at different sizes, weights and tracking, and the
   monospace is reserved for instrument readouts — timestamps, coordinates,
   percentages — where its fixed advance is doing a job rather than a style.
   ========================================================================== */
@font-face{
  font-family:"Moon Sans";
  src:url("../assets/fonts/notosans-400.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap;
}
@font-face{
  font-family:"Moon Sans";
  src:url("../assets/fonts/notosans-600.woff2") format("woff2");
  font-weight:600;font-style:normal;font-display:block;
}

/* ==========================================================================
   moon.css — one hierarchy, five states
   ==========================================================================

   R2 was a collection of fixed islands: a masthead, a HUD, a mode rail, a time
   bar, a deck, two panels. Each was fine and together they were a dashboard
   with a Moon behind it. R3 has one rail, one caption, one console, one drawer
   and one advanced panel, and at any moment most of them are not there.

   The layout is driven entirely by two attributes on <html>:
     data-state   loading | entry | opening | act1 | act2 | act3 | observatory
     data-mode    phase | reveal | surface        (free observatory only)

   Motion tokens, used everywhere and nowhere else:
     --ease-ui     interface enter and exit, under 300ms
     --ease-move   moving between compositions
     --ease-sheet  drawers and sheets
   ========================================================================== */

:root{
  color-scheme:dark;

  /* NIGHTGLASS is a NEUTRAL near-black, not a navy. R4's ground was #040405 --
     blue is more than three times red in that value, and across a full screen
     it reads as a wash. These are near-neutral with a whisper of cool so the
     silver Moon still reads as the warmest thing on screen. */
  --void:#040405;
  --deep:#08090c;
  --ink:#eef1f3;
  --dim:#a4aeb6;
  /* #9aa4ad, up from #7f8992. Most of this palette is measured against a dark
     panel, where the old value was a comfortable 5.5:1. But --mute is also the
     colour of the rail buttons, .con-when and .con-zone, and those sit on
     translucent chrome over the LIVE RENDER -- so their real background is
     sometimes a lit lunar surface, where the old value measured 3.05:1 against
     a 4.5 requirement. This reads 3.9:1 over a lit limb and 7.6:1 over the
     dark, which is the trade in the right direction: the failing case improves
     by a quarter and the passing case stays far above the line. text-shadow
     does not count toward contrast and was never the answer here. */
  --mute:#9aa4ad;
  --rule:rgba(255,255,255,.35);
  --rule-soft:rgba(255,255,255,.22);
  --accent:#8fd3c7;
  /* measurements, never controls: renderer.js states the rule and the interface
     was breaking it in four places */
  --data:#9fb6c4;
  --accent-deep:#2f5f58;
  --warm:#ffd9a8;
  --warn:#e0a15c;

  --panel:rgba(9,10,13,.74);
  --panel-solid:rgba(9,10,13,.95);
  --panel-blur:blur(18px) saturate(130%);

  --f-display:"Moon Sans",system-ui,-apple-system,"Segoe UI",Arial,sans-serif;
  --f-body:"Moon Sans",system-ui,-apple-system,"Segoe UI",Arial,sans-serif;
  --f-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"DejaVu Sans Mono",monospace;

  --ease-ui:cubic-bezier(.23,1,.32,1);
  --ease-move:cubic-bezier(.77,0,.175,1);
  --ease-sheet:cubic-bezier(.32,.72,0,1);
  --t-press:120ms;
  --t-ui:260ms;
  --t-move:520ms;

  --rail-h:56px;
  --safe-b:env(safe-area-inset-bottom,0px);
  --safe-t:env(safe-area-inset-top,0px);
  --safe-l:env(safe-area-inset-left,0px);
  --safe-r:env(safe-area-inset-right,0px);
}

*{box-sizing:border-box}
html,body{height:100%}
html{overflow-x:clip;-webkit-text-size-adjust:100%;scrollbar-width:none}
html::-webkit-scrollbar{display:none}
body{
  margin:0;background:var(--void);color:var(--ink);
  font-family:var(--f-body);font-size:16px;line-height:1.62;letter-spacing:.004em;
  -webkit-font-smoothing:antialiased;overflow-x:clip;
}
h1,h2,h3{font-family:var(--f-display);font-weight:600;letter-spacing:-.022em;line-height:1.08;margin:0;text-wrap:balance}
h1{font-size:clamp(30px,4.6vw,56px);letter-spacing:-.035em}
h2{font-size:clamp(21px,2.4vw,30px);letter-spacing:-.026em}
h3{font-size:12.5px;font-family:var(--f-display);font-weight:600;letter-spacing:.14em;
  text-transform:uppercase;color:var(--accent);margin:22px 0 9px}
p{margin:0 0 11px;max-width:62ch;text-wrap:pretty}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.small{font-size:13.5px;color:var(--dim);line-height:1.6;max-width:56ch}
.credit{font-size:13px;color:var(--mute)}
.mark{font-family:var(--f-display);font-weight:600;font-size:10.5px;letter-spacing:.24em;
  text-transform:uppercase;color:var(--dim);margin:0 0 14px}

.skiplink{position:fixed;left:-9999px;top:8px;z-index:400;background:var(--accent);color:#04120f;
  padding:13px 18px;min-height:44px;display:inline-flex;align-items:center;border-radius:4px;
  font-family:var(--f-display);font-weight:600;font-size:13px;text-decoration:none}
.skiplink:focus{left:8px}
/* The ring needs a dark outer edge or it disappears over a lit lunar surface,
   which is exactly what is behind the rail in Explore the surface. Measured
   there without it: 1.07:1. */
:where(a,button,input,select,summary,[tabindex]):focus-visible{
  outline:2px solid var(--accent);outline-offset:3px;
  box-shadow:0 0 0 5px rgba(4,4,5,.92);border-radius:3px}

/* -- buttons ---------------------------------------------------------------
   One family. Primary is the single dominant action in any state; .btn is
   secondary; .quiet is tertiary and never competes.
   ------------------------------------------------------------------------- */
.btn{
  font-family:var(--f-display);font-weight:600;font-size:12.5px;letter-spacing:.085em;text-transform:uppercase;
  padding:12px 17px;min-height:44px;min-width:44px;border:1px solid var(--rule);border-radius:4px;
  background:rgba(255,255,255,.045);color:var(--ink);cursor:pointer;white-space:nowrap;
  transition:background-color var(--t-press) var(--ease-ui),border-color var(--t-press) var(--ease-ui),
             color var(--t-press) var(--ease-ui),transform var(--t-press) var(--ease-ui),opacity var(--t-ui) var(--ease-ui);
}
.btn:hover{border-color:var(--accent-deep);color:var(--accent)}
.btn:active{transform:scale(.97)}
.btn.primary{
  background:var(--accent);border-color:var(--accent);color:#04120f;font-weight:600;
  padding:13px 22px;box-shadow:0 6px 30px -12px rgba(143,211,199,.7);
}
.btn.primary:hover{background:#a3ded3;border-color:#a3ded3;color:#04120f}
.btn.quiet{background:transparent;border-color:transparent;color:var(--mute);padding:11px 12px}
.btn.quiet:hover{color:var(--ink)}
.btn.small{font-size:11px;padding:10px 12px;letter-spacing:.08em}
.btn[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:#04120f}
.btn.round{width:52px;height:52px;min-width:52px;border-radius:50%;padding:0;display:grid;place-items:center}
.btn:disabled,.btn[aria-disabled="true"]{opacity:.45;cursor:default}
.play-glyph{width:0;height:0;border-left:12px solid currentColor;border-top:8px solid transparent;
  border-bottom:8px solid transparent;margin-left:3px;transition:none}
[data-playing="true"] .play-glyph{width:12px;height:14px;border:0;margin-left:0;
  background:linear-gradient(to right,currentColor 0 4px,transparent 4px 8px,currentColor 8px 12px)}

/* -- the stage ------------------------------------------------------------- */
#stage{position:fixed;inset:0;z-index:0;background:var(--void)}
#moon{display:block;width:100%;height:100%;touch-action:pan-y}
[data-mode="surface"] #moon,[data-state="act3"] #moon{touch-action:none;cursor:grab}
[data-mode="surface"] #moon:active,[data-state="act3"] #moon:active{cursor:grabbing}

/* Optical layer: vignette and a static sensor grain. No animation, no time
   uniform, no cost: it is one painted element that never repaints. */
/* One vignette, at z-index 1, below every control. An earlier draft ran this
   at .46 AND left the loading veil's own radial gradient above the interface,
   so the frame was multiplied down twice and the UI was crushed with it. */
#optics{position:fixed;inset:0;z-index:1;pointer-events:none;
  background:
    radial-gradient(125% 100% at 50% 46%,transparent 52%,rgba(0,0,0,.20) 82%,rgba(0,0,0,.42) 100%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
  background-size:auto,140px 140px;
  mix-blend-mode:normal;opacity:.34}

#fallback[hidden]{display:none}
#fallback{position:absolute;inset:0;display:grid;place-content:center;gap:14px;justify-items:center;padding:24px;text-align:center;z-index:2}
#fallback img{max-width:min(520px,80vw);height:auto;border-radius:4px;opacity:.9}
#fallback p{font-family:var(--f-mono);font-size:13px;color:var(--dim);max-width:46ch}

/* the gesture cue: one dot, one line of text, gone forever after the first drag */
#gesture{position:fixed;left:50%;top:56%;transform:translate(-50%,0);z-index:6;pointer-events:none;
  font-family:var(--f-display);font-weight:600;font-size:12.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink);
  display:flex;align-items:center;gap:10px;opacity:0;transition:opacity var(--t-ui) var(--ease-ui);
  text-shadow:0 2px 14px rgba(0,0,0,.9)}
#gesture[data-on="1"]{opacity:.9}
.ges-dot{width:26px;height:26px;border:1px solid var(--ink);border-radius:50%;position:relative;display:block}
.ges-dot::after{content:"";position:absolute;inset:7px;border-radius:50%;background:var(--ink);
  animation:gesSwipe 2.6s var(--ease-move) infinite}
@keyframes gesSwipe{0%,12%{transform:translateX(-7px)}50%{transform:translateX(7px)}88%,100%{transform:translateX(-7px)}}

/* -- loading and entry ----------------------------------------------------- */
/* ==========================================================================
   THE OPENING
   ==========================================================================
   The Moon assembles out of particles. That is the event, and the job of
   everything on this screen is to stay out of its way and then arrive as if it
   belongs to the same light.

   Deleted from the previous revision, deliberately: the concentric target
   rings, the crosshair ticks, the corner brackets, the full-width sight line
   and the per-letter wordmark animation. Together they read as a targeting HUD
   borrowed from another product, and they competed with the one thing worth
   looking at.

   Nothing replaces them. The forming Moon is the only thing on this screen.
   An "aureole" was tried and rejected by Aarush -- see the note further down
   about why a 2D disc over the canvas can never be the right answer here.
   ========================================================================== */

/* The wordmark resolves once, out of the same softness the particles come from:
   it rises a little, it sharpens, and it stops. No letter dances on its own. */
/* Bigger, and tracked TIGHTER rather than looser.
   58px of display type at +0.055em reads thin and spaced -- the tracking that
   helps a 10px label hurts a 70px one. Large type wants negative tracking; the
   letters are already far apart at that size and the word should read as one
   shape. This is the single change that makes the mark feel set rather than
   typed, and it costs nothing. */
.veil-title{
  font-family:var(--f-display);font-weight:600;
  font-size:clamp(38px,7.4vw,76px);letter-spacing:-.012em;
  line-height:.98;margin:0 0 18px;color:#f1f4f6;
  animation:mark-resolve 2000ms cubic-bezier(.17,.84,.24,1) 620ms both}
@keyframes mark-resolve{
  from{opacity:0;letter-spacing:.26em;filter:blur(9px);transform:translateY(7px)}
  55% {opacity:1;filter:blur(1.4px)}
  to  {opacity:1;letter-spacing:-.012em;filter:blur(0);transform:none}
}
.veil-line{
  animation:line-rise 1500ms var(--ease-ui) 1500ms both;
  color:var(--dim);margin:0 0 30px;font-size:15.5px;letter-spacing:.005em}
@keyframes line-rise{from{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}

/* The choices arrive last, after the Moon has finished becoming one. */
#entry{animation:entry-rise 1100ms var(--ease-ui) both}
@keyframes entry-rise{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}

@media (prefers-reduced-motion:reduce){
  .veil-title,.veil-line,#entry{animation:none;opacity:1;filter:none;transform:none;letter-spacing:.055em}
}
[data-motion="reduced"] .veil-title,
[data-motion="reduced"] .veil-line,
[data-motion="reduced"] #entry{animation:none;opacity:1;filter:none;transform:none;letter-spacing:.055em}

#veil{position:fixed;inset:0;z-index:60;display:grid;place-items:center;padding:24px;
  background:radial-gradient(120% 100% at 50% 42%,rgba(4,4,5,.35) 0%,rgba(4,4,5,.86) 62%,rgba(4,4,5,.97) 100%);
  transition:opacity 620ms var(--ease-ui),visibility 620ms}
#veil[hidden]{display:grid}                     /* we fade, never cut */
[data-state="opening"] #veil,[data-state="act1"] #veil,[data-state="act2"] #veil,
[data-state="act3"] #veil,[data-state="observatory"] #veil{
  opacity:0;visibility:hidden;pointer-events:none;background:none}
.veil-inner{text-align:center;max-width:min(560px,92vw);position:relative}


/* Once the entry copy is on screen the reticle steps well back and well out:
   its lines were crossing the sentences and the buttons. */
[data-veil="entry"] 
.veil-title{margin:0 0 10px}
.veil-line{font-size:clamp(15px,1.5vw,18px);color:var(--dim);margin:0 auto 26px;max-width:34ch}
#loadState{transition:opacity 300ms var(--ease-ui)}
[data-veil="entry"] #loadState{opacity:0;height:0;overflow:hidden;margin:0}
.loadbar{width:min(300px,70vw);height:2px;background:rgba(255,255,255,.10);margin:0 auto 12px;overflow:hidden;border-radius:2px}
.loadbar i{display:block;height:100%;width:0%;background:var(--accent);transition:width 420ms var(--ease-ui)}
#loadLabel{font-family:var(--f-display);font-weight:600;font-size:11px;letter-spacing:.18em;
  text-transform:uppercase;color:var(--mute);margin:0}
#entry{opacity:0;transform:translateY(8px);transition:opacity 520ms var(--ease-ui) 120ms,transform 520ms var(--ease-ui) 120ms}
/* display:none, not display:block.
   This was `display:block;pointer-events:none`, which left the entry block in
   flow for the whole loading state -- pushing the acquisition bar up into the
   Moon -- and, worse, VISIBLE: `#entry{animation:entry-rise 1100ms both}` is
   unconditional, so once that animation finished at 1100ms its `to{opacity:1}`
   filled forward and beat the `opacity:0` on this rule. That is the ghost in
   the reduced-motion capture, where the third button painted at its final
   position with its siblings missing. Hidden means out of flow and unpainted;
   the reveal is entry-rise, which now plays when the element appears. */
#entry[hidden]{display:none}
[data-veil="entry"] #entry{opacity:1;transform:none;pointer-events:auto}
.entry-note{font-size:15px;color:var(--dim);max-width:46ch;margin:0 auto 24px}
.entry-actions{display:flex;flex-direction:column;align-items:center;gap:10px}
.entry-actions .btn{min-width:270px}

/* -- the opening title beat ------------------------------------------------ */
/* Placed to the left, where the Moon is not: during the opening the disc sweeps
   in from the lower right, so the words never sit on top of it. */
#openingTitle{position:fixed;left:max(24px,var(--safe-l));top:50%;transform:translateY(-46%);z-index:26;
  max-width:min(430px,44vw);text-align:left;pointer-events:none;opacity:0;
  transition:opacity 900ms var(--ease-ui),transform 1400ms var(--ease-move);transform:translateY(6px)}
#openingTitle[data-on="1"]{opacity:1;transform:translateY(-50%)}
.ot-title{font-size:clamp(34px,6vw,72px);letter-spacing:-.04em;margin:0 0 12px;
  text-shadow:0 4px 60px rgba(0,0,0,.9)}
.ot-line{font-size:clamp(15px,1.6vw,19px);color:var(--dim);margin:0;max-width:30ch;
  text-shadow:0 2px 24px rgba(0,0,0,.9)}

/* -- reduced-motion cross-fade -------------------------------------------- */
/* A cross-fade, not a blackout. An earlier draft ran a full-opacity 260 ms
   wipe in each direction, which under reduced motion produced a one-second
   black screen — precisely the kind of effect the preference exists to avoid. */
#fade{position:fixed;inset:0;z-index:50;background:var(--void);opacity:0;pointer-events:none;
  transition:opacity 180ms linear}
#fade[data-on="1"]{opacity:.72}
[data-motion="reduced"] #fade{transition-duration:.12s}

.stepper{display:flex;flex-wrap:wrap;gap:6px}
.stepper .btn{flex:1 1 auto;min-width:72px}

/* -- the rail --------------------------------------------------------------
   Quiet by construction: one row, one weight of type, no boxes.
   ------------------------------------------------------------------------- */
#rail{position:fixed;top:0;left:0;right:0;z-index:30;height:calc(var(--rail-h) + var(--safe-t));
  padding:var(--safe-t) max(14px,var(--safe-r)) 0 max(14px,var(--safe-l));
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:12px;
  /* deeper at the top and carried further down: the rail's controls sit over
     the live render, so their real background can be a lit lunar surface. The
     old .72->0 ramp left the labels on close to bare Moon by the time the
     gradient had faded, which is where the contrast measurement failed. */
  background:linear-gradient(to bottom,rgba(4,4,5,.94) 0%,rgba(4,4,5,.72) 68%,rgba(4,4,5,0) 100%);
  opacity:0;transform:translateY(-6px);visibility:hidden;
  transition:opacity 500ms var(--ease-ui),transform 500ms var(--ease-ui),visibility 500ms;
  pointer-events:none}
[data-state="act1"] #rail,[data-state="act2"] #rail,[data-state="act3"] #rail,
[data-state="observatory"] #rail{opacity:1;transform:none;pointer-events:auto;visibility:visible}
.rail-mark{margin:0;font-family:var(--f-display);font-weight:600;font-size:11px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--dim);display:flex;align-items:center;gap:9px}
.rail-dot{width:7px;height:7px;border-radius:50%;background:var(--accent);opacity:.85}
#progress{display:flex;gap:4px;justify-self:center}
/* a plain [hidden] loses to the rule above, and the rail then wraps */
#progress[hidden],#railMode[hidden]{display:none}
#progress button{background:transparent;border:0;cursor:pointer;color:var(--mute);
  font-family:var(--f-display);font-weight:600;font-size:11px;letter-spacing:.12em;
  padding:8px 12px;min-height:44px;min-width:44px;
  display:flex;align-items:center;gap:8px;border-radius:4px;
  transition:color var(--t-press) var(--ease-ui),background-color var(--t-press) var(--ease-ui)}
#progress button em{font-style:normal;font-size:10.5px;letter-spacing:.06em;color:var(--mute);opacity:0;
  max-width:0;overflow:hidden;white-space:nowrap;transition:opacity var(--t-ui) var(--ease-ui),max-width var(--t-ui) var(--ease-ui)}
#progress button:hover{color:var(--ink);background:rgba(255,255,255,.05)}
#progress button:hover em{opacity:.8;max-width:130px}
#progress button[aria-pressed="true"]{color:var(--ink);background:transparent;
  box-shadow:inset 0 -2px 0 var(--accent)}
#progress button[aria-pressed="true"] em{opacity:1;max-width:150px;color:var(--dim)}
#progress button[data-visited="1"]{color:var(--dim)}
#railMode{margin:0;justify-self:center;font-family:var(--f-display);font-weight:600;font-size:11px;
  letter-spacing:.18em;text-transform:uppercase;color:var(--dim)}
/* graphics quality: a real listbox, because a native <select> paints a white
   system menu across a near-black exhibit and cannot be styled out of it */
.rail-pop{position:relative}
#btnQuality{display:inline-flex;align-items:center;gap:7px}
.rail-val{font-family:var(--f-mono);font-size:10.5px;letter-spacing:.06em;
  color:var(--accent);text-transform:none}
#qualityMenu,#speedMenu{position:absolute;right:0;top:calc(100% + 7px);z-index:50;margin:0;padding:5px;
  list-style:none;min-width:236px;border-radius:11px;border:1px solid var(--rule);
  background:var(--panel-solid);backdrop-filter:var(--panel-blur);
  -webkit-backdrop-filter:var(--panel-blur);box-shadow:0 18px 44px -18px rgba(0,0,0,.9)}
#qualityMenu[hidden],#speedMenu[hidden]{display:none}
#qualityMenu li,#speedMenu li{display:grid;gap:1px;padding:9px 11px;border-radius:7px;cursor:pointer;
  min-height:44px;align-content:center}
#qualityMenu li b,#speedMenu li b{font-family:var(--f-display);font-size:12.5px;font-weight:600;color:var(--ink)}
#qualityMenu li em,#speedMenu li em{font-style:normal;font-size:11px;color:var(--mute);line-height:1.35}
#qualityMenu li:hover,#speedMenu li:hover{background:rgba(255,255,255,.055)}
#qualityMenu li[aria-selected="true"],#speedMenu li[aria-selected="true"]{background:rgba(255,255,255,.08)}
#qualityMenu li[aria-selected="true"] b,#speedMenu li[aria-selected="true"] b{color:var(--accent)}
#qualityMenu li:focus-visible,#speedMenu li:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
@media (max-width:860px){
  #qualityMenu,#speedMenu{position:fixed;left:8px;right:8px;top:auto;
    bottom:calc(var(--console-h,150px) + var(--safe-b) + 14px);min-width:0}
}

.rail-actions{display:flex;gap:2px;justify-self:end;align-items:center}
.rail-btn{background:transparent;border:0;color:var(--mute);cursor:pointer;text-decoration:none;
  font-family:var(--f-display);font-weight:600;font-size:11.5px;letter-spacing:.11em;text-transform:uppercase;
  padding:10px 12px;min-height:44px;display:flex;align-items:center;gap:8px;border-radius:4px;
  transition:color var(--t-press) var(--ease-ui),background-color var(--t-press) var(--ease-ui)}
.rail-btn:hover{color:var(--ink);background:rgba(255,255,255,.05)}
.rail-btn:active{transform:scale(.97)}
#btnSound[aria-pressed="true"]{color:var(--accent)}
.sound-glyph{width:13px;height:13px;border-radius:50%;border:1.5px solid currentColor;position:relative;display:block;opacity:.85}
#btnSound[aria-pressed="true"] .sound-glyph{background:currentColor}
#btnSound[aria-pressed="false"] .sound-glyph::after{content:"";position:absolute;left:-2px;top:5px;width:17px;height:1.5px;
  background:currentColor;transform:rotate(-45deg)}

/* -- the story caption ----------------------------------------------------- */
/* The caption carries its own darkness. Type over a lit lunar surface is not
   legible on trust, and the Moon moves: what is behind the words in Act I is
   not what is behind them in Act III. */
/* This lived on #caption::before until R4's visual review measured a hard
   vertical seam at the caption's right edge -- a 70% luminance step down a
   straight 720px line across black sky. #caption sets overflow-y:auto to stay
   scrollable on a short screen, which makes it a scroll container, which clips
   its own ::before to the padding box: the gradient's 260px right bleed was
   amputated while still at half opacity. A scrim has to be a sibling, not a
   pseudo-element of a scrolling box. */
#capScrim{position:fixed;left:0;bottom:0;z-index:19;pointer-events:none;
  width:min(820px,64vw);height:min(760px,88svh);opacity:0;
  background:radial-gradient(72% 62% at 20% 56%,rgba(4,4,5,.90) 0%,rgba(4,4,5,.70) 36%,
    rgba(4,4,5,.30) 64%,rgba(4,4,5,0) 84%);
  transition:opacity 460ms var(--ease-ui)}
#caption[data-on="1"] ~ #capScrim{opacity:1}
/* The dock's popover opens upward -- but ONLY on a wide screen. Unscoped, this
   rule sat later in the file than the compact block at equal specificity and
   won everywhere; and because the compact block makes the menu position:fixed,
   `bottom:100%` then resolved against the VIEWPORT and put the whole menu above
   the top of the screen. Speed was unreachable on every phone. */
@media (min-width:861px){
  #speedMenu{right:auto;left:0;top:auto;bottom:calc(100% + 7px);min-width:250px}
}
#btnSpeed{display:inline-flex;align-items:baseline;gap:7px;white-space:nowrap}
#btnSpeed .btn-sub{font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--mute)}
#btnSpeed #speedVal{color:var(--accent);font-family:var(--f-mono);font-size:11px;letter-spacing:.03em}
.con-speed{position:relative}
#caption{position:fixed;left:max(24px,var(--safe-l));
  bottom:calc(var(--console-h,150px) + clamp(28px,4vh,44px) + var(--safe-b));z-index:20;
  width:min(420px,calc(100vw - 48px));max-height:calc(100svh - var(--console-h,150px) - var(--rail-h) - 70px);
  overflow-y:auto;overflow-x:hidden;scrollbar-width:none;
  opacity:0;transform:translateY(10px);pointer-events:none;visibility:hidden;
  transition:opacity 460ms var(--ease-ui),transform 460ms var(--ease-ui),visibility 460ms}
#caption::-webkit-scrollbar{display:none}
#caption[data-on="1"]{opacity:1;transform:none;pointer-events:auto;visibility:visible}
#caption[data-on="0"]{visibility:hidden}
#caption[hidden]{display:block}
.cap-num{display:flex;align-items:baseline;gap:12px;margin:0 0 10px;
  font-family:var(--f-display);font-weight:600;font-size:10.5px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--dim)}
.cap-sub{color:var(--dim);letter-spacing:.14em}
.cap-num,.cap-hint{text-shadow:0 2px 16px rgba(4,4,5,.95),0 0 34px rgba(4,4,5,.8)}
.cap-title{margin:0 0 12px;text-shadow:0 2px 30px rgba(0,0,0,.85)}
.cap-copy{font-size:clamp(15px,1.2vw,17.5px);color:var(--dim);max-width:36ch;margin:0 0 18px;
  text-shadow:0 2px 20px rgba(0,0,0,.8)}
.cap-note{font-size:14px;color:var(--dim);max-width:38ch;margin:0 0 18px;padding:11px 14px;
  border-left:2px solid var(--accent-deep);background:rgba(143,211,199,.06);border-radius:0 4px 4px 0}
.cap-note b{color:var(--accent);font-weight:600}
.cap-legend{display:flex;flex-wrap:wrap;gap:4px 14px;margin-top:10px;
  font-family:var(--f-display);font-weight:600;font-size:10px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--mute)}
.note-map{display:block;margin:10px 0 2px}
.map-cap{display:block;font-style:normal;font-size:12px;line-height:1.45;color:var(--mute);margin-top:6px}
.note-map canvas{width:100%;height:auto;display:block;border:1px solid var(--rule);border-radius:3px;
  background:#0a0d11;image-rendering:auto}
.cap-legend b{display:inline-flex;align-items:center;white-space:nowrap;font-weight:600}
.cap-legend .sw{margin-right:6px;flex:0 0 auto}
.cap-actions{display:flex;flex-wrap:wrap;gap:9px;align-items:center}
#actPrimary{min-width:240px;justify-content:center;text-align:center}
[data-state="observatory"] .cap-hint{display:none}
.cap-hint{margin:16px 0 0;font-family:var(--f-display);font-weight:600;font-size:10.5px;
  letter-spacing:.1em;color:var(--dim);text-transform:uppercase}

/* -- the control dock -------------------------------------------------------
   One surface. Transport, what you are looking at, when, the lunar month, the
   three modes, and — during the story — Back and Next in a fixed place. R3
   scattered these across a caption that moves and a sheet behind a grip.
   --------------------------------------------------------------------------- */
#console{position:fixed;left:50%;transform:translateX(-50%) translateY(12px);
  bottom:calc(clamp(14px,2.6vh,24px) + var(--safe-b));z-index:24;
  width:min(720px,calc(100vw - 40px));
  background:var(--panel);backdrop-filter:var(--panel-blur);-webkit-backdrop-filter:var(--panel-blur);
  border:1px solid var(--rule);border-radius:14px;padding:12px 14px 13px;
  display:grid;gap:10px;opacity:0;pointer-events:none;visibility:hidden;
  transition:opacity 420ms var(--ease-ui),transform 420ms var(--ease-ui),visibility 420ms}
#console[data-on="1"]{opacity:1;transform:translateX(-50%);pointer-events:auto;visibility:visible}
#console[hidden]{display:grid}
.sheet-grip{display:none}

/* --- the console head -------------------------------------------------------
   A GRID, not a flex row.

   The flex version put four things on one line: the transport, a text block
   that was allowed to shrink to nothing (`min-width:0`), two buttons that
   refused to shrink at all (`flex:0 0 auto`), and a 44ch paragraph. At 821x605
   -- the size Aarush reviews at -- the arithmetic left the text block about
   130px, so "Sun 30 Aug, 23:17" wrapped down five lines and the city clipped
   behind the buttons. Nothing was hidden on purpose; the layout simply had no
   way to say which item should give way.

   The grid says it. The transport is its own column and never moves. The middle
   column is the only flexible one and carries the two things a visitor reads --
   what the Moon is doing, and where and when they are looking from -- on
   separate lines, so neither can squeeze the other. Actions and status hold the
   right column at their natural width. Below 900px the right column drops
   underneath instead of competing for the same line.

   The reading order of the source is the reading order of the hierarchy:
   transport, identity, local observation, actions, status. */
.con-head{
  display:grid;
  grid-template-columns:auto minmax(11rem,1fr) auto;
  grid-template-areas:
    "play ident  actions"
    "play local  status";
  align-items:center;
  column-gap:14px;row-gap:4px}
#btnPlay{grid-area:play}
.con-ident{grid-area:ident;min-width:0}
.con-local{grid-area:local;min-width:0}
.con-actions{grid-area:actions;display:flex;gap:7px;align-items:center;justify-content:flex-end}
.con-status{grid-area:status;justify-self:end;justify-content:flex-end;text-align:right;min-width:0;max-width:min(34ch,40vw)}

.con-head p{margin:0;max-width:none}
.con-phase{font-family:var(--f-display);font-size:15.5px;font-weight:600;letter-spacing:-.012em;
  color:var(--ink);display:flex;align-items:baseline;gap:9px;flex-wrap:wrap}
.con-lit{font-family:var(--f-mono);font-size:11.5px;color:var(--data);letter-spacing:.04em;font-weight:400}

/* City, date and time on ONE line, in one place, allowed to wrap as a whole
   rather than a word at a time. This is the line the rejection screenshot
   showed broken into five. */
.con-local{font-family:var(--f-mono);font-size:11.5px;color:var(--mute);letter-spacing:.03em;
  display:flex;gap:8px;align-items:baseline;flex-wrap:wrap}
.con-when{white-space:nowrap}
.con-zone{color:var(--dim);font-size:10px;letter-spacing:.14em;text-transform:uppercase;white-space:nowrap}
.con-zone:empty{display:none}

/* The status is three or four words. If it needs a sentence, the sentence is
   behind Why? and is nobody's default reading. */
/* The badge and its "Why?" share a line.

   "Why?" only became visible in the default state once the opening message was
   supplied -- before that it was hidden, and the status block was one line
   tall. Restoring it correctly added a second line, and on the two tightest
   viewports (844x390 and 375x667) that pushed the last row of the console below
   the fold. It belongs beside the state it explains anyway: "Geocentric view
   WHY?" reads as one thought, and costs nothing. */
.con-status{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px}
.con-badge{font-size:11px;letter-spacing:.02em;color:var(--mute);line-height:1.35}
.con-badge:empty{display:none}
.con-why{margin-top:0}
.con-why[open]{flex-basis:100%}          /* the sentence takes its own line */
.con-why[hidden]{display:none}
.con-why>summary{list-style:none;cursor:pointer;display:inline-block;
  font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);
  padding:2px 0;min-height:0}
.con-why>summary::-webkit-details-marker{display:none}
.con-why>summary:hover{color:var(--ink)}
/* Explicit, not inherited from the UA.

   A closed <details> is supposed to hide everything but its <summary>, and on
   the browser this is tested in it did not: the sentence rendered at full size
   with the details reporting open=false, and collided with the timeline and the
   mode buttons. Chromium hides collapsed content through the ::details-content
   pseudo rather than by setting display on the child, so a stylesheet that
   gives that child any box at all can end up racing it.

   The disclosure still works exactly as before -- this only states the closed
   case outright instead of assuming it. */
.con-why:not([open]) .con-note{display:none}
.con-why .con-note{margin-top:5px;font-size:11.5px;line-height:1.5;color:var(--dim);
  text-align:left;max-width:34ch;margin-left:auto}

/* Below 900px the right-hand column stops competing for the same line and
   goes underneath. This is a REFLOW, not a hide: every item Aarush named --
   city, date, time, phase, the actions and the status -- is still on screen and
   still readable. 900px is chosen because it is comfortably above the 821px
   rejection viewport, so that size gets the two-row form rather than the
   squeezed one. */
@media (max-width:759px){
  .con-head{
    grid-template-columns:auto minmax(0,1fr);
    grid-template-areas:
      "play ident"
      "play local"
      "actions actions"
      "status  status";
    row-gap:8px}
  .con-actions{justify-content:flex-start;flex-wrap:wrap}
  .con-status{justify-self:start;text-align:left;max-width:52ch}
  .con-why .con-note{margin-left:0}
}

/* the lunar month, with its events where they really are */
.con-time{position:relative;padding-bottom:19px}
.con-time-label{margin:0 0 1px;font-family:var(--f-display);font-weight:600;font-size:10px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--mute)}
#scrub{width:100%;height:46px;background:transparent;-webkit-appearance:none;appearance:none;cursor:pointer}
#scrub::-webkit-slider-runnable-track{height:3px;border-radius:2px;background:rgba(255,255,255,.38)}
#scrub::-moz-range-track{height:3px;border-radius:2px;background:rgba(255,255,255,.38)}
#scrub::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:15px;height:15px;
  border-radius:50%;background:var(--accent);margin-top:-6px;
  box-shadow:0 0 0 4px rgba(143,211,199,.16);transition:box-shadow var(--t-press) var(--ease-ui)}
#scrub::-moz-range-thumb{width:15px;height:15px;border:0;border-radius:50%;background:var(--accent);
  box-shadow:0 0 0 4px rgba(143,211,199,.16)}
#scrub:active::-webkit-slider-thumb{box-shadow:0 0 0 7px rgba(143,211,199,.22)}
#vol{width:100%;accent-color:var(--accent);height:38px;background:transparent}
#scrubMarks{position:absolute;left:0;right:0;top:33px;height:9px;pointer-events:none}
#scrubMarks span{position:absolute;top:0;width:1px;height:6px;background:var(--rule);transform:translateX(-.5px)}
#scrubMarks span[data-major="1"]{height:9px;background:rgba(143,211,199,.6)}
#scrubMarks b{position:absolute;top:9px;transform:translateX(-50%);font-family:var(--f-display);
  font-size:10px;letter-spacing:.05em;color:var(--mute);text-transform:uppercase;font-weight:600;white-space:nowrap}
#scrubMarks b[data-edge="left"]{transform:none;left:0!important}
#scrubMarks b[data-edge="right"]{transform:none;right:0!important;left:auto!important}

.con-modes{display:flex;gap:9px;align-items:center;flex-wrap:wrap}
/* The three mode buttons and every .seg button fill their container edge to
   edge, and the container hides overflow -- so the global focus ring, drawn at
   outline-offset:3px, was clipped away entirely. These are the observatory's
   primary navigation and about twenty controls in Advanced: they had no visible
   focus indicator at all. An inset ring lands inside the clip. */
#modeSwitch button:focus-visible,.seg button:focus-visible{
  outline:2px solid var(--accent);outline-offset:-3px;border-radius:2px}
#modeSwitch{display:grid;grid-template-columns:repeat(3,1fr);gap:0;flex:1 1 340px;
  border:1px solid var(--rule);border-radius:8px;overflow:hidden}
#modeSwitch button{background:transparent;border:0;border-right:1px solid var(--rule-soft);
  color:var(--dim);cursor:pointer;padding:9px 11px;min-height:48px;text-align:left;
  display:grid;gap:1px;transition:background-color var(--t-press) var(--ease-ui),
  color var(--t-press) var(--ease-ui)}
#modeSwitch button:last-child{border-right:0}
#modeSwitch button b{font-family:var(--f-display);font-size:12.5px;font-weight:600;letter-spacing:-.005em}
#modeSwitch button em{font-style:normal;font-family:var(--f-display);font-weight:600;font-size:10px;
  letter-spacing:.12em;text-transform:uppercase;color:var(--mute)}
#modeSwitch button:hover{background:rgba(255,255,255,.045);color:var(--ink)}
#modeSwitch button[aria-pressed="true"]{background:rgba(255,255,255,.07);color:var(--ink);
  box-shadow:inset 0 -2px 0 var(--accent)}
.con-speed{flex:0 1 150px}
.con-speed select{min-height:44px}

/* Back and Next live here, in one place that never moves */
.con-extra-row{display:none}          /* desktop keeps these in the head row */
.con-story{display:flex;align-items:center;gap:10px}
.con-story[hidden]{display:none}
.con-where{margin:0;flex:1 1 auto;font-family:var(--f-display);font-weight:600;font-size:11px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--mute)}
#storyNext{background:var(--accent);border-color:var(--accent);color:#04120f}
#storyNext:hover{background:#a3ded3;border-color:#a3ded3;color:#04120f}

/* what each state actually needs */
[data-state="act1"] #console .con-modes,
[data-state="act2"] #console .con-modes,
[data-state="act3"] #console .con-modes{display:none}
[data-state="act2"] #console .con-time,
[data-state="act3"] #console .con-time{display:none}
[data-state="act1"] #console .con-actions,
[data-state="act2"] #console .con-actions,
[data-state="act3"] #console .con-actions{display:none}
[data-state="act2"] #console #btnPlay,
[data-state="act3"] #console #btnPlay{display:none}

/* -- the contextual drawer ------------------------------------------------- */
#drawer{position:fixed;right:max(20px,var(--safe-r));bottom:calc(clamp(16px,3vh,28px) + var(--safe-b));
  z-index:22;width:min(330px,calc(100vw - 40px));
  background:var(--panel);backdrop-filter:var(--panel-blur);-webkit-backdrop-filter:var(--panel-blur);
  border:1px solid var(--rule);border-radius:14px;padding:16px 17px;
  max-height:min(72vh,640px);
    mask-image:linear-gradient(to bottom,#000 0,#000 calc(100% - 28px),transparent 100%);overflow-y:auto;scrollbar-width:thin;
  opacity:0;transform:translateY(10px);pointer-events:none;visibility:hidden;
  transition:opacity 380ms var(--ease-sheet),transform 380ms var(--ease-sheet),visibility 380ms}
#drawer[data-on="1"]{opacity:1;transform:none;pointer-events:auto;visibility:visible}
#drawer[data-on="0"]{visibility:hidden}
#drawer[hidden]{display:block}
.drawer-title{font-size:19px;margin:0 0 3px}
.drawer-sub{font-family:var(--f-display);font-weight:600;font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--dim);margin:0 0 12px}
.drawer-copy{font-size:14.5px;color:var(--dim);margin:0 0 14px}
#drawerBody{display:grid;gap:11px}
.drawer-row{display:flex;justify-content:space-between;gap:12px;font-family:var(--f-mono);font-size:11.5px}
.drawer-row dt{color:var(--mute)}
.drawer-row dd{margin:0;color:var(--ink);font-variant-numeric:tabular-nums}
.drawer-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:4px}

/* the coverage meter, used in Act II and in the reveal drawer */
.cov-meter{position:relative;height:9px;border:1px solid var(--rule);border-radius:3px;
  background:#141a21;overflow:hidden;margin:4px 0 8px}
.cov-meter i{position:absolute;inset:0 auto 0 0;width:var(--cum,0%);background:#6FB7DD;
  transition:width 420ms var(--ease-ui)}
.cov-meter u{position:absolute;left:50%;top:0;bottom:0;width:1px;background:rgba(255,255,255,.5);text-decoration:none}
.cov-legend{font-family:var(--f-display);font-weight:600;font-size:10px;color:var(--mute);margin:7px 0 0;
  display:flex;gap:12px;align-items:center;flex-wrap:wrap}
.sw{display:inline-block;width:11px;height:11px;border-radius:2px;vertical-align:-1px;margin-right:5px}
.sw-seen{background:#6fb7dd}.sw-unseen{background:#39404b}.sw-now{background:#ffc98a}
.big-number{font-family:var(--f-display);font-size:34px;letter-spacing:-.03em;color:var(--ink);
  font-variant-numeric:tabular-nums;line-height:1}
.big-label{font-family:var(--f-display);font-weight:600;font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--mute)}

/* -- advanced science ------------------------------------------------------ */
/* Docked, not modal. R3 marked the rail, console, drawer, caption AND stage
   inert while Advanced was open, so opening the science panel switched off the
   Moon. It now sits beside the experience and everything else keeps working. */
/* Advanced docks on the LEFT on a wide screen.

   R4 docked it right, at the same z-band as the contextual drawer, which is
   also right -- so opening the science panel covered the one card that explains
   what the current mode is showing. It was proudly non-modal and geometrically
   modal. Left is free: the caption only occupies it during the guided story,
   and Advanced is hidden then anyway. */
#advanced{position:fixed;top:calc(var(--rail-h) + var(--safe-t) + 8px);left:8px;right:auto;
  bottom:calc(var(--console-h,150px) + clamp(14px,2.6vh,24px) + var(--safe-b) + 8px);
  z-index:40;width:min(380px,calc(100vw - 16px));border-radius:12px;
  background:var(--panel-solid);backdrop-filter:var(--panel-blur);-webkit-backdrop-filter:var(--panel-blur);
  border:1px solid var(--rule);
  transform:translateX(calc(-100% - 16px));visibility:hidden;
  transition:transform 380ms var(--ease-sheet),visibility 0s linear 380ms;
  display:flex;flex-direction:column;padding-top:var(--safe-t)}
#advanced[data-on="1"]{transform:none;visibility:visible;
  transition:transform 380ms var(--ease-sheet),visibility 0s}
#advanced[hidden]{display:flex}
.adv-head{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:16px 18px 12px;border-bottom:1px solid var(--rule-soft)}
.adv-head h2{font-size:17px}
.adv-scroll{overflow-y:auto;padding:4px 18px calc(28px + var(--safe-b));scrollbar-width:thin}
.adv-block{padding-bottom:8px;border-bottom:1px solid var(--rule-soft);margin-bottom:8px}
.adv-block:last-child{border-bottom:0}
.adv-gloss{display:block;font-family:var(--f-body);font-size:12px;letter-spacing:0;text-transform:none;
  color:var(--mute);margin-top:3px}
#advanced dl{margin:0;display:grid;gap:6px;font-family:var(--f-mono);font-size:11.5px}
#advanced dl>div{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap}
#advanced dt{color:var(--dim);flex:1 1 auto}
.dt-gloss{display:block;font-family:var(--f-display);font-style:normal;font-weight:400;
  font-size:11.5px;letter-spacing:0;color:var(--mute);margin-top:1px;max-width:26ch}
#advanced dd{margin:0;color:var(--ink);font-variant-numeric:tabular-nums;text-align:right;overflow-wrap:anywhere}
#advanced ul{margin:0;padding:0;list-style:none;font-family:var(--f-mono);font-size:11.5px;display:grid;gap:5px}
#advanced ul li{display:flex;justify-content:space-between;gap:12px;color:var(--dim)}
.opt{padding:12px 0;border-top:1px solid var(--rule-soft)}
.opt:first-of-type{border-top:0}
.opt>span{display:block;font-family:var(--f-display);font-weight:600;font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--mute);margin-bottom:7px}
.seg{display:flex;border:1px solid var(--rule);border-radius:5px;overflow:hidden}
.seg button{flex:1;background:transparent;border:0;border-right:1px solid var(--rule);color:var(--dim);
  font-family:var(--f-display);font-weight:600;font-size:11px;letter-spacing:.05em;padding:12px 5px;min-height:44px;
  cursor:pointer;text-transform:uppercase;
  transition:background-color var(--t-press) var(--ease-ui),color var(--t-press) var(--ease-ui)}
.seg button:last-child{border-right:0}
/* Selection, not action. A solid accent fill is the visual weight of
   .btn.primary -- the one dominant action on the screen -- and the View group
   alone has six segmented controls, so opening the Science Lab produced six
   solid mint rectangles, none of which does anything when pressed. They mark a
   state. This is the treatment #modeSwitch already uses for exactly the same
   job, so the panel now has one vocabulary for "selected" instead of two. */
.seg button[aria-pressed="true"]{background:rgba(143,211,199,.13);color:var(--accent);
  box-shadow:inset 0 -2px 0 var(--accent)}
.opt .small{margin-top:8px}
#covMap{width:100%;height:auto;display:block;border:1px solid var(--rule);border-radius:4px;background:#0a0d11}
.cov-nums{margin:11px 0 10px}
.cov-nums dd{color:var(--accent)}
.cov-run{display:flex;gap:8px;align-items:end;flex-wrap:wrap;margin-bottom:10px}
.field{display:flex;flex-direction:column;gap:4px;min-width:0}
.field.inline{flex:1 1 150px}
.field>span{font-family:var(--f-display);font-weight:600;font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--mute)}
.field select,.field input{background:rgba(255,255,255,.05);border:1px solid var(--rule);color:var(--ink);
  font-family:var(--f-mono);font-size:16px;padding:10px 10px;border-radius:5px;min-height:44px;width:100%}
#isolateNote{color:var(--warn)}
#isolateNote strong{color:var(--warn)}

/* -- the written page ------------------------------------------------------ */
/* When the written page is what you are looking at, the instrument's chrome is
   not. Toggled by an IntersectionObserver rather than by scroll position, so it
   works the same on every device. */
[data-reading="1"] :is(#rail,#console,#drawer,#caption){opacity:0;pointer-events:none;visibility:hidden;
  transition:opacity 260ms var(--ease-ui),visibility 260ms}
[data-reading="1"] #optics{opacity:.2}

/* With JavaScript running, the exhibit is a fixed application and the document
   does not scroll: an earlier draft printed "scroll to continue", and scrolling
   both scrolled into the essay AND advanced the story to its end, so a visitor
   who did the most natural thing on the web lost the whole story silently.
   Without JavaScript the article is exactly where it was: in the flow, below
   the exhibit, fully crawlable. */
[data-js="1"]{overflow:hidden}
[data-js="1"] #read{position:fixed;inset:0;z-index:46;margin:0;max-width:none;
  overflow-y:auto;overscroll-behavior:contain;background:rgba(4,4,5,.97);
  padding:clamp(60px,9vh,110px) max(20px,calc(50vw - 34ch)) 90px;
  opacity:0;visibility:hidden;transition:opacity 320ms var(--ease-ui),visibility 320ms}
[data-js="1"][data-reading="1"] #read{opacity:1;visibility:visible}
#readClose{position:fixed;top:max(14px,var(--safe-t));right:max(14px,var(--safe-r));z-index:47;
  display:none}
[data-js="1"][data-reading="1"] #readClose{display:inline-flex}

#read{position:relative;z-index:5;margin:100svh auto 0;max-width:70ch;
  padding:clamp(48px,8vw,110px) clamp(20px,4vw,30px) clamp(64px,10vw,130px);
  background:linear-gradient(180deg,transparent,var(--void) 10%,var(--void))}
#read h2{margin-bottom:16px}
#read p{color:var(--dim)}

#perf{position:fixed;left:10px;bottom:10px;z-index:45;font-family:var(--f-mono);font-size:10px;
  color:var(--mute);background:rgba(5,6,10,.8);padding:8px 10px;border:1px solid var(--rule);
  border-radius:4px;white-space:pre;line-height:1.5}

/* ==========================================================================
   compact: a phone gets its own composition, not a squeezed desktop
   ========================================================================== */
@media (max-width:860px) and (min-height:501px){
  :root{--rail-h:52px}
  #rail{grid-template-columns:1fr auto;gap:8px;
    padding-left:max(12px,var(--safe-l));padding-right:max(12px,var(--safe-r))}
  /* Compact progress: three tappable marks, so a phone visitor knows where in
     the story they are and can move between acts. An earlier draft dropped the
     indicator entirely on small screens. */
  #progress{grid-column:2;grid-row:1;justify-self:end;gap:0;margin-right:2px}
  #progress button{padding:10px 7px;min-width:26px;min-height:44px;font-size:11px}
  #progress button em{display:none}
  #progress button[aria-pressed="true"]{box-shadow:inset 0 -2px 0 var(--accent);color:var(--ink)}
  .rail-actions{grid-column:3}
  #rail{grid-template-columns:1fr auto auto;gap:6px}
  .rail-mark{font-size:10px;letter-spacing:.16em}
  /* THE WORDMARK GIVES WAY TO THE CONTROLS.

     Measured at 375, 390 and 412px, the rail overflowed its own box by 69, 54
     and 32px, and what fell off the right-hand edge was #btnRead -- "Explain",
     the way into the written page. It was not scrollable and not wrapped; it
     was simply outside the rail, which clips. That is a control being hidden by
     an overflow, which is the thing this pass exists to stop.

     Something has to go, and it is the brand mark: the page has a <title>, the
     entry screen has just said the name in 44px type, and "Explain" is a
     function. Below 430px the mark stands down and the rail fits. */
  @media (max-width:430px){ .rail-mark{display:none} }
  /* three items in a two-column rail wrapped the actions onto the Moon */
  #railMode{display:none}
  .rail-btn{padding:10px 10px;font-size:12px;letter-spacing:.06em;min-width:44px}
  /* the rail must not wrap on a phone: two of its labels were long enough to
     take a second line and collide with their neighbours */
  .rail-long{display:none}
  #btnRead .rail-short{display:inline}
  .rail-actions{gap:10px;flex-wrap:nowrap}
  .rail-btn{white-space:nowrap}
  #btnQuality .rail-label{position:static;width:auto;height:auto;clip-path:none;
    overflow:visible;font-size:9.5px;letter-spacing:.14em}
  /* the lunation labels collide at 360px: keep the ones that carry the shape */
  #scrubMarks b[data-minor="1"]{display:none}
  .rail-btn .rail-label{position:absolute;width:1px;height:1px;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap}   /* hidden, but still an accessible name */
  #btnStory,#btnRead{font-size:12px}
  .btn.small{font-size:12px;padding:12px 13px;min-height:44px}
  .btn.tiny,.drawer-actions .btn{font-size:12px;min-height:44px}
  .cap-legend{font-size:11px}
  .con-lit,.con-date{font-size:12.5px}
  .drawer-row{font-size:13px}

  /* the caption becomes the bottom half of the screen, above the console */
  #caption{left:12px;right:12px;width:auto;bottom:calc(var(--console-h,104px) + 14px + var(--safe-b));
    max-height:min(48svh,420px);overflow-y:auto;scrollbar-width:thin}
  .cap-title{font-size:clamp(21px,6.2vw,28px)}
  .cap-copy{font-size:15.5px;max-width:none}
  .cap-actions .btn{flex:1 1 auto;justify-content:center;text-align:center}
  .cap-actions .btn.primary{flex-basis:100%}
  .cap-hint{display:none}

  /* The dock on a phone: everything a beginner needs, always visible, in a
     fixed order, with nothing behind a grip. R3 hid the transport, the
     timeline and the modes behind an unlabelled 44px handle and Aarush could
     not find them — which is exactly what a first-time visitor would do. */
  #console{left:0;right:0;bottom:0;width:auto;transform:translateY(10px);
    border-radius:16px 16px 0 0;border-bottom:0;
    padding:10px max(12px,var(--safe-l)) calc(12px + var(--safe-b)) max(12px,var(--safe-r));
    gap:9px;max-height:none;overflow:visible;box-sizing:border-box}
  #console[data-on="1"]{transform:none}
  #console *{min-width:0;max-width:100%}
  .con-head{gap:11px}
  .con-phase{font-size:14.5px}
  .con-when{white-space:nowrap;font-size:12px}
  /* they move to .con-extra-row; the grid area goes with them, or the row
     stays behind as an empty gap under the local line */
  .con-actions{display:none}
  .con-head{grid-template-areas:"play ident" "play local" "status status"}
  .con-time{padding-bottom:20px}
  /* One row of modes, and one row carrying everything else. R4 first shipped
     the speed control on its own line and the three extra buttons on another,
     which took the dock past half the screen -- and on a phone the Moon has to
     keep the screen. Speed sits with the buttons; both are 44px tall. */
  .con-modes{gap:6px;row-gap:0}
  .con-speed{display:flex;flex-wrap:wrap;min-width:0}   /* or the hint lands beside the button */
  .con-speed select{min-height:44px;width:100%}
  /* Reset view exists twice on a phone -- once in the modes row, once in the
     extra row. One of them is enough, and the row it leaves behind is 44px of
     the Moon's screen. */
  #console .con-modes #btnResetView{display:none}
  .con-speed{flex:1 1 100%}
  #modeSwitch{flex:1 1 100%;grid-template-columns:repeat(3,1fr)}
  #modeSwitch button{min-height:46px;padding:6px 5px;text-align:center;justify-items:center}
  #modeSwitch button b{font-size:11.5px;line-height:1.12;hyphens:auto}
  #modeSwitch button em{display:none}
  .con-speed{display:flex}
  #btnResetView{display:none}
  /* the row that carries what the head row could not */
  /* WRAP INSIDE THE BUTTON, NOT ACROSS THE ROW.

     Three buttons on one line as flex items gave each about 113px at 375px,
     and "Show my Moon now" needs 201 on one line -- so its label was clipped
     inside its own button. Letting the ROW wrap fixed the clipping and cost a
     whole extra 44px row, which pushed "Reset view" off the bottom of an
     iPhone SE.

     A three-column grid with the label allowed to take two lines does both: one
     row, every label whole, and each target still comfortably over 44px. Text
     wrapping on purpose is fine; text being cut off is not. */
  .con-extra-row{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
  .con-extra-row .btn{
    min-height:46px;padding:7px 6px;line-height:1.2;
    white-space:normal;text-align:center;hyphens:auto}
  /* desktop hides these during the guided story; a phone must agree, or the
     story is interrupted by a "Reset view" for a view you have not taken and a
     "Show me tonight's Moon" that silently destroys the act you are watching */
  [data-state="act1"] .con-extra-row,
  [data-state="act2"] .con-extra-row,
  [data-state="act3"] .con-extra-row{display:none}
  .con-extra-row .btn{flex:1 1 auto;justify-content:center}
  .con-story{gap:6px}
  .con-story .btn{flex:0 0 auto}
  .con-where{font-size:10.5px;text-align:center}
  /* #storySkip is NOT hidden here. It used to be, "because Skip lives in the
     rail" -- while line 962 hides the rail's #btnStory during every act
     "because the dock duplicates it". Each rule assumed the other control
     existed, so on a phone in any act BOTH were display:none and the story
     could not be skipped at all. The entry screen promises "You can skip it at
     any point." The dock keeps the skip on a phone; the rail keeps it on a
     desktop; neither is now conditional on the other. */

  /* The drawer is not a floating card on a phone: it is moved into the console
     sheet by app.js, so opening the sheet is the one gesture that reveals
     everything about the current view. Nothing floats over the Moon at rest. */
  /* The drawer explains the current mode, and on a phone it is the ONLY place
     that does -- but the dock has to stay a dock. Bounded, and it scrolls. */
  #console{max-height:56svh}
  /* Inside the sheet the drawer keeps the one thing a phone visitor cannot get
     anywhere else -- the sentence that says what the current mode is showing.
     The numbers, the meter, the legend and the interval buttons are all in
     Advanced, which is one tap away and does not cost the Moon its screen. */
  #drawer .drawer-title,#drawer .drawer-sub,#drawer #drawerBody{display:none}
  #drawer{position:static;left:auto;right:auto;bottom:auto;width:auto;
    max-height:min(11svh,84px);overflow-y:auto;
    background:transparent;backdrop-filter:none;-webkit-backdrop-filter:none;border:0;
    border-top:1px solid var(--rule-soft);border-radius:0;padding:14px 2px 2px;
    transform:none;opacity:1}
  #drawer[data-on="0"]{display:none}
  /* R4 review: this line meant a phone visitor tapped "Peek around the edges",
     watched the Moon turn blue and orange, and had no words anywhere on screen
     telling them what the colours meant -- the exact complaint that produced R3,
     reproduced on mobile. The drawer stays. It restyles to an inline block
     inside the sheet, so it costs the Moon nothing at rest. */
  /* On a phone Advanced is a sheet from the bottom, capped so the Moon is never
     wholly replaced by a settings panel, with its own scroll and a pinned head. */
  /* The sheet stops ABOVE the console instead of on top of it.

     It used to be `bottom:0; max-height:72svh`, and both panels are
     bottom-anchored -- so at 412x915 a 659px sheet covered the whole console:
     the transport, the phase, the city, the date and the timeline all
     disappeared behind it. Aarush's requirement is that opening the Lab must
     not make the console unusable, and a panel you cannot see is not usable.

     --console-h is measured and published by app.js for exactly this kind of
     layout, so the sheet is bounded by it. On a 915px phone that still leaves
     the Lab about 570px, which is more than it had vertical content for; what
     it loses is the part that was lying over the readout. It still slides up
     from below, so it still reads as a sheet. */
  #advanced{left:0;right:0;width:100%;border-left:0;border-radius:14px 14px 0 0;
    top:auto;bottom:calc(var(--console-h,150px) + 8px + var(--safe-b));
    max-height:calc(100svh - var(--console-h,150px) - var(--rail-h) - 26px);
    transform:translateY(calc(100% + var(--console-h,150px) + 16px))}
  #advanced .adv-head{position:sticky;top:0;z-index:2;background:var(--panel-solid);
    padding-top:calc(12px + var(--safe-t) * 0)}
  #advanced .adv-scroll{overflow-y:auto;overscroll-behavior:contain;
    padding-bottom:calc(18px + var(--safe-b));
    mask-image:linear-gradient(to bottom,#000 0,#000 calc(100% - 22px),transparent 100%)}
  #advanced[data-on="1"]{transform:none}
  .adv-head{padding-top:calc(14px + var(--safe-t))}

  .entry-actions .btn{min-width:min(320px,86vw);width:100%}
  .entry-note{font-size:15px}
  /* The exhibit owns the first screen; the article begins below it. Dropping
     this on phones is what let the written page slide up over the Moon. */
  #read{margin-top:100svh;padding-top:44px}
  #gesture{top:auto;bottom:calc(var(--peek,104px) + 92px + var(--safe-b))}
}

/* Anything short — a phone on its side, or a laptop at 200% zoom — gets the
   inline composition, with every control present and no hidden grip. */
@media (max-height:500px){
  #console .con-actions{display:flex}
  #modeSwitch{grid-template-columns:repeat(3,1fr)}
  #modeSwitch button b{font-size:11.5px}
  #modeSwitch button em{font-size:9.5px}
  #progress{display:flex}
  #progress button em{display:none}
  #console{max-height:none;overflow:visible}
}

/* short landscape: the Moon goes to one side and the chrome down the other */
@media (max-height:500px) and (orientation:landscape){
  /* Left column: the words above the transport, both clear of each other and
     of the Moon on the right. An earlier draft let the console sit on top of
     the caption's primary button. */
  #caption{bottom:auto;top:calc(var(--rail-h) + 6px);max-height:calc(100svh - var(--rail-h) - 150px);
    width:min(360px,46vw);left:max(14px,var(--safe-l));right:auto;padding-bottom:4px}
  #caption::before{inset:-24px -60px -20px -40px}
  .cap-title{font-size:19px;margin-bottom:7px}
  .cap-copy{font-size:13.5px;margin-bottom:10px;line-height:1.45}
  .cap-num{margin-bottom:6px;font-size:9.5px}
  .cap-note{font-size:12.5px;padding:8px 10px;margin-bottom:10px}
  .cap-actions{gap:7px}
  .cap-actions .btn{font-size:11.5px;padding:11px 13px}
  #console{left:max(14px,var(--safe-l));right:auto;transform:none;width:min(400px,48vw);
    bottom:calc(8px + var(--safe-b));border-radius:12px;max-height:none;overflow:visible;
    padding:8px 11px 10px;gap:6px}
  #console .con-time{padding-bottom:11px}
  #console .btn.round{width:44px;height:44px;min-width:44px}

  #console[data-on="1"]{transform:none}
  #drawer{right:max(14px,var(--safe-r));left:auto;width:min(330px,40vw);
    bottom:calc(10px + var(--safe-b));max-height:calc(100svh - var(--rail-h) - 30px)}
  #gesture{bottom:auto;top:60%}

  /* A LANDSCAPE PHONE IS NOT A SMALL DESKTOP, AND IT IS NOT A TALL PHONE.

     The compact block that carries every phone rule is gated on
     `(max-width:860px) and (min-height:501px)`. A 844x390 landscape phone is
     narrow enough for those rules and far too short for them, so it matched
     none of them: the rail kept its full-length "What am I looking at?", which
     wrapped over four lines and clipped, and the console kept a desktop-height
     stack with `max-height:none` -- so it grew up the screen until it was
     underneath the rail, with the two drawing over each other. Measured at
     844x390: the transport, the status badge and the Lab button were all
     covered.

     390px of height is the entire budget. So: short rail labels, one line of
     modes without their sub-labels, and a console that is not allowed to reach
     the rail. */
  .rail-long{display:none}
  #btnRead .rail-short{display:inline}
  /* The quarter labels collide on a track this narrow -- measured, "LAST
     QUARTER" and "NEXT NEW" ran into each other. The compact block already
     drops them for exactly this reason, but it is gated on min-height:501px
     and so never applies at 390px tall. New and full stay: they are what gives
     the bar its shape. */
  #scrubMarks b[data-minor="1"]{display:none}
  #console{max-height:calc(100svh - var(--rail-h) - var(--safe-t) - 14px)}
  #modeSwitch button em{display:none}
  /* 44, not 40. Saving four pixels of height by shrinking the primary
     navigation below the minimum touch target is not a trade worth making on
     the one layout that is definitely being touched. The height comes back
     from the paddings around it instead. */
  #modeSwitch button{min-height:44px;padding:4px 6px}
  #modeSwitch button b{font-size:11px}
  .con-phase{font-size:14px}
  .speed-hint{display:none}          /* it repeats what the button already says */
  #console .con-time{padding-bottom:6px}
  #console{padding:7px 10px 8px;gap:5px}
  /* The console here is a 400px column, so the head takes its narrow form:
     three columns need 538px and overflowed, which pushed the transport to
     x = -89 -- off the screen, with the Lab not even open yet. */
  .con-head{
    row-gap:4px;
    grid-template-columns:auto minmax(0,1fr);
    grid-template-areas:
      "play ident"
      "play local"
      "actions actions"
      "status  status"}
  .con-actions{justify-content:flex-start;flex-wrap:wrap}
  .con-status{justify-self:start;text-align:left;max-width:none}
  .con-why .con-note{margin-left:0}

  /* And the Lab goes to the other side. In landscape the console is a LEFT
     column, and the Lab's desktop rule also docks left -- so opening it laid a
     380px panel straight over the transport and the readout. The right half is
     where the drawer lives, and the Lab supersedes the drawer while it is open,
     so that is the side with room. The console stays visible and operable
     throughout, which is the whole requirement. */
  #advanced{
    left:auto;right:max(14px,var(--safe-r));
    width:min(380px,46vw);
    top:calc(var(--rail-h) + 6px);bottom:calc(8px + var(--safe-b));
    max-height:none;border-radius:12px;
    transform:translateX(calc(100% + 16px))}
  #advanced[data-on="1"]{transform:none}
}

/* ==========================================================================
   reduced motion, transparency, contrast, forced colours
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.14s !important}
  .ges-dot::after{animation:none}
}
[data-motion="reduced"] #veil{transition-duration:.2s}
@media (prefers-reduced-transparency:reduce){
  #console,#drawer,#advanced{background:var(--panel-solid);backdrop-filter:none;-webkit-backdrop-filter:none}
  #optics{opacity:.3}
}
@media (prefers-contrast:more){
  :root{--dim:#d5dbe0;--mute:#aab3ba;--rule:rgba(255,255,255,.55);--rule-soft:rgba(255,255,255,.4)}
  #console,#drawer,#advanced{background:#05070c;backdrop-filter:none}
}
@media (forced-colors:active){
  #console,#drawer,#advanced,#veil,#caption{background:Canvas;border-color:CanvasText}
  .btn,.seg button,#progress button,.rail-btn,#modeSwitch button{border:1px solid CanvasText;color:CanvasText}
  /* forced-color-adjust:none is required here: Highlight resolves
     semi-transparent, and Chrome then paints a Canvas backplate behind the
     label, which rendered every selected segment as a blank white rectangle */
  .btn.primary,.btn[aria-pressed="true"],.seg button[aria-pressed="true"],
  #progress button[aria-pressed="true"],#modeSwitch button[aria-pressed="true"]{
    forced-color-adjust:none;background:Highlight;color:HighlightText;border-color:Highlight}
  /* colour is not available, so the swatches carry a shape instead */
  .sw{forced-color-adjust:none;background:Canvas;border:2px solid CanvasText}
  .sw-seen{border-style:dashed}
  .sw-unseen{border-style:dotted}
  .sw-now{border-style:solid;background:CanvasText}
  /* the play triangle is drawn out of transparent borders, which forced colours
     fills in, turning it into a solid square */
  .play-glyph{display:none}
  .fc-label{display:inline !important}
  #optics{display:none}
}
.fc-label{display:none}

/* Defaults for the wide layout. These MUST be scoped: media queries add no
   specificity, so unscoped they beat the compact block purely by source order
   and the phone rendered both the long label and the short one -- which is
   what pushed the rail's actions off the right-hand edge. */
@media (min-width:861px){
  .rail-short{display:none}
  .rail-long{display:inline}
}

/* A selected segment is filled with the accent, so an inset accent ring on it is
   accent-on-accent: 1.00:1, literally not rendered. The selected button is also
   the one a keyboard user lands on most often. */
/* #modeSwitch and .seg now share the tinted treatment, so the ring is legible
   against it without the dark-on-accent inversion the solid fill needed. The
   inset underline is kept so the selected state survives the focus ring. */
.seg button[aria-pressed="true"]:focus-visible,
#modeSwitch button[aria-pressed="true"]:focus-visible{
  box-shadow:inset 0 -2px 0 var(--accent)}

/* The rail sits over the render with no backplate, and in surface mode the
   render behind it is a lit lunar surface. Measured there: 1.02:1. */
#rail{background:linear-gradient(180deg,rgba(4,4,5,.88) 0%,rgba(4,4,5,.64) 68%,rgba(4,4,5,0) 100%)}
.rail-btn,.rail-mark,#progress button{text-shadow:0 1px 3px rgba(4,4,5,.95)}
#gesture{text-shadow:0 1px 4px rgba(4,4,5,.96),0 0 18px rgba(4,4,5,.85)}

/* Boundaries and swatches that measured under 3:1 against their own grounds. */
:root{--rule-soft:rgba(255,255,255,.36);--accent-deep:#4a8a80}
.sw-unseen{background:#5a626e}
/* the selected option must not be carried by hue alone */
#qualityMenu li[aria-selected="true"],#speedMenu li[aria-selected="true"]{
  box-shadow:inset 3px 0 0 var(--accent)}

/* opacity:0 removes an element from neither the tab order nor the accessibility
   tree, and #entry[hidden]{display:block} defeats the hidden attribute -- so the
   two entry buttons were focusable and readable during loading. */
#entry[hidden]{visibility:hidden}
[data-veil="entry"] #entry{visibility:visible}

/* the caption's own focus rings were clipped by its overflow */
#caption{padding-left:4px;padding-right:4px}

/* ==========================================================================
   opening composition, phone, and the last review's findings
   ========================================================================== */

/* The veil is a centred COLUMN, not five stretched bands. With .veil-inner
   closed early every authored margin became an equal share of the height and
   half a phone screen was empty. */
/* A short viewport used to lose the wordmark.

   The veil is a grid whose single child is the copy column. At 821x605 -- the
   size Aarush reviews at -- the column was taller than the viewport, and with
   the compact rule anchoring it to the END, the overflow went off the TOP:
   "Nightglass" and "One Moon. More than one face." were simply not on screen.
   Anchoring is only safe while the content fits, so it is now conditional, and
   the column scrolls inside the veil rather than the page rather than silently
   losing its first two lines. */
#veil{align-content:center;overflow-y:auto;overscroll-behavior:contain}
.veil-inner{display:flex;flex-direction:column;align-items:center;
  max-width:min(560px,92vw);text-align:center}

/* On a phone adaptPose centres the disc, so a centred wordmark was stamped
   across the Moon's face -- complaint 2 in its purest form. Lift the copy
   column clear and let the Moon hold the upper half. */
@media (max-width:860px){
  /* The copy column starts BELOW the Moon, and the Moon says where that is.

     In this layout adaptPose lifts the disc into the upper half and the copy
     takes the lower one. That works while the viewport is tall. At 821x605 --
     the size Aarush reviews at -- the disc sits at y=179 with a radius of 118,
     so its lower limb is at 297, and the bottom-anchored column began at 288:
     the wordmark grazed the Moon's face by nine pixels. This is the defect the
     compact layout was written to prevent, reappearing at a height it was not
     tested against.

     --my and --mr are written every frame while the veil is up, so the padding
     can be derived from the disc itself rather than guessed as a vh fraction.
     `safe end` keeps the column on screen if that ever leaves too little room,
     rather than pushing its first line off the top. */
  #veil{
    align-content:safe end;
    padding-top:calc(var(--my,45%) + var(--mr,120px) + 18px);
    padding-bottom:calc(9vh + var(--safe-b))}
  .veil-title{font-size:clamp(32px,11vw,50px);letter-spacing:-.01em}
  /* the dock must be able to scroll rather than run off the bottom: the mode
     explanation is the only place a phone says what the tints mean */
  #console{overflow-y:auto;overscroll-behavior:contain}
  #drawer{max-height:min(22svh,150px)}
  /* "SPEED GUIDED" had a whole empty row to itself */
  .con-speed{flex:0 0 auto}
}

/* Advanced docks left, but the console is centred at min(720px,100vw-40px), so
   at 1440 it starts at x=360 and a 380px panel at x=8 ended at x=388. The
   previous round moved this bug from the right side to the left rather than
   fixing it. */
@media (min-width:861px){
  #advanced{width:min(340px, max(280px, calc(50vw - 384px)))}
}

/* the accent had four jobs; measurements take the data hue */
.cov-nums dd{color:var(--data)}

/* One dominant action per state. The caption's primary is the ACT's action; the
   dock's Next is navigation and is secondary. The caption's own Continue/Back
   are for the arrival card only. */
[data-state="act1"] #actContinue,[data-state="act2"] #actContinue,[data-state="act3"] #actContinue,
[data-state="act1"] #actBack,[data-state="act2"] #actBack,[data-state="act3"] #actBack{display:none}
#storyNext{background:rgba(255,255,255,.05);color:var(--ink);border:1px solid var(--rule)}
#storyNext:hover{background:rgba(255,255,255,.09)}
/* the rail's Skip duplicates the dock's "Skip the story" during an act */
[data-state="act1"] #btnStory,[data-state="act2"] #btnStory,[data-state="act3"] #btnStory{display:none}

/* ---- the popovers Advanced now uses, and the speed state line -------------
   Every popover in the build shares one implementation and one look. The two
   in Advanced were native <select>s until this revision: an opened system
   dropdown paints a white menu across a near-black exhibit and cannot be
   styled out of it. */
.pop{position:relative;display:inline-flex}
.pop > ul{position:absolute;left:0;top:calc(100% + 7px);z-index:50;margin:0;padding:5px;
  list-style:none;min-width:250px;max-width:min(320px,86vw);border-radius:11px;
  border:1px solid var(--rule);background:var(--panel-solid);
  backdrop-filter:var(--panel-blur);-webkit-backdrop-filter:var(--panel-blur);
  box-shadow:0 18px 44px -18px rgba(0,0,0,.92)}
.pop > ul[hidden]{display:none}
.pop > ul li{display:grid;gap:1px;padding:9px 11px;border-radius:7px;cursor:pointer;
  min-height:44px;align-content:center}
.pop > ul li b{font-family:var(--f-display);font-size:12.5px;font-weight:600;color:var(--ink)}
.pop > ul li em{font-style:normal;font-size:11px;color:var(--mute);line-height:1.35}
.pop > ul li:hover{background:rgba(255,255,255,.055)}
.pop > ul li[aria-selected="true"]{background:rgba(255,255,255,.08);
  box-shadow:inset 3px 0 0 var(--accent)}
.pop > ul li[aria-selected="true"] b{color:var(--accent)}
.pop > ul li:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
/* Advanced is narrow; its popovers open to the right edge if they would clip */
#covRangeMenu{max-height:min(46vh,320px);overflow-y:auto}

/* Its own line, under the control it describes. It used to be a sibling in a
   flex row, so at 821px it was laid alongside the speed button and the two
   overlapped. It is also short-lived: it says what a press just did. */
.speed-hint{margin:5px 0 0;font-size:11px;color:var(--mute);letter-spacing:.01em;
  min-height:1em;flex-basis:100%}
.speed-hint:empty{display:none}

/* `#advanced ul` (1,0,1) out-specifies `.pop > ul[hidden]` (0,2,1), so inside
   the Science Lab the popovers were laid out as grids and never actually
   closed -- an invisible-looking menu sat over the step buttons and swallowed
   their clicks. Found by the control-truth suite, because it clicks the real
   thing. Hidden means hidden, and it must out-specify the layout rule. */
#advanced .pop > ul[hidden],
.pop > ul[hidden]{display:none !important}
#advanced .pop > ul{display:block;gap:0}
#advanced .pop > ul li{display:grid;gap:1px}

/* The entry column is a column, and it sits on the optical centre rather than
   the geometric one -- the Moon occupies the right third, so the copy wants to
   be a little left and a little high of dead centre. */
@media (min-width:861px){
  /* -6vw, not -4vw. At POSES.entry the disc sits at shift -0.21 with a radius
     of 0.415 * short / 2, which puts its left limb at about 58% of the width;
     a 560px column offset by 4vw still grazed it. Measured, not guessed: the
     startup suite asserts the clearance rather than the offset. */
  /* -9vw, not -6vw. The R6 wordmark is set at up to 76px instead of 58, which
     widened the copy column by about 40px -- enough for its right edge to reach
     the Moon's left limb at 1440x900 under reduced motion, where the entry pose
     arrives as a cut rather than a move. Measured, not guessed: the startup
     gate hit-tests the disc and named the box. */
  #veil .veil-inner{transform:translate(-9vw,-3vh)}
}

/* ==========================================================================
   The Science Lab
   ==========================================================================
   The panel was a flat stack of eight blocks in the order they were written,
   which put "Observation data" -- twelve rows of raw numbers -- above the
   controls a person actually reaches for. It is five named groups now, in the
   order someone would want them, with the raw values collapsed by default.

   <details>/<summary> rather than a hand-built accordion: the open state, the
   keyboard behaviour and the screen-reader semantics are the browser's, and
   they are correct. */
.adv-group{border-bottom:1px solid var(--rule-soft)}
.adv-group:last-of-type{border-bottom:0}
.adv-group > summary{
  cursor:pointer;list-style:none;padding:14px 2px 12px;display:grid;gap:2px;
  min-height:44px;align-content:center;position:relative}
.adv-group > summary::-webkit-details-marker{display:none}
.adv-group > summary::after{
  content:"";position:absolute;right:4px;top:50%;width:7px;height:7px;
  border-right:1.5px solid var(--mute);border-bottom:1.5px solid var(--mute);
  transform:translateY(-70%) rotate(45deg);transition:transform 220ms var(--ease-ui)}
.adv-group[open] > summary::after{transform:translateY(-30%) rotate(-135deg)}
.adv-group > summary:hover .adv-group-name{color:var(--accent)}
.adv-group > summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}
.adv-group-name{font-family:var(--f-display);font-size:13.5px;font-weight:600;
  color:var(--ink);letter-spacing:-.004em}
.adv-group-blurb{font-size:11.5px;color:var(--mute);line-height:1.4;padding-right:18px}
.adv-group .adv-block{border:0;padding-top:0}
.adv-group .adv-block h3{font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--mute);margin-top:14px}
.adv-group .adv-block:first-of-type h3{margin-top:2px}

/* --- arriving from the House ---------------------------------------------
   The wordmark, the tagline and the acquisition bar are an introduction. A
   visitor who came through the terrace has had it: they read MIND OF AARUSH
   on the approach, walked to the Moon, and pressed Enter NIGHTGLASS under a
   composed frame. Showing them a title card now would be the site introducing
   itself to someone already inside it -- and it would put type across the face
   of the disc in the first second after a match cut, which is the one second
   that has to stay clean.

   The three doors remain, and they are all that remains. */
[data-entry="house"] .veil-title,
[data-entry="house"] .veil-line,
[data-entry="house"] #loadState{display:none}
[data-entry="house"] #veil{background:transparent;backdrop-filter:none}
[data-entry="house"] .veil-inner{animation:none}
/* The doors go under the Moon rather than across it. The camera is holding the
   handoff composition -- disc centred at 0.46 of the short side -- so the clear
   space is the bottom of the frame, and that is where the choice belongs. */
[data-entry="house"] #veil{align-content:safe end;padding-bottom:calc(7vh + var(--safe-b))}
[data-entry="house"] #entry{animation:entry-arrive 620ms var(--ease-ui) both}
@keyframes entry-arrive{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* --- the entry screen's own hierarchy -------------------------------------
   .entry-third was in the markup with no rule behind it
   at all, so the fine print inherited body copy -- 16px of --ink at 17:1, the
   second-loudest thing on the first screen a visitor ever sees, louder than
   both of the choices it was describing -- and the third door rendered flush
   against the second with no margin, reading as its caption rather than as a
   way in. Loudness here has to follow importance: primary choice, alternative,
   third way out, then the note. */
.entry-third{margin-top:16px;font-size:11px}

/* --- the location status line in the dock --------------------------------- */
/* .con-note now lives inside <details class="con-why">; it is never a
   permanent block in the console. Its layout is defined with .con-why above. */

/* --- the loading state: the Moon is the event, so the copy gets out of it ---
   The offset above is correct for the ENTRY state, where the camera has moved
   to POSES.entry (shift -0.21) and the disc genuinely does occupy the right
   third. It is wrong for the 3.4 seconds before that. During loading the
   camera is approaching POSES.forming -- shift 0, lift 0, disc dead centre at
   0.46 of the short side -- so a copy column offset by 4vw is still inside the
   disc, and the wordmark, the tagline and the acquisition bar sat across the
   Moon's face for the entire formation. The one moment Aarush singled out as
   the thing he likes played as the background of a title card.

   So during loading the copy goes under the disc, where there is real room:
   at fill 0.46 the limb clears the lower ~27% of the frame, and the loading
   column (wordmark, one line, a 2px bar and a label) is much shorter than that.
   The phone already did this -- it is the better composition of the two, and
   this is only extending it to the viewport that needed it more. */
/* the loading column is short -- a bar and a label -- so it only needs to clear
   the disc, not to be pushed to the floor */
#veil[data-veil="loading"]{align-content:safe end;padding-bottom:5.5vh}
#veil[data-veil="loading"] .veil-inner{transform:none}

/* And the wordmark waits. Moving the copy column to the bottom is not enough on
   its own: wordmark + tagline + bar is 399px tall at 1440x900, and the clear
   space under a disc at 0.46 of the short side is 268px. Something had to give,
   and it is not going to be the Moon.

   So the loading state shows only what the loading state is for -- a thin bar
   and the name of what is being acquired -- and NIGHTGLASS introduces itself
   once the Moon is whole and the camera has stepped off-axis, where there is
   room for it beside the disc rather than across it. The mark-resolve animation
   then plays at the moment it means something instead of over the formation.
   "The Moon is the event; everything else defers to it" is the instruction; a
   title card that waits its turn is what deferring looks like. */
#veil[data-veil="loading"] .veil-title,
#veil[data-veil="loading"] .veil-line{display:none}

/* --- the relief label, because the article promises one ---------------------
   #read says of the exaggerated terrain: "I kept the exaggeration as a teaching
   aid, and it is labelled wherever it is on." Nothing read [data-relief], so
   that sentence pointed at a label that did not exist -- in a project whose
   whole argument is that its claims are measured. Either the sentence goes or
   the label arrives; the label is the better answer, because the claim is the
   right one to make. */
#reliefBadge{position:fixed;z-index:24;left:max(18px,var(--safe-l));bottom:calc(18px + var(--safe-b));
  font-family:var(--f-display);font-weight:600;font-size:10px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--warn);background:rgba(4,4,5,.72);border:1px solid rgba(255,255,255,.14);
  border-radius:999px;padding:5px 10px;pointer-events:none;opacity:0;
  transition:opacity 320ms var(--ease-ui)}
[data-relief="exaggerated"] #reliefBadge{opacity:1}
@media (max-width:860px){#reliefBadge{font-size:9px;padding:4px 8px}}

/* --- H7: the caption's backing must be as wide as the caption ---------------
   #capScrim is a 64vw radial anchored bottom-left, centred at 20% of its own
   width. That is right on a desktop, where #caption is a column in the lower
   left. On a phone the compact block makes the caption full-bleed
   (left:12px;right:12px), and the scrim was never re-scoped -- so at 390px the
   scrim covered 250px of a 366px caption and the last third of every line was
   set on bare render. Over a lit limb --dim measures 1.30:1, which is not text.
   A phone gets a full-width bottom ramp instead of a corner glow, because the
   caption it has to back is a full-width block. */
@media (max-width:860px){
  #capScrim{width:100%;height:min(70svh,560px);
    background:linear-gradient(to top,rgba(4,4,5,.94) 0%,rgba(4,4,5,.86) 28%,
      rgba(4,4,5,.62) 55%,rgba(4,4,5,.24) 78%,rgba(4,4,5,0) 100%)}
}

/* --- there is no aureole, and that is the correction ------------------------
   Aarush rejected the startup because it showed "a strange black circle with a
   white glow in front of the real forming Moon ... like two overlapping Moons
   or an eclipse."

   He was looking at this element. #aureole was a DOM circle sized in MOON RADII
   and painted with a radial-gradient whose stops are fractions of closest-side
   -- that is, of HALF the element. Sized at 2.9 * mr, half is 1.45 * mr, so a
   stop written at 35% intending "the limb" landed at 0.51 * mr: a bright
   annulus drawn straight across the middle of the Moon's face, with a
   transparent hole inside it that read as a second, smaller, hard-edged black
   disc in front of the real one.

   It is not tuned, softened, resized or moved. It is GONE, along with the
   element, so that no arrangement of stops can ever put a second silhouette in
   front of the Moon again. The particle formation and the real WebGL Moon are
   the only things on screen during startup, which is what he asked for and is
   also cheaper: one fewer composited layer over a full-screen canvas.

   If glow is ever wanted back, it belongs in the WebGL scene behind the real
   Moon, derived from the real rendered limb -- never as a 2D disc over it. */


/* --- arriving from the House: the doors go in a row -------------------------
   The camera is holding the handoff composition, so the disc is centred at 0.46
   of the short side and the only clear ground is the strip beneath it -- 216 px
   at 1280x800. The default entry block is a column: two stacked buttons, a third
   below them and a note, about 200 px before padding, which put the choice
   across the Moon's lower limb in the first second after the cut.

   A row is the right shape for this state anyway. There is no wordmark above it
   competing for the eye, the three doors are peers, and laying them out along
   the bottom reads as a footer to the arrival rather than as a card dropped on
   top of it. The phone keeps the column, where there is already room. */
@media (min-width:861px){
  [data-entry="house"] .veil-inner{max-width:min(940px,94vw);transform:none}
  [data-entry="house"] .entry-actions{flex-direction:row;justify-content:center;gap:12px}
  [data-entry="house"] .entry-actions .btn{min-width:210px}
  [data-entry="house"] #entry{display:flex;flex-direction:row;align-items:center;
    justify-content:center;flex-wrap:wrap;gap:12px}
  [data-entry="house"] .entry-third{margin-top:0}
}


/* --- the small-laptop window, placed after the phone rules on purpose ------
   760-860px is a real and awkward size -- a small laptop window, and the exact
   821x605 Aarush reviews at. The phone rules start at 860px and move the
   actions into their own full-width row, which is right on a 390px phone with
   844px of height and wrong here: it spends vertical space this viewport does
   not have. Between 760 and 860 the head keeps its own actions and the phone
   row stands down, so the console is two rows shorter. */
/* `and (min-height:501px)`, matching the phone block's own guard.

   Without it this block also matched 844x390 -- a landscape phone -- and, being
   last in the file, beat the landscape rules. Two things broke together there:
   the Lab went back to docking LEFT, straight over a console that is itself a
   left column; and the three-column console head was forced into a 400px
   panel, so its columns overflowed and pushed the transport to x = -89, off the
   screen entirely. This block is about a small LAPTOP WINDOW. A viewport 390px
   tall is not that, and it has its own rules further up. */
@media (min-width:760px) and (max-width:860px) and (min-height:501px){
  #console .con-actions{display:flex}
  .con-extra-row{display:none}
  .con-head{
    grid-template-columns:auto minmax(9rem,1fr) auto;
    grid-template-areas:
      "play ident  actions"
      "play local  status"}
  /* The phone rules turn .con-speed into a full-width flex row so the button
     can span the screen. At this size that squeezed "Speed" and "Guided" on top
     of each other and laid the hint alongside. It is a block again, at its own
     width, with the hint on the line below where it belongs. */
  .con-speed{display:block;flex:0 0 auto;min-width:0}
  #btnSpeed{width:auto}
  #console .con-modes #btnResetView{display:inline-flex}

  /* THE LAB DOCKS HERE; IT DOES NOT COVER THE CONSOLE.

     The phone rules turn Advanced into a full-width sheet from the bottom,
     capped at 72svh "so the Moon is never wholly replaced by a settings panel".
     That cap protects the Moon and says nothing about the console. At 821x605 --
     605px tall -- 72svh is 436px of sheet over a console that starts at 266px,
     so opening the Lab hid the transport, the phase, the city, the date and the
     whole timeline. Measured: the Play button was underneath an <h3>.

     At this width there is room for the docked panel the desktop uses: 380px of
     Lab still leaves 441px of Moon, and the console keeps its own space
     underneath. A panel you open to study something must not hide the thing it
     is studying, or the readout describing it. */
  #advanced{
    left:8px;right:auto;width:min(380px,calc(100vw - 16px));
    border-left:1px solid var(--rule);border-radius:12px;
    top:calc(var(--rail-h) + var(--safe-t) + 8px);
    bottom:calc(var(--console-h,150px) + clamp(14px,2.6vh,24px) + var(--safe-b) + 8px);
    max-height:none;transform:translateX(calc(-100% - 16px))}
  #advanced[data-on="1"]{transform:none}
}

/* --- opening the Lab on a short screen ---------------------------------------
   At 821x605 -- the size Aarush reviews at -- the rail takes 48px and the
   console 339, which leaves 218 for the Science Lab. A 218px panel is not an
   instrument, it is a sliver; measured, it showed one group heading and
   nothing else.

   There is no arrangement that gives both panels everything on a screen this
   short, so the console gives up the parts you did not open the Lab to use:
   the timeline, the mode switch and the phone action row stand down, and the
   HEAD stays -- transport, phase, illumination, city, date and the status
   badge. Everything needed to know what you are looking at and to start or stop
   time is still there, and CLOSE brings the rest straight back.

   This is a deliberate focus state, not a layout failure: it is keyed on the
   Lab being open, it is fully reversible, and the console never becomes
   unusable, which is the requirement. */
@media (max-height:760px){
  [data-adv="1"] #console .con-time,
  [data-adv="1"] #console .con-modes{display:none}
}
/* The phone's action row STAYS. It carries "Science Lab" on a phone, so hiding
   it while the Lab was open removed the control that had just been used and
   left the panel's own CLOSE as the only way back. A focus state may put things
   aside; it may not take away the way out. */

/* --- the smallest phone still in the matrix ---------------------------------
   375x667. Measured, the console needed 383px of a 374px budget and the action
   row's bottom edge sat nine pixels past the fold. Every row is load-bearing --
   the transport and readout, the timeline, the three modes, the three actions --
   so nothing is dropped; the nine pixels come out of the gaps between them and
   the timeline's own bottom padding, which were tuned on taller phones. */
@media (max-width:400px) and (max-height:700px){
  #console{gap:6px;padding:9px 12px 10px}
  #console .con-time{padding-bottom:15px}
  .con-modes{gap:5px}
}

/* ==========================================================================
   R6 — the startup, composed with the Moon rather than laid over it
   ==========================================================================
   R5.2 fixed the right things: the fake aureole and the paragraph are gone.
   What it left was plain — a centred wordmark and two buttons on black, with
   no relationship to the one thing on screen worth looking at.

   The direction here is a single idea, not three: THE MOON LIGHTS THE WORDS,
   AND THE WORDS ARRIVE AFTER IT. Nothing is drawn over the disc, nothing is
   attached to it, and no new object exists that could ever be mistaken for a
   second Moon. The only new relationship is illumination -- which is the
   subject of the whole exhibit.

   `--mx` / `--my` / `--mr` are the disc's real projected centre and radius,
   written every frame while the veil is up. They already exist; this reads
   them. */

/* 1 -- TYPE THAT CATCHES THE MOON'S LIGHT.

   `background-attachment: fixed` puts the gradient in VIEWPORT coordinates, so
   `circle at var(--mx) var(--my)` is genuinely the Moon's position on screen,
   not a position inside the text box. background-clip:text then shows it only
   through the glyphs. As the disc forms and the camera settles, the highlight
   travels across the letters, brightest on the side facing the Moon.

   It degrades honestly: without the custom properties the gradient falls back
   to 50%/45% and the wordmark is simply a light grey, which is what R5.2 had. */
@supports (background-clip: text) or (-webkit-background-clip: text) {
  [data-veil] .veil-title{
    color:transparent;
    background-image:radial-gradient(
      circle at var(--mx, 50%) var(--my, 45%),
      #ffffff 0,
      #f6f9fb calc(var(--mr, 180px) * 1.15),
      #cdd6de calc(var(--mr, 180px) * 2.6),
      #8d98a3 calc(var(--mr, 180px) * 4.6),
      #6b757f 100%);
    background-attachment:fixed;
    -webkit-background-clip:text;
    background-clip:text;
  }
}

/* 2 -- BEATS, NOT A SIMULTANEOUS CARD.

   The controls used to appear all at once at a fixed 3400ms. They now arrive
   after the Moon is substantially formed, in three separate moments, so the
   screen is composed in time as well as in space: the Moon, then its name,
   then what it says, then the way in. Each beat is short; the whole ladder is
   under a second and a half once the formation has finished. */
#veil[data-beat="mark"] .veil-line,
#veil[data-beat="mark"] #entry,
#veil[data-beat="line"] #entry{opacity:0;pointer-events:none}
#veil[data-beat="line"] .veil-line{opacity:1}

/* 3 -- A CINEMATIC FRAME, NOT A GRAPHIC.

   Slightly deeper at the top and bottom than at the sides, which is what a
   wide lens does and what a HUD never does. It is a full-screen wash: it has
   no edge, no radius and no centre of its own that could read as an object.
   The startup gate distinguishes exactly this from a shape by extent. */
#veil[data-veil="loading"],
#veil[data-veil="entry"]{
  background:
    linear-gradient(to bottom, rgba(2,2,3,.55) 0%, rgba(2,2,3,0) 26%,
                               rgba(2,2,3,0) 74%, rgba(2,2,3,.62) 100%),
    radial-gradient(128% 104% at 50% 44%, rgba(4,4,5,.20) 0%,
                    rgba(4,4,5,.80) 64%, rgba(4,4,5,.96) 100%);
}

/* the light on the type is a still image when motion is reduced */
@media (prefers-reduced-motion:reduce){
  [data-veil] .veil-title{background-attachment:scroll}
}
