/* ============================================================
   SPINE — the homepage, site-plan.md §6.
   Colour: the 13 semantic slots only (CLR-01). Zero raw values.
   Requires: styles/fonts.css, styles/primitives.css,
             styles/tokens/agency.css + the other room files,
             styles/tokens/motion.css, src/rail.css, src/intake.css

   Motion budget for this page (MTN-01.3 — one choreographed moment
   per view): the hero gets the MTN-04 stagger, each rail gets its
   own on first scroll-into-view, and nothing else on the page
   animates at all. The nav never moves (MTN-08).
   ============================================================ */

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

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

.spine {
  --gutter: 1rem;
  --measure: 68rem;

  margin: 0;
  background: var(--surface-0);
  color: var(--text-hi);
  font-family: var(--face-body);
  /* TYP-03: no exceptions. */
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

.spine__wrap {
  inline-size: 100%;
  max-inline-size: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ---------- nav (MTN-08: chrome never moves) -------------------- */

.nav {
  position: sticky;
  inset-block-start: 0;
  z-index: 20;
  background: var(--surface-0);
  border-block-end: 1px solid var(--border);
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-block-size: 3.5rem;
}

/* The nav's intake trigger. Compact, because the nav is chrome and
   must not grow (MTN-08), but it is the one door reachable from any
   scroll position. */
.btn--nav {
  flex: 0 0 auto;
  min-block-size: 2.125rem;
  padding: 0 0.875rem;
  font-size: var(--fs-label);
  letter-spacing: 0.08em;
}

/* Text-only wordmark until the real mark is drawn. */
.nav__mark {
  font-family: var(--face-display);
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-hi);
  text-decoration: none;
  white-space: nowrap;
}

.nav__links {
  display: flex;
  gap: 0.25rem;
  overflow-x: auto;
  scrollbar-width: none;
  /* Without this the strip refuses to shrink below its content width,
     so on a narrow viewport it overruns the intake button instead of
     scrolling. A flex item needs an explicit zero minimum to yield. */
  min-inline-size: 0;
}

.nav__links::-webkit-scrollbar { display: none; }

/* Below this the wordmark, five anchors and the intake button cannot
   share a line: the strip clips mid-word behind the button and reads
   as broken rather than scrollable. The hero's labels scroll to the
   same four sections, so nothing becomes unreachable — the divisions
   are one tap away on the first screen, and "Start here" never leaves
   the bar. */
@media (max-width: 44rem) {
  .nav__links { display: none; }
}

.nav__link {
  padding: 0.4rem 0.6rem;
  border-radius: 0.375rem;
  font-family: var(--face-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-decoration: none;
  white-space: nowrap;
}

@media (hover: hover) and (pointer: fine) {
  .nav__link:hover { color: var(--text-hi); }
}

.nav__mark:focus-visible,
.nav__link:focus-visible,
.spine a:focus-visible,
.spine button:focus-visible {
  /* CLR-03: ring follows the link fallback chain. */
  outline: 2px solid var(--link-gold, var(--interactive));
  outline-offset: 2px;
}

/* ---------- section furniture ----------------------------------- */

/* Who We Serve, Network and Contact are connective tissue, not
   destinations — generous, but never forced to a full viewport. */
.section { padding-block: 4.5rem; }

.section__eyebrow {
  margin: 0 0 0.5rem;
  font-family: var(--face-mono);
  font-size: var(--fs-label);
  line-height: 1.3;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.section__title {
  margin: 0;
  max-inline-size: 22ch;
  font-family: var(--face-display);
  font-size: var(--fs-display-lg);
  line-height: 1.15;
  font-weight: 700;
  color: var(--text-hi);
}

.section__lede {
  margin: 0.75rem 0 0;
  max-inline-size: 60ch;
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--text-dim);
}

/* ---------- hero: the dart scene ---------------------------------
   Ported from docs/reference/hero-prototype-v3.html. The prototype
   carried hex because it was standalone; every value here is a token.
   The scene itself reads the same slots at runtime (src/hero3d.js),
   so canvas and DOM cannot drift apart.
   ------------------------------------------------------------------ */

.hero {
  position: relative;
  /* height, not min-height: min-block-size and min-height are the same
     property, so a 720px floor written as min-height silently beats a
     100svh written as min-block-size. The prototype uses the two
     distinct properties and so does this. */
  block-size: 100vh;              /* fallback for pre-svh browsers */
  block-size: 100svh;
  min-block-size: 720px;
  overflow: hidden;
  display: block;
  padding-block: 0;
}

/* ONE FIXED CANVAS, AND IT IS THE PAGE'S GROUND.
   The drum is real geometry in the scene, thirty units deep, and the
   camera walks down it as the page scrolls — so the canvas does not
   move: it is pinned to the viewport and every section travels in
   front of it.

   `z-index: -1` puts it in the negative stacking level: above the
   root's background, below every piece of in-flow content on the page.
   That is the whole layering rule for the drum, in one line, and it is
   why nothing else needed a stacking context to sit in front. */
.hero__canvas {
  position: fixed;
  inset: 0;
  z-index: -1;
  display: block;
}

/* Vignette. Derived from --surface-0 rather than a literal, so it
   follows the room instead of pinning the hero to one palette.

   IT BELONGS TO THE HERO'S BOX, WHICH IS WHY IT GOES WHERE THE DRUM
   IS THE GROUND. The drum's world runs the length of the page and the
   hero's box ends at the first screenful — so a full-bleed darkening
   drawn on that box paints one tone above the boundary and none below
   it, and the join reads as a horizontal seam sliding up the wall as
   you scroll. Nothing at section level may paint over a continuous
   world: the depth this used to give is the scene's own now, in its
   fog and its background. */
.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(120% 90% at 50% 30%, transparent 55%,
    color-mix(in srgb, color-mix(in oklab, var(--surface-0), black 45%) 55%, transparent) 100%);
}

.hero__copy {
  position: absolute;
  inset-inline-start: 50%;
  inset-block-start: 7vh;
  transform: translateX(-50%);
  z-index: 3;
  inline-size: min(92vw, 760px);
  text-align: center;
  /* The scene tracks the pointer underneath, so the copy must not
     swallow it — the one interactive child opts back in. */
  pointer-events: none;
}

.hero__eyebrow {
  margin: 0;
  font-family: var(--face-mono);
  font-size: var(--fs-label);
  line-height: 1.3;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--interactive);
}

.hero__title {
  margin: 0.875rem 0 0;
  font-family: var(--face-display);
  font-size: clamp(2.125rem, 6vw, 4rem);
  line-height: 1.05;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-hi);
}

/* Fixed height so a thesis swap never nudges the layout. */
.hero__caption {
  margin-block-start: 1rem;
  min-block-size: 4.5em;
}

.hero__divname {
  margin: 0;
  font-family: var(--face-mono);
  font-size: var(--fs-body-sm);
  line-height: 1.3;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  transition: color var(--t-slow) var(--e-out);
}

.hero__thesis {
  margin: 0.375rem 0 0;
  font-size: var(--fs-body);
  line-height: 1.5;
  color: var(--text-dim);
  transition: opacity var(--t-base) var(--e-out);
}

.hero__caption.is-swapping .hero__thesis { opacity: 0; }

/* ---------- labels: projected buttons ---------- */

.hero__label {
  appearance: none;
  position: absolute;
  z-index: 2;
  transform: translate(-50%, -50%);
  padding: 0.875rem 1rem;
  border: 0;
  background: none;
  font-family: var(--face-mono);
  font-size: var(--fs-label);
  line-height: 1.3;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  opacity: 0;
  transition: color var(--t-base) var(--e-out), opacity var(--t-slow) var(--e-out);
}

.hero__label::after {
  content: '';
  position: absolute;
  inset-inline-start: 50%;
  inset-block-end: 0.5rem;
  inline-size: 56%;
  block-size: 1.5px;
  background: currentColor;
  transform: translateX(-50%) scaleX(0);
  transition: transform var(--t-base) var(--e-out);
}

.hero.is-loaded .hero__label { opacity: 0.85; }

.hero__label.is-active { opacity: 1; font-weight: 500; }

.hero__label.is-active::after { transform: translateX(-50%) scaleX(1); }

.hero__label:focus-visible {
  /* Project rule (CLR-03): rings follow the link fallback chain, not
     currentColor as the prototype had it — a dim label would give a
     dim ring. */
  outline: 2px solid var(--link-gold, var(--interactive));
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---------- static fallback ---------------------------------------
   No WebGL, or prefers-reduced-motion. The mark is the same one the
   gates use (src/gate.js), so there is a single drawing of it.
   ------------------------------------------------------------------ */

.hero__static {
  position: absolute;
  inset-block-end: 12vh;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hero__mark {
  inline-size: clamp(5rem, 14vw, 9rem);
  block-size: auto;
  color: var(--interactive);
}

/* Without a scene to project onto, the labels sit in a row under the
   mark rather than at absolute coordinates nothing computed. */
.hero.is-static .hero__label {
  position: static;
  transform: none;
  opacity: 0.85;
}

.hero.is-static .hero__labels,
/* GLASS ONLY WHEN THERE IS A DRUM BEHIND IT. Without WebGL, or under
   reduced motion, the scene never mounts and `has-drum` is never set:
   the rooms keep their own surfaces and the page is what it always
   was. */

/* THEY RIDE THE CANVAS. A name sits under the figure it belongs to,
   and the figure is now in a world the camera descends through — so
   the layer that holds the names is pinned exactly as the canvas is,
   and hero3d writes viewport coordinates into it. It scrolls away by
   the figures leaving the frame, which is what the descent is for. */
.spine.has-drum .hero__labels,
.spine.has-drum .hero__read {
  /* A REAL BOX, THE SIZE OF THE VIEWPORT. `.hero__labels` is
     `display: contents` in its own rule — it generates no box at all,
     so `position: fixed` did nothing and its children went on being
     placed against the hero, one nav-bar below where the projection
     said. That is a whole label of clearance, and it is why the names
     came back down onto the helmets. */
  display: block;
  position: fixed;
  inset: 0;
  transform: none;
  z-index: 3;
  /* A VIEWPORT-SIZED BOX AT z-index 3 IS A LID ON THE PAGE. It exists
     to hold four names over a canvas, and everything under it — every
     card, every arrow, the rail's whole surface — has to stay
     clickable. The names take the pointer back for themselves. */
  pointer-events: none;
  /* AND IT IS CLIPPED TO THE HERO'S BOX. A pinned layer with nothing
     tying it to the hero moment rides over every section below it —
     four division names and two data chips across the agency rail. The
     two insets are the hero's own edges in viewport pixels, written by
     src/hero3d.js from one measurement a frame; inside the hero they
     are zero and nothing is clipped. clip-path takes hit-testing with
     it, so a name that has left the hero is not clickable either. */
  clip-path: inset(var(--hero-clip-top, 0px) 0 var(--hero-clip-bottom, 0px) 0);
}

/* PAST THE HERO, THE HERO'S DOM IS GONE — all of it, by one class
   rather than one rule per satellite. The clip already empties these
   layers as the hero leaves; this is the other half of the same
   measurement, and it is what makes "gone" mean not painted, not
   measured and not reachable rather than merely clipped to nothing.
   Scrolling back to the top restores them by the same number.

   Not scoped to has-drum: the copy block is absolute inside a hero
   that clips it, so it was already contained — and a rule that only
   holds on one of the three mount paths is a rule with a hole in it. */
.hero.is-past .hero__copy,
.hero.is-past .hero__labels,
.hero.is-past .hero__read {
  visibility: hidden;
  pointer-events: none;
}

.spine.has-drum .hero__label {
  pointer-events: auto;
}

.hero__labels {
  position: absolute;
  inset-inline-start: 50%;
  inset-block-end: 5vh;
  transform: translateX(-50%);
  z-index: 2;
  display: contents;
}

.hero.is-static .hero__labels {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.25rem;
}

/* ---------- the SCOUTER read: Analytics' data chips --------------
   Three chips anchored to body points on the Analytics figure and
   projected every frame by the same machinery that places the division
   names. src/scouter-read.js is the delivered component and the design
   these follow; it targets the single-avatar SLOT api, so the hero
   renders the same design against its own four-figure scene rather
   than importing something written for a different stage.

   WHY THIS LAYER CARRIES data-room="analytics".
   The read belongs to one division, and its four colours are that
   division's semantic slots — border, text-hi, text-dim, interactive,
   success — which in the analytics room resolve to exactly the values
   the design names (#2E2D22, #EFEDE4, #8F8D7E, #D8AE3C, #58A97F).
   Scoping the layer to the room is how those are cited without a
   literal, and it is the same mechanism readPalette() already uses to
   read each division's own gold for the labels. The --an-* aliases
   below exist so this file reads like the component it mirrors. */

.hero__read {
  --an-line:    var(--border);
  --an-ink:     var(--text-hi);
  --an-dim:     var(--text-dim);
  --an-accent:  var(--interactive);
  --an-success: var(--success);

  position: absolute;
  inset: 0;
  z-index: 3;
  /* The chips sit over the canvas the figure is picked from, so they
     must never take the pointer: moving toward a chip would otherwise
     read as leaving the figure and pop the whole read straight back
     out from under the cursor. */
  pointer-events: none;
  font-family: var(--face-display);
  /* THE SIZE DIAL. Every dimension inside a chip is em-based, so one
     font-size scales the whole read without touching the figure —
     the property the delivered component is built around, kept. */
  font-size: 14px;
  display: none;
}

.hero__read.is-live { display: block; }

.hero__read-lines {
  position: absolute;
  inset: 0;
  overflow: visible;
}

.hero__read-lines line {
  stroke: var(--an-accent);
  stroke-width: 1;
  opacity: 0.75;
}

.hero__read-lines circle { fill: var(--an-accent); }

.hero__chip {
  position: absolute;
  top: 0;
  left: 0;
  /* Scaled from the middle so a pop-in grows out of its own centre
     rather than sliding. The translate half is what places it. */
  transform: translate(-50%, -50%) scale(0);
  white-space: nowrap;
  background: color-mix(in srgb, var(--surface-0) 85%, transparent);
  border: 1px solid var(--an-line);
  padding: 0.55em 0.8em;
  border-radius: 2px;
  font-variant-numeric: tabular-nums;      /* TYP-03 */
}

.hero__chip .k {
  display: block;
  font-family: var(--face-mono);
  font-size: 0.7em;
  font-weight: 500;
  letter-spacing: 0.26em;
  color: var(--an-dim);
  margin-block-end: 0.2em;
}

.hero__chip .v {
  font-size: 1.25em;
  font-weight: 600;
  color: var(--an-ink);
}

.hero__chip .v s {
  text-decoration: none;
  font-size: 0.8em;
  font-weight: 400;
  color: var(--an-dim);
}

.hero__chip .verdict {
  display: inline-block;
  margin-inline-start: 0.6em;
  padding: 0.1em 0.6em;
  font-family: var(--face-mono);
  font-size: 0.7em;
  font-weight: 600;
  letter-spacing: 0.22em;
  color: var(--on-interactive);
  background: var(--an-success);
  border-radius: 2px;
  vertical-align: 0.15em;
}

.hero__chip .bar {
  block-size: 2px;
  background: var(--an-line);
  margin-block-start: 0.45em;
  border-radius: 1px;
  overflow: hidden;
}

.hero__chip .bar i {
  display: block;
  block-size: 100%;
  background: var(--an-success);
  transform-origin: left;
  transform: scaleX(var(--f));
}

.hero__ring {
  display: inline-block;
  vertical-align: middle;
  margin-inline-end: 0.55em;
  inline-size: 2em;
  block-size: 2em;
}

.hero__ring circle { fill: none; stroke-width: 3; }
.hero__ring .bg { stroke: var(--an-line); }

.hero__ring .fg {
  stroke: var(--an-success);
  stroke-linecap: round;
  stroke-dasharray: 75.4;
  stroke-dashoffset: var(--o);
}

/* Without a scene to project onto there is nowhere to anchor a chip and
   nothing to draw a leader to — so the read becomes what it is on its
   own: three readable cards under the mark. Same treatment the labels
   already get, and the reason the reduced-motion path can show all
   three at once with nothing animating. */
.hero.is-static .hero__read {
  display: flex;
  position: static;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  margin-block-start: 1.5rem;
}

.hero.is-static .hero__read-lines { display: none; }

.hero.is-static .hero__chip {
  position: static;
  transform: none;
}

/* ---------- buttons (CMP-02) ------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-block-size: 2.75rem;
  padding: 0 1.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--interactive);
  --hover-base: var(--interactive);
  background: var(--hover-base);
  color: var(--on-interactive);
  font-family: inherit;
  font-size: var(--fs-body-sm);
  font-weight: 600;
  text-decoration: none;
  transition: background-color var(--t-fast) var(--e-out), transform var(--t-instant) var(--e-out);
}

.btn--ghost {
  --hover-base: transparent;
  background: var(--hover-base);
  border-color: var(--border);
  color: var(--text-hi);
}

@media (hover: hover) and (pointer: fine) {
  .btn:hover { background: color-mix(in oklab, var(--hover-base), var(--hover-pole, white) 6%); }
}

.btn:active { transform: scale(0.965); }

/* ---------- who we serve ----------------------------------------- */

.serve__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
  margin-block-start: 2rem;
}

.serve__box {
  padding: 1.25rem;
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  background: var(--surface-1);
}

.serve__box h3 {
  margin: 0 0 0.375rem;
  font-family: var(--face-display);
  font-size: var(--fs-display-md);
  line-height: 1.25;
  font-weight: 600;
  color: var(--text-hi);
}

.serve__box p {
  margin: 0;
  font-size: var(--fs-body-sm);
  line-height: 1.5;
  color: var(--text-dim);
}

/* ---------- rail sections ---------------------------------------- */

/* The room arrives via data-room on this element (tokens/<division>.css);
   everything inside — including the rail component — simply reads the
   slots it always reads. */
/* NOTHING ON THE SPINE PAINTS ITS OWN GROUND.
   The drum is the ground now: a section, or a rail inside one, that
   fills itself with a surface fills over the wall the page is
   descending. So on the spine they are glass and the room arrives as
   the world's paint. The CARDS keep their surfaces — they are what
   floats on the wall — and every other page keeps everything, because
   the drum is the spine's alone. */
.spine.has-drum .railsection,
.spine.has-drum .railsection .rail,
.spine.has-drum .contact,
.spine.has-drum .section,
.spine.has-drum .hero {
  background: transparent;
}

/* THE FIRST SECTION COMES IN ON THE WALL, NOT ON THE PODIUM.
   "Who we serve" used to arrive while the viewport was still on the
   drum's top face: its heading crossed into the reading band 125px
   ABOVE the rim at 1280x900, so the copy read as floating over the
   cast rather than as printed on the wall the page is descending.

   The fix is document spacing, not choreography — the descent is
   locked, and the room ramp reads section offsets, so pushing the
   section down moves its room's arrival with it and nothing has to be
   told twice.

   340px, and it is a MARGIN rather than a bigger padding: the section's
   own padding changes at the wide breakpoint, and a rule that
   overwrote it would quietly shorten the block on a desktop while
   lengthening it on a laptop.

   The rim's screen position is a function of the descent, which is
   measured in scroll pixels, so the offset is one too — and the number
   is set by where the rim BULGES rather than by where it starts. As
   the stage finishes zooming in the rim runs down the frame, then the
   descent takes it back up faster than the page moves; the heading
   catches it in between. Solved against that crossing, 1280x900 wants
   267px and 1920x1080 wants 330 — a taller frame carries the rim
   further down before the camera starts lifting it. The later of the
   two wins, rounded up.

   LANDSCAPE ONLY, on the same boundary the stage uses: narrowT is zero
   at aspect 1 and above, so a portrait page has no zoom ramp for the
   rim to run down and nothing here to correct. Its spacing is what it
   was. */
@media (min-aspect-ratio: 1 / 1) {
  .spine.has-drum .hero + .section { margin-block-start: 340px; }
}

/* AND THE PORTRAIT HALF OF THE SAME PROBLEM. A phone has no zoom ramp
   — the stage holds one framing below aspect 1 — so the rim's only
   motion is the descent's, and the crossing is later and shallower:
   the copy came in 49px over the face and was 98px over it by the time
   it was being read. Solved the same way, against the whole entry
   rather than one instant.

   The boundary is the complement of the landscape rule's to the last
   thousandth, so the two partition every aspect and neither can apply
   twice. */
@media (max-aspect-ratio: 9999 / 10000) {
  .spine.has-drum .hero + .section { margin-block-start: 220px; }
}

/* and no section-level overlay either — the vignette is the hero box's
   own, and the hero box ends in the middle of the drum's wall */
.spine.has-drum .hero::after {
  content: none;
}

/* and the rules between rooms go with the surfaces: a divider drawn
   across the wall is a line across the drum */
.spine.has-drum .network,
.spine.has-drum .contact {
  border-block-start: 0;
}

/* THE PAGE'S OWN TONE GOES ON THE ROOT, NOT THE BODY.
   A background on <body> is propagated to the viewport's canvas only
   while the root has none — and a canvas at `z-index: -1` paints ABOVE
   that propagated background but BELOW a background the body paints
   for itself. Chrome resolved that pair by covering the drum with a
   strip of body at the foot of every frame. So the drum's page tone is
   written on <html> by src/drum-rooms.js and the body carries none:
   one surface, painted once, under everything.
   Without the drum (no WebGL, no JS) the body keeps its own. */
body.spine.has-drum {
  background: transparent;
}

.railsection {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-block-size: 100vh;          /* fallback for pre-svh browsers */
  min-block-size: 100svh;
  /* THE DRUM IS THE GROUND on the spine: a section that paints its own
     surface paints over the wall, and the rule between two rooms is a
     line drawn across it. Both go there — ratified with the drum — and
     the room arrives as the world's paint instead. Every other page
     keeps its surface; the drum is the spine's alone. */
  background: var(--surface-0);
  /* `color` inherits as a resolved VALUE, so without this the section
     would carry the master room's near-white ink down from <body> and
     any text not setting its own colour would vanish on paper. Naming
     the slot here re-resolves it inside the room. */
  color: var(--text-hi);
  /* Clear the sticky nav when a division anchor is followed. */
  scroll-margin-block-start: 3.5rem;
}

/* A rail centred in a full-viewport section leaves a lot of air above
   and below it, because a card is only as tall as its copy. Giving
   the cards a viewport-relative floor makes the section read as
   deliberate rather than under-filled — and it gives the gate room to
   behave like a door. Capped in rem so it cannot run away on a tall
   desktop, and well under the height budget at 390x844. */
.railsection .rail__card {
  min-block-size: min(26rem, 46vh);
}

/* THE IDENTITY CARD'S QUIET WAY OUT IS GONE with the link it styled
   (src/gate.js). The spine no longer points at the deep pages: a rail
   IS its division, so a link out of the card into a longer version of
   itself was a door back into the room you are standing in. */

/* ---------- division gates (§2: the door at the end of a rail) ----
   Built by src/gate.js after the rail mounts, so Rail's API and its
   contract suite are untouched. A gate is a threshold, not a sixth
   service card, so it is wider, one surface higher, and the only
   thing in the row wearing the room's gold as an outline.

   rail.css fills .rail__card--cta with --interactive. A gate undoes
   that fill: the mark has to BE the gold, and gold-on-gold is
   invisible. The weight comes back through the border, the extra
   width, the elevation step and a solid gold arrow — CLR-02 reads
   that as raised, which is what a door should be.
   ------------------------------------------------------------------ */

/* A GATE IS A CARD WITH AN OUTLINE, in every room. NIL's gate reached
   this first — paper surface, beige edge — and it turned out to be the
   rule rather than the light room's exception: a door does not need a
   surface of its own to be a door, and giving it one puts a
   differently-coloured panel in the middle of the card layer.

   So the surface is the room's NORMAL card surface, --surface-1, the
   same one every content card on the rail is painted with, and the
   whole distinction is the 2px outline. It resolves through
   --card-accent where a room declares one (NIL's beige, the tone its
   identity piece is filled with) and falls back to the room's accent
   everywhere else, which is each of the three dark rooms' gold. The
   weight is still all there: two pixels against a content card's one,
   the extra width, the extra padding, the elevation step and the solid
   accent arrow. */
.rail__card--cta.gate {
  /* Wider than a content card. gate.js fires a resize afterwards so
     the rail re-measures its snap offsets against the new width. */
  flex-basis: min(calc(var(--rail-card-w) + 3rem), 88vw);
  gap: 0.75rem;
  padding: calc(var(--rail-card-pad) * 1.15);
  border: 2px solid var(--card-accent, var(--interactive));
  --hover-base: var(--surface-1);
  background: var(--hover-base);
  color: var(--text-hi);
}

.gate__head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* THE OPERATOR'S OWN LOGO, VERBATIM (src/gate.js). It is a raster with
   its own two colours, so nothing here tints, masks or filters it —
   the box is the only decision left. Square, because the file is, and
   a shade under the 28x34 the drawn arrowhead occupied so the four
   gates line up on the same cap height as the eyebrow beside them. */
.gate__mark {
  flex: 0 0 auto;
  inline-size: 1.875rem;
  block-size: 1.875rem;
  object-fit: contain;
}

.gate__words { display: flex; flex-direction: column; gap: 0.125rem; }

.gate__eyebrow {
  margin: 0;
  font-family: var(--face-mono);
  font-size: var(--fs-label);
  line-height: 1.3;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.gate__division {
  margin: 0;
  font-family: var(--face-display);
  font-size: var(--fs-display-md);
  line-height: 1.15;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  /* Gold TEXT. CLAUDE.md forbids --interactive for this on paper, so
     it resolves through --link-gold where a room defines one (NIL)
     and falls back to the room accent everywhere else. Same pattern
     as rail.css's .rail__link. */
  color: var(--link-gold, var(--interactive));
}

/* Undo the accent-fill text colours rail.css sets for a plain CTA
   card; on a gate the fill is gone, so --on-interactive would be
   unreadable. */
.rail__card--cta.gate .rail__title {
  margin-block-start: 0.25rem;
  color: var(--text-hi);
}

/* The arrow is the affordance: destination on the left where there
   is one, a solid gold target on the right. */
.rail__card--cta.gate .rail__cta-action {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  inline-size: 100%;
  margin-block-start: auto;
  padding-block-start: 0.5rem;
  color: var(--text-dim);
}

.rail__card--cta.gate .rail__cta-action .rail__chev {
  flex: 0 0 auto;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  padding: 0.625rem;
  border-radius: 50%;
  background: var(--interactive);
  color: var(--on-interactive);
}

/* MTN-06 hover-lift, same -3px as any other card — a gate is heavier,
   not springier. */
@media (hover: hover) and (pointer: fine) {
  .rail__card--cta.gate:hover {
    transform: translateY(-3px);
    background: color-mix(in oklab, var(--hover-base), var(--hover-pole, white) 6%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .rail__card--cta.gate:hover { transform: none; }
}

/* ---------- light-room physics ------------------------------------
   CLR-02 inverts on paper. A white card on warm paper is 1.02:1 apart
   from its own background, so lightness cannot carry elevation and a
   shadow has to. Scoped to the light room: in the dark rooms height
   is still lightness, and a shadow there would simply disappear.
   ------------------------------------------------------------------ */

[data-room='nil'] .rail__card { box-shadow: var(--elevation-1); }

/* NIL'S OVERRIDE IS GONE, because it became the rule. The room still
   supplies the tone — --card-accent is beige here and unset in the
   three dark rooms, so the base rule above resolves to beige on paper
   and to gold everywhere else, which is what the override used to say
   in a room of its own. */

/* ---------- network ---------------------------------------------- */

.network { border-block-start: 1px solid var(--border); }

.network__nodes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-block-start: 2rem;
  padding: 0;
  list-style: none;
}

.network__node {
  padding: 0.5rem 0.875rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-1);
  font-family: var(--face-mono);
  font-size: var(--fs-label);
  line-height: 1.3;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ---------- contact ---------------------------------------------- */

.contact {
  border-block-start: 1px solid var(--border);
  background: var(--surface-1);
  scroll-margin-block-start: 3.5rem;
}

.contact__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-block-start: 2rem;
}

.foot {
  padding-block: 2rem;
  border-block-start: 1px solid var(--border);
  font-family: var(--face-mono);
  font-size: var(--fs-label);
  line-height: 1.6;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ---------- MTN-01.6 reduced motion ------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .spine.is-staged .hero__stagger,
  .spine.is-staged.is-entering .hero__stagger {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .btn { transition: none; }
  .btn:active { transform: none; }
}

/* ---------- >=48rem ---------------------------------------------- */

@media (min-width: 48rem) {
  .spine { --gutter: 2rem; }

  .section { padding-block: 7rem; }

  .serve__grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 64rem) {
  .serve__grid { grid-template-columns: repeat(4, 1fr); }
}
