/* =================================================================
   SNITCH — base.css
   Reset, .wrap, section rhythm, the three type registers (§2.3),
   ::selection, .js gating doctrine, .sr-only, scroll-margin.
   STEP 5.0 / 5.0a.

   THE NO-JS DOCTRINE (§7.5.2, §6.6):
   No reveal target is `opacity:0` in any stylesheet. Every hidden
   state either comes from gsap.set() at bind time, or is gated
   behind `.js` / `html.booting`, which the inline script at the top
   of <body> adds. If you write `opacity:0` in a stylesheet without a
   `.js` ancestor in the selector, you have broken the build.

   The MASTER reduced-motion keyframe kill block lives in glitch.css
   (§3.1, §6.6) — it is the single place every @keyframes in the build
   is switched off. The block at the bottom of this file covers the
   document-level surfaces only (scroll behaviour, the ground step).
   ================================================================= */

/* ---- RESET ------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;   /* §3.2: kept. No Lenis, so nothing double-animates,
                                and it is the no-JS anchor behaviour. */
}

body {
  margin: 0;
  background: var(--ground);              /* §5.0 — stepped per zone by chapter.js */
  transition: background-color 150ms linear;  /* a STEP, not a tween through a muddy midpoint */
  color: var(--ink-200);
  font-family: var(--f-voice);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-voice);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, h4, p, ul, ol, figure, blockquote { margin: 0; }
ul, ol { padding: 0; }
img, svg, video { max-width: 100%; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }

a { color: var(--brand); text-decoration: none; }
a:hover { color: var(--hot); }

/* §7.5.9 — focus indicators survive the border-radius:0 rewrite.
   `outline:none` never appears anywhere without a replacement. */
:focus-visible { outline: 2px solid var(--hot); outline-offset: 2px; }

::selection { background: var(--brand); color: var(--g-hero); }

/* ---- THE POINTER (§2.3a) ----------------------------------------
   The system's own (operator, 2026-10-13: "We don't want a custom
   cursor"). No cursor image anywhere: the browser's arrow over
   prose, the hand over links and controls, the I-beam in a field,
   as on any other site. img/cursor.{svg,png}, the retired pixel
   arrow, are no longer referenced. */

/* ---- THE WRAP --------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--wrap-max);
  margin-inline: auto;
  padding-inline: var(--wrap-pad);
}

/* ---- SECTION RHYTHM --------------------------------------------- */
/* #stage is the target of Glitch.punch()'s scale(1.006).
   It must exist and must have NO background of its own (§5.0). */
#stage { position: relative; background: none; }

main > section { position: relative; padding: var(--sp-9) 0; }
/* Per-chapter overrides (hero runway, snitch/changelog centring, the
   closing's 160/100) live in sections.css. */

/* ---- THE THREE REGISTERS (§2.3) --------------------------------- */
/* A. THE APPARATUS SPEAKS — display face, 400, uppercase, positive
      tracking ALWAYS. text-transform is typesetting: the DOM string
      stays verbatim, so SR, copy-paste and the PHP content hash all
      see original sentence case. */
h2,
.row .label,
.navpill a,
.build-num,
.id-card dt,
.id-card-org,
.id-card-name,
.link,
.chapter-label > span {
  font-family: var(--f-display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: var(--tr-display);
}

/* Section headings share ONE size and ONE gap to their content, so
   #details / #specs_list / #closing read as peers. --t-h2 (38–78px) was
   defined for exactly this but applied nowhere — every section h2 was
   falling through to the UA default (~24px). #snitch h2 (--t-h2-xl) and
   .signal-heading set their own size and out-specify this. */
h2 {
  font-size: var(--t-h2);
  line-height: var(--lh-head);
  margin-bottom: var(--sp-6);
}

/* B. THE MACHINE SPEAKS TO YOU — voice face, sentence case.
      Weight rule: 400 minimum for anything under 40px. Weight 300 is
      permitted ONLY on h1#fill, .id-card-name and #closing h2 — all
      >=28px. The live theme's font-weight:300 on p.roast is a
      light-theme habit and does not survive. */
p, li, h1, h3, .lede, .roast, .note, .cl-text, .snitch-intro {
  font-family: var(--f-voice);
  font-weight: 400;
  letter-spacing: var(--tr-voice);
  text-transform: none;
}

/* ---- WIDOWS AND ORPHANS (reported 2026-09-21) --------------------
   A last line carrying one or two words is the commonest typographic
   fault on this page, and it is worst exactly where the copy is best:
   the server writes these paragraphs per visitor, so their length is
   never the same twice and no amount of hand-tuning the wording can
   settle a break that moves with the reader's own machine.

   text-wrap:pretty is the right instrument and balance is not. `pretty`
   only reconsiders the LAST few lines to avoid stranding a word, which
   is precisely the complaint; `balance` re-flows the whole block to even
   the lines out, which is a heading treatment and looks wrong on a
   paragraph of any length. It degrades to nothing on an engine that does
   not support it, so there is no fallback to write.

   THE HEADINGS ARE DELIBERATELY NOT IN HERE. sections.css has per-heading
   decisions about wrapping — .psych-head carries an explicit "NO
   text-wrap:balance (human's call), the orphan was solved in the COPY
   instead" — and a blanket rule here would silently overrule every one of
   them. Headings are handled one at a time, where the note lives.

   NOT h1 EITHER: #fill is split into per-word spans by hero.js and
   scaled by its own --fill-scale governor, and it is one line by
   construction, so there is no last line to strand. */
p, li, .lede, .roast, .note, .cl-text, .snitch-intro {
  text-wrap: pretty;
}

/* C. RAW READING — machine face, 10–13px, --tr-micro, --ink-400. */
.nav-readout,
.foot-stamp,
.snitch-status,
.snitch-fieldno,
.snitch-slot,
.id-card-idno,
p.composing,
.changelog-list li::before {
  font-family: var(--f-machine);
  font-size: var(--t-mono);
  letter-spacing: var(--tr-micro);
  line-height: var(--lh-mono);
  color: var(--ink-400);
  text-transform: none;
}

/* ---- §5.0a SHELL ATOMS ------------------------------------------ */
/* Exactly as the live regions in body.html had it inlined. */
.sr-only{ position:absolute; width:1px; height:1px; margin:-1px; padding:0; border:0;
          overflow:hidden; clip:rect(0,0,0,0); clip-path:inset(50%); white-space:nowrap; }
#top{ display:block; height:0; }
/* EVERY ANCHOR THE NAV POINTS AT, or it lands under the bar. The three
   added with the second pill are DESCENDANTS of #closing, and a
   scroll-margin on the ancestor does nothing for them — the browser
   offsets the element it actually scrolled to.
   #top is deliberately absent: it is a zero-height marker at the very top
   of the document, and an offset there would scroll the page backwards
   past its own beginning. */
#details, #snitch, #specs_list, #closing,
#thesis, #means, #changelog{ scroll-margin-top: calc(var(--nav-h) + 24px); }

/* ---- .js GATING ------------------------------------------------- */
/* The inline script at the top of <body> swaps html.no-js for
   html.js + html.booting. Anything that hides a reveal target hangs
   off `.js`; anything that belongs to the veil hangs off `.booting`.
   The gated rules themselves live with their components:
     html.booting #boot-veil   -> chrome.css
     .js .fill .word           -> sections.css  (step 5.2)  */
.no-js .js-only { display: none; }

/* ---- REDUCED MOTION — document level ---------------------------- */
/* The master keyframe kill block is in glitch.css (§6.6). */
@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior: auto; }
  body{ transition: none; }
}
