/* Pixel Observatory.
   Square corners, hard offset shadows, one accent colour, pixel font for
   labels and mono for everything you actually read. Ported from the sibling
   site's retro spec so the two read as a family. */

:root {
  --bg: #0a0d13;
  --panel: #131820;
  --panel-2: #0f141b;
  --line: #232c38;
  --ink: #cfd8e3;
  --dim: #87919e;
  --accent: #e0a83e;
  --accent-ink: #0a0d13;
  --shadow: #000508;
  --px: 'Silkscreen', ui-monospace, monospace;
  --mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --hud-gap: 14px;
}

@font-face {
  font-family: 'Silkscreen';
  src: url('fonts/silkscreen-400.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Silkscreen';
  src: url('fonts/silkscreen-700.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('fonts/plexmono-400.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('fonts/plexmono-600.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}

* { box-sizing: border-box; }

/* Several components below set display on elements that also carry [hidden].
   Without this the attribute quietly loses and dialogs open on page load. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.6;
  overflow: hidden;
}

a { color: var(--accent); text-decoration-color: rgba(224, 168, 62, 0.45); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---- stage ---- */

#app { position: fixed; inset: 0; }

#app-stage {
  position: absolute;
  inset: 0;
}
/* Opening a part shrinks the stage. The ResizeObserver on #app-stage picks this
   up and reframes the camera, so the piece you clicked stays in view instead of
   sitting behind the panel describing it. */
body.panel-open #app-stage { right: min(400px, 92vw); }
/* The controls move over with it, or the right-hand buttons end up clipped
   behind the panel. */
body.panel-open .hud-top { right: min(400px, 92vw); }
/* The view buttons and the slider go away entirely while a part is open. They
   steer a model that is now sharing the screen, and the panel is the thing
   being read. */
body.panel-open .hud-bottom { display: none; }

#stage {
  display: block;
  width: 100%;
  height: 100%;
  /* The whole style lives on this line: never interpolate the upscale. */
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  cursor: grab;
}
#stage:active { cursor: grabbing; }

.loading {
  position: absolute;
  inset: auto 0 50% 0;
  text-align: center;
  font-family: var(--px);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
}

.hover-label {
  position: fixed;
  transform: translate(14px, -34px);
  z-index: 5;
  pointer-events: none;
  font-family: var(--px);
  font-size: 11px;
  letter-spacing: 0.08em;
  padding: 5px 9px;
  color: var(--accent);
  background: var(--panel-2);
  border: 1px solid var(--line);
  box-shadow: 3px 3px 0 var(--shadow);
}

/* ---- hud ---- */

.hud {
  position: absolute;
  left: 0; right: 0;
  padding: 18px 20px;
  pointer-events: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--hud-gap);
}
/* Only real controls capture the pointer. Letting the containers capture it
   made the whole top strip of the stage undraggable, because a block-level h1
   in a stretched flex column is as wide as the viewport. */
.hud button,
.hud input,
.hud a,
.hud label { pointer-events: auto; }

.hud-top { top: 0; flex-direction: column; gap: 6px; }
.hud-bottom { bottom: 0; align-items: flex-end; }

.site-title {
  font-family: var(--px);
  font-weight: 700;
  font-size: clamp(18px, 3.4vw, 27px);
  letter-spacing: 0.02em;
  margin: 0;
  color: #e8eef5;
  text-shadow: 2px 2px 0 var(--shadow);
}

.site-tagline {
  margin: 0;
  max-width: 46ch;
  font-size: 13px;
  color: var(--dim);
}

.countdown {
  margin: 6px 0 0;
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
}
.countdown-label {
  font-family: var(--px);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
}
.countdown-value {
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

.reveal {
  position: relative;
  font-family: var(--px);
  width: 22px; height: 22px;
  line-height: 1;
  padding: 0;
  color: var(--accent);
  background: var(--panel-2);
  border: 1px solid var(--line);
  box-shadow: 2px 2px 0 var(--shadow);
  cursor: pointer;
}
/* A 22px square is the right size to look at and the wrong size to hit. Grow
   the target to the 44px minimum without growing the box. */
.reveal::after {
  content: '';
  position: absolute;
  inset: -11px;
}

.hud-legend {
  display: block;
  font-family: var(--px);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 6px;
}

.views { display: block; }
.views-row { display: flex; gap: 6px; flex-wrap: wrap; }

.view-button,
.action {
  font-family: var(--px);
  font-size: 11px;
  letter-spacing: 0.06em;
  min-height: 44px;
  padding: 0 14px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: 3px 3px 0 var(--shadow);
  cursor: pointer;
}
.view-button[aria-pressed='true'] { color: var(--accent); border-color: var(--accent); }

/* Reset sits with the views but is not one of them, so it gets a gap and a
   quieter treatment. Disabled until there is actually something to put back. */
.view-reset { margin-left: 12px; color: var(--dim); }
.view-reset:not(:disabled):hover { color: var(--accent); border-color: var(--accent); }
.view-reset:disabled { opacity: .38; cursor: default; }

/* Two-line action: the name on top, what it actually does underneath. */
.action-stacked {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 3px;
  padding-top: 7px;
  padding-bottom: 7px;
  text-align: left;
}
.action-sub {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0;
  text-transform: none;
  opacity: .8;
}
.action-primary { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.action:disabled { opacity: 0.4; cursor: default; }
/* Fading the gold fill left a muddy olive slab that still looked pressable,
   right above the real call to action. */
.action-primary:disabled {
  opacity: 1;
  color: var(--dim);
  background: var(--panel);
  border-color: var(--line);
  box-shadow: none;
}

.explode { min-width: 210px; }

/* Square channel with tick notches and a hard-shadowed block for a thumb.
   The stock control is round-ended and glossy, which is the one shape this
   whole design language does not have. */
.explode-range, .fallback-scrub {
  -webkit-appearance: none;
  appearance: none;
  /* Block, and no UA margin. As an inline element the input sits on a text
     baseline, which adds a descender gap underneath and pushed this whole
     group out of line with the view buttons beside it. */
  display: block;
  margin: 0;
  width: 100%;
  height: 44px;
  background: transparent;
  cursor: pointer;
}

.explode-range::-webkit-slider-runnable-track,
.fallback-scrub::-webkit-slider-runnable-track {
  height: 12px;
  border: 1px solid var(--line);
  box-shadow: 2px 2px 0 var(--shadow);
  background:
    linear-gradient(var(--accent), var(--accent)) left center / var(--fill, 0%) 100% no-repeat,
    repeating-linear-gradient(90deg, var(--panel-2) 0 7px, var(--line) 7px 8px);
}
.explode-range::-moz-range-track,
.fallback-scrub::-moz-range-track {
  height: 12px;
  border: 1px solid var(--line);
  box-shadow: 2px 2px 0 var(--shadow);
  background: repeating-linear-gradient(90deg, var(--panel-2) 0 7px, var(--line) 7px 8px);
}
.explode-range::-moz-range-progress,
.fallback-scrub::-moz-range-progress {
  height: 12px;
  background: var(--accent);
}

.explode-range::-webkit-slider-thumb,
.fallback-scrub::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 13px;
  height: 26px;
  margin-top: -8px;
  border: 1px solid var(--bg);
  border-radius: 0;
  background: var(--accent);
  box-shadow: 2px 2px 0 var(--shadow);
}
.explode-range::-moz-range-thumb,
.fallback-scrub::-moz-range-thumb {
  width: 13px;
  height: 26px;
  border: 1px solid var(--bg);
  border-radius: 0;
  background: var(--accent);
  box-shadow: 2px 2px 0 var(--shadow);
}
.explode-range:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--ink); outline-offset: 1px; }

.actions { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }

/* ---- component panel ---- */

.panel {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: min(400px, 92vw);
  overflow-y: auto;
  padding: 22px 24px 34px;
  background: var(--panel);
  border-left: 1px solid var(--line);
  box-shadow: -6px 0 0 var(--shadow);
  z-index: 6;
}

.panel-close, .overlay-close {
  flex: 0 0 auto;
  font-family: var(--px);
  font-size: 11px;
  width: 44px; height: 44px;
  color: var(--ink);
  background: var(--panel-2);
  border: 1px solid var(--line);
  box-shadow: 3px 3px 0 var(--shadow);
  cursor: pointer;
}
.panel-close { float: right; }

.component-name {
  font-family: var(--px);
  font-size: 15px;
  letter-spacing: 0.04em;
  margin: 8px 0 4px;
  color: #e8eef5;
}
.component-short { margin: 0 0 14px; color: var(--accent); font-size: 13px; }
.component-blurb { margin: 0 0 16px; }

.fact-list { margin: 0 0 16px; padding-left: 18px; }
.fact-list li { margin-bottom: 10px; font-size: 13.5px; }
.fact-list li::marker { color: var(--accent); }

.source-link {
  display: inline-block;
  margin-left: 5px;
  width: 15px; height: 15px;
  line-height: 15px;
  text-align: center;
  font-family: var(--px);
  font-size: 9px;
  text-decoration: none;
  color: var(--accent-ink);
  background: var(--accent);
  vertical-align: 2px;
}

.official-link { display: inline-block; font-size: 13px; }
.shared-note {
  border-left: 2px solid var(--accent);
  padding-left: 12px;
  margin: 0 0 16px;
  font-size: 13px;
  color: var(--dim);
}

/* ---- overlays ---- */

.overlay {
  position: absolute;
  inset: 0;
  z-index: 8;
  background: rgba(6, 9, 13, 0.86);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3vh 20px;
  overflow-y: auto;
}
.overlay-inner {
  width: min(660px, 100%);
  max-height: 100%;
  overflow-y: auto;
  padding: 24px 26px 30px;
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: 8px 8px 0 var(--shadow);
}

/* The light path is a fixed-height column: the picture and the words scroll,
   the buttons never move. Reserving space for each slot instead meant the copy
   wrapped differently on every step and walked the Next button 73px down the
   screen on a phone, and left a dead band above the heading on the steps with
   no schematic label. */
.lightpath-inner {
  display: flex;
  flex-direction: column;
  max-height: 94vh;
  overflow: hidden;
  height: auto;
  padding-bottom: 22px;
}
.lightpath-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  flex: 0 0 auto;
}
.lightpath-head .overlay-kicker { margin-bottom: 14px; }
.lightpath-scroll {
  /* A fixed height, not a flexible one. Left flexible, the box shrank to fit
     each step's copy and carried the buttons up and down with it. */
  height: min(62vh, 520px);
  overflow-y: auto;
  flex: 0 0 auto;
  /* Softens the last line when there is more below, so a clipped sentence
     reads as "keep going" rather than as a bug. */
  -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - 26px), transparent);
  mask-image: linear-gradient(180deg, #000 calc(100% - 26px), transparent);
}
.lightpath-foot {
  flex: 0 0 auto;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  margin-top: 14px;
}
.overlay-kicker {
  font-family: var(--px);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 12px;
}
.overlay-title {
  font-family: var(--px);
  font-size: 20px;
  line-height: 1.25;
  margin: 0 0 12px;
  color: #e8eef5;
  text-wrap: balance;
}
.overlay-subtitle {
  font-family: var(--px);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim);
  margin: 22px 0 10px;
}
.overlay-body { margin: 0 0 14px; }
.overlay-note { font-size: 12.5px; color: var(--dim); margin: 16px 0 0; }

.lightpath-stage {
  margin: 0 0 18px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  /* Fixed height so Next does not shift under the pointer between steps. */
  display: flex;
  align-items: center;
}
/* Same idea for the copy: the tallest step sets the height for all of them. */

.lightpath-dots { display: flex; gap: 7px; margin: 0 0 14px; }
.dot { width: 10px; height: 10px; background: var(--line); }
.dot-on { background: var(--accent); }
.diagram { display: block; width: 100%; height: auto; }
.diagram-label { font-family: var(--px); font-size: 11px; fill: var(--dim); text-anchor: middle; }
.diagram-small { font-family: var(--mono); font-size: 10px; fill: var(--dim); text-anchor: middle; }
.diagram-band { font-family: var(--px); font-size: 16px; fill: var(--ink); text-anchor: middle; }
.diagram-band-off { fill: var(--line); }
.readout { stroke: var(--dim); stroke-width: 1.5; stroke-dasharray: 4 3; }
.beam-star { stroke: #e8eef5; }
.beam-planet { stroke: var(--accent); }
.aperture, .bay, .mask, .filter, .detector, .swatch { fill: none; stroke: var(--line); stroke-width: 1.5; }
.bay { stroke-dasharray: 4 3; }
.mask { fill: #14181f; stroke: var(--ink); }
.filter { fill: #0f141b; stroke: var(--accent); }
.filter-off { stroke: var(--line); }
.detector { fill: #0f141b; stroke: var(--ink); }
.swatch { stroke: var(--ink); }
/* No fill declared: the stop colour arrives as a fill attribute, and any CSS
   fill here would beat it. */
.swatch-filled { stroke: var(--ink); stroke-width: 1.5; }
.swatch-pending { fill: none; stroke: var(--line); stroke-dasharray: 5 4; }

/* Present because the picture below it is invented, quiet because it is the
   same sentence on every diagram step and nobody needs it shouted four times. */
.schematic-warning {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--dim);
  border-left: 2px solid var(--line);
  padding: 1px 0 1px 10px;
  margin: 0 0 14px;
}

.step-sources { margin: 14px 0 0; }
.step-note {
  margin: 8px 0 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--dim);
}
.step-source {
  font-size: 11.5px;
  color: var(--dim);
  text-decoration-color: var(--line);
}
.step-source:hover { color: var(--accent); }

.lightpath-nav { display: flex; align-items: center; gap: 12px; margin-top: 4px; }

#lightpath-finale { margin-top: 14px; }
.finale-link {
  /* Flex rather than a 46px line-height: the label wraps at narrow widths, and
     a line-height that tall turned two lines into a 92px slab with a hole in
     the middle. */
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 46px;
  line-height: 1.35;
  padding: 10px 14px;
  text-align: center;
  text-decoration: none;
}
.lightpath-counter {
  font-family: var(--px);
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--dim);
  margin: 0 auto;
}

.source-list { margin: 0; padding-left: 18px; font-size: 12.5px; }
.source-list li { margin-bottom: 7px; }

/* ---- fallback and no-js ---- */

.fallback { position: absolute; inset: 0; display: grid; place-content: center; gap: 12px; padding: 20px; }
.fallback-frame { display: block; width: min(760px, 92vw); height: auto; image-rendering: pixelated; }
.fallback-notice { max-width: 60ch; color: var(--dim); font-size: 13px; margin: 0; }
.fallback-scrub-label { font-family: var(--px); font-size: 10px; letter-spacing: 0.12em; color: var(--dim); }

/* The snap views and the exploded view steer a renderer that is not running. */
body.is-fallback .views,
body.is-fallback .explode { display: none; }

/* Hidden by default, because JavaScript is the common case and the page must
   not flash the reading copy before a deferred module gets to run. */
#fallback-content { display: none; }
.noscript-note {
  position: relative;
  z-index: 20;
  margin: 0;
  padding: 12px 20px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 13px;
}

/* A browser with scripting on never parses noscript content into the DOM, so
   this marker exists only when scripting is off. That replaces the old
   html.no-js class, which had to be stripped by JavaScript and therefore
   painted the whole parts library first on every load. No inline script, which
   matters: the deploy's policy is script-src 'self'. */
html:has(#no-js) #app { display: none; }
html:has(#no-js) body { overflow: auto; }

/* And if boot throws, fall back to the words rather than a black rectangle. */
body.is-broken #app { display: none; }
body.is-broken { overflow: auto; }

html:has(#no-js) #fallback-content,
body.is-broken #fallback-content {
  display: block;
  max-width: 720px;
  margin: 0 auto;
  padding: 40px 20px 80px;
}

.reading-title {
  font-family: var(--px);
  font-size: 15px;
  letter-spacing: 0.05em;
  margin: 34px 0 12px;
  color: #e8eef5;
}
.reading-subtitle {
  font-family: var(--px);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim);
  margin: 26px 0 10px;
}

/* ---- narrow screens ---- */

@media (max-width: 720px) {
  .hud { padding: 12px 14px; }
  .site-tagline { display: none; }
  .hud-bottom { flex-direction: column; align-items: stretch; }
  .actions { margin-left: 0; flex-wrap: nowrap; }

  /* Keep the observatory clear of the controls rather than centred behind
     them. --hud-bottom is measured in main.js and drops to zero when the
     controls are hidden. */
  #app-stage { bottom: var(--hud-bottom, 0px); }

  /* All six view buttons on one line: tighter padding and no wrapping. */
  .views-row { flex-wrap: nowrap; gap: 4px; }
  .view-button { padding: 0 8px; font-size: 10px; flex: 0 1 auto; min-width: 0; }
  .view-reset { margin-left: 8px; }

  /* And the two actions on one line, with the long label free to shrink. */
  .action-stacked { flex: 1 1 auto; min-width: 0; }
  .action-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .actions .action { flex-shrink: 0; }
  .panel {
    top: auto;
    width: 100%;
    height: 62vh;
    max-height: 62vh;
    border-left: 0;
    border-top: 1px solid var(--line);
    box-shadow: 0 -6px 0 var(--shadow);
  }
  /* Give the sheet the bottom and keep the observatory in the top third,
     with the controls sitting just above the sheet rather than under it. */
  body.panel-open #app-stage { right: 0; bottom: 62vh; }
  body.panel-open .hud-top { right: 0; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
