/* Dominia — the page's connective sections: lede, why play, contest, founding commanders, and
 * the footer wordmark.
 *
 * These exist because the page had a hero and a mechanic and then stopped. A landing page whose
 * job is an install needs a beat between "what is this" and "here is how it works", and a beat
 * that gives a reason to act before it asks for an email. The first is lifted in construction
 * from the reference: one statement with most of a screen of air around it. The last used to be
 * numbers ("The Realm") and is now a dossier — see the note at .founders for why.
 *
 * The middle two were added after the mechanic sequence landed, and they answer the two
 * questions it leaves open. 002 says what you can do; it does not say why anyone would want to
 * (003) or what it costs you to stop (004). Between them they are the difference between a
 * mechanic that is understood and one that is wanted.
 */

/* ---------------------------------------------------------------- lede

   ONE sentence, and a great deal of nothing. The reference does this repeatedly — see
   shots/ref/orig/f03.png, where a three-line 57.6px statement sits in the lower-left with the
   entire upper half of the frame empty. The emptiness is not wasted space; it is what makes the
   statement read as a claim rather than as a paragraph. */

.lede {
  padding-block: var(--s-32) var(--s-24);
  border-top: 1px solid var(--hairline-strong);
}

.lede__label { color: var(--slate); }

.lede__title {
  margin-block: var(--s-16) var(--s-12);
  /* 18 characters a line at the headline size puts this on three lines at 1440, which is the
     shape the reference sets and the shape 1.06 leading was measured for. */
  max-width: 18ch;
  color: var(--bone);
}

/* 30ch, not 34. The reference's section-001 paragraph is a FOUR-LINE block about 250px wide
   tucked into the bottom-left corner (shots/g2/ref-home-d-02.png), and a short stack reads as a
   caption under a claim. At 34ch this ran three long lines, which reads as the start of an
   article. Nothing is cut; the same sentence just sets narrower. */
.lede__body {
  color: var(--bone-2);
  max-width: 30ch;
}

/* THE CHARACTER BUDGET: 58. Every message is mono, so "one entry, one line" is arithmetic rather
   than taste — the message column holds (its width / the advance) characters and nothing else
   decides it. The longest entry is 56 characters ("Cmdr. Paresh walked 3.2 km and captured
   Botanical Garden"), so the column is sized for 58 and messages are written to stay under it.
   Both halves are load-bearing; break either and a message goes to two lines.

   WHY THE MESSAGES ARE 10.8px AND NOT 12.6px. At 12.6px the advance is 7.56 and 56 characters
   need a 424px column, i.e. a 533px panel — and a 533px panel cannot sit beside the copy until
   1426px, because below that the copy column drops under the headline's own 18ch measure and the
   headline changes shape. That would have stacked this section on every 1280 and 1366 laptop.
   At 10.8px — --t-label-sm, the site's smallest chrome size, which the timestamps already use —
   the advance is 6.48, the same 56 characters need 363px, and the panel lands at 480px, which is
   where it already was. The denser setting also reads more like a real feed, but that is a bonus
   and not the reason.

   THE BAND AND THE BREAKPOINT follow from that: 480px at 1280 (57 chars) up to 512px at 1506 and
   beyond (62 chars), side by side only from 1280px. The copy column is still NOT told a width —
   .lede__title and .lede__body carry their own 18ch and 34ch measures, and at 1280 the 1fr
   leaves 616px against the headline's 617px, so the headline keeps its designed two-line shape
   at every width where the two columns coexist.

   Below 1280 the panel stacks and takes the full measure, which is more room than it needs down
   to about 520px. Below THAT — phones — 56 characters do not fit on one line at any size this
   site is willing to set, so they wrap, with the message hanging under itself rather than under
   the timestamp. Wrapped and readable beats unwrapped and 8px. */
/* Stacked is the DEFAULT, and it is declared before the media query on purpose: both rules are
   one class deep, so at equal specificity the later one wins, and when this sat below the block
   it beat the `margin-top: 0` inside it. The two columns then matched in height and started 48px
   apart — a bug that measures as "heights align, tops do not". Source order is the whole fix. */
.monitor { margin-top: var(--s-12); }

/* THE STACKED BAND HAS A CEILING ON THE PANEL, and it is the one thing the stacked layout was
   missing. Between 721 and 1279 the chassis took the full measure, so at 1279 the pane was
   1160px wide and 653px tall on its own and the section ran to 1385px — a 900px laptop got the
   headline and half a picture, and nothing else (shots/g2/premise/a7-d-1279x900.png). 760px is
   a 428px pane, which with the bar and the log is about two thirds of an 800px screen and
   leaves the copy above it in the same frame. Wider than the panel needs for its 58-character
   budget either way; this is a composition cap, not a measure. */
@media (min-width: 721px) and (max-width: 1279px) {
  /* The log is a sibling of the chassis now, so it has to be told the same ceiling or it sets
     wider than the thing it is docked to. */
  .monitor,
  .comms { max-width: 760px; }
}

/* ---- THE SECTION HOLDS THE WHOLE FRAME, from 1280 up. -------------------------------------
   Measured before this pass, at 1440x900: the section was 688px tall in a 900px viewport and
   all of its content sat in the TOP half — copy from y130 to y460, monitor from y130 to y590 —
   with roughly 300px of dead ground under the paragraph before section 002 arrived
   (shots/g2/premise/before-d-1440x900.png). The bar does not do that. In BOTH of its comparable
   sections the frame is full: section 001 stands one large figure through nearly the entire
   900px height with the index label and paragraph tucked into the bottom-left corner
   (shots/g2/ref-home-d-02.png), and the section above it runs its media panel from mid-frame
   down and OFF the right edge, with the label, paragraph and chip in the bottom-left
   (shots/g2/ref-home-d-01.png). Two different pictures, one construction: a tall object holding
   the right, text on the floor at the left, and the emptiness banked at the TOP where it reads
   as air above a composition rather than as a gap under one.

   So three things change and they are one move:
     1. the section is at least a screen tall, and its content is bottom-aligned inside it;
     2. the monitor takes a height off the VIEWPORT rather than off its own 16:9 arithmetic,
        so it spans the frame instead of sitting in the top third;
     3. it bleeds off the right edge, which is the bar's own device for filling a frame — the
        page margin is cancelled on that side only.

   THE BLEED IS FREE HERE because .hud-corners--left already puts this panel's brackets on the
   LEFT pair. There was never a right-hand corner mark to lose. */
@media (min-width: 1280px) {
  .lede {
    /* svh, not vh: a mobile URL bar is irrelevant at 1280 but a resize during the shot is not,
       and svh is the stable one. Padding is viewport-relative so the composition survives the
       700-1080 height sweep instead of being tuned for 900 and clipping at 720. */
    min-height: 100svh;
    padding-block: clamp(48px, 7svh, 120px) clamp(40px, 6svh, 88px);
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
  }

  .lede__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) clamp(520px, 46vw, 720px);

    /* TWO ROWS, and the split is the whole of round 2's answer to a blind critic who called the
       left half "a headline pinned to the top and a caption pinned to the bottom … parts near
       each other", with "roughly 450px of vertical nothing" between them. Measured, that span was
       456px, and it was a direct consequence of stretching the copy column and pushing its two
       halves to opposite edges.

       Row 2 is the copy at its own height, on the floor. Row 1 is whatever is left above it, and
       the undocked log sits at the BOTTOM of row 1 — so the log and the copy read as one low
       cluster and the air banks at the top, above a composition, rather than opening a hole in
       the middle of one. That is the shape the reference sets in the same slot: everything low,
       air above, one tall object holding the frame (shots/g2/ref-home-d-02.png).

       ROWS RATHER THAN ABSOLUTE PLACEMENT. The log doubles in height under reduced motion
       (css/motion.css opens it to all twelve entries), and an absolutely-positioned panel would
       grow straight into the headline. A grid row cannot: row 1's automatic minimum is the log's
       own min-content, so the grid gets taller and the section, which is min-height rather than
       height, simply grows with it. */
    grid-template-rows: 1fr auto;

    /* Column gap only. The row gap is the log's own margin-bottom, because the two rows want
       about a third of what the two columns want and one `gap` cannot say both. */
    column-gap: clamp(32px, 5vw, 96px);
    row-gap: 0;

    /* THE BLEED, and it is done by DELETING the right padding rather than by adding a negative
       right margin. The negative margin was tried first and is wrong: on an auto-width block
       `width = containing block - marginLeft - marginRight`, so -67px of margin makes the
       element 67px WIDER THAN THE VIEWPORT and the page gains a horizontal scroll — the panel
       runs off the edge instead of stopping at it, and the readout's last field goes with it
       (shots/g2/premise/a1-d-1440x900.png, where "24 FPS · LIVE" is cut in half).
       Dropping the padding keeps the box exactly viewport-wide and simply moves its content
       edge to the frame edge, which is all the bleed ever needed. */
    padding-right: 0;
  }

  /* THE PANEL TAKES ITS HEIGHT FROM THE VIEWPORT, not from its own contents. The old rule did
     the opposite and that is what left the lower half empty: a 16:9 pane at 620px is 349px tall,
     plus a bar and a 94px log window, and 486px is simply not a screen no matter where you put
     it. Now the chassis is told how tall to be and the PICTURE absorbs the difference.

     The clamp floor of 600px is arithmetic, not taste. The shortest this chassis can ever be is
     head (43) + log (121) + border (2) + a 16:9 pane, and the widest pane it gets is 752px at
     1920+, so 43+121+2+423 = 589. 600 clears that at every width, which means the pane is never
     asked to shrink below its 56.25cqw floor — see .feed further down for why that matters, and
     KX in js/premise.js for what breaks if it ever does. */
  .monitor {
    grid-column: 2;
    grid-row: 1 / -1;
    display: flex;
    flex-direction: column;
    margin-top: 0;
    height: clamp(600px, 78svh, 780px);
  }

  /* ---- THE COPY IS ONE BLOCK ON THE FLOOR --------------------------------------------------
     Round 1 held the copy column open to the full height of the row and pushed the headline to
     the ceiling and the label and paragraph to the floor, so that the top-left quadrant would not
     be empty. It bought that at the cost of a 456px hole in the middle of the column, which is
     the thing the critic actually saw. The trade was the wrong way round: air at the top of a
     frame reads as air; air *inside* a block of type reads as a missing element.

     So the three parts set as one stack — claim, index, caption — and the stack stands on the
     floor. What holds the middle of the column now is an object rather than a margin: the log,
     one row up, running out past the gutter and over the picture. See .monitor__log below.

     THE DOM ORDER IS NOT TOUCHED. `order: -1` moves the headline visually and leaves the reading
     order as it was — index label, heading, paragraph — so the section still announces itself
     before it speaks. */
  .lede__copy {
    grid-column: 1;
    grid-row: 2;
    display: flex;
    flex-direction: column;
  }

  /* 48px under the headline, 24px between label and paragraph: the caption pair binds to itself
     more tightly than it binds to the claim, which is what makes three parts read as two. */
  .lede__title { order: -1; margin: 0 0 var(--s-12); }
  .lede__label { margin: 0; }
  .lede__body  { margin-top: var(--s-6); }

  /* .feed and .monitor__log take their share of that height in a SECOND 1280 block further
     down, immediately after their own base rules. Both rules are one class deep, so at equal
     specificity source order decides, and their base rules are declared below this point — put
     the overrides here and `flex: none` and the 3.5-row height simply win. The file has been
     bitten by exactly this before; see the note above .monitor's margin-top. */
}


/* ---------------------------------------------------------------- the premise monitor

   A field monitor beside the claim: a picture and the log that reads it, in one chassis. The
   reference material for this kind of panel is always cyan-on-black with bloom on the live
   rows, and css/hud.css already settled how this brand answers that: "we have five colours,
   hairlines, and no glow. What survives translation is the GRAMMAR." So the grammar is here —
   bracketed leading edge, ruled ground, mono timestamps, a mark that is alive — and the bloom
   is not.

   WHY THE VIDEO IS INSIDE THE CHASSIS RATHER THAN BESIDE IT. The hero is already a full-bleed
   photograph with a floating HUD panel on it. Give this section the same construction one
   screen later and section 001 is the hero again with a different picture. The difference that
   makes them two objects instead of one repeated twice: the hero is the WORLD — edge to edge,
   no frame — and this is a piece of EQUIPMENT looking at the world. Recessed, bezelled,
   contained. Which is also why the copy keeps its own column and no type is ever set on the
   footage: nothing here needs a scrim, so nothing here fights the picture for contrast.

   GROUND. --ink-0 rather than the --ink-2 plane the other panels stand on. The kind colours are
   the point of the log and they are all mid-luminance: on --ink-2 alliance green measures
   3.71:1 and would fail as a mark; dropping to --ink-0 buys every one of them roughly 1.4x. The
   panel reads as a screen recessed into the page rather than a card standing on it, which is
   also what a monitor should do. */

.monitor {
  background-color: var(--ink-0);
  border: 1px solid rgba(63, 143, 78, 0.38);   /* --alliance at instrument weight, not neon */
  --hud-arm: 12px;
  --hud-line: var(--alliance);
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.monitor__head {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid rgba(63, 143, 78, 0.24);
}
.monitor__id    { color: var(--bone); }
.monitor__state { display: flex; align-items: center; gap: var(--s-2); color: var(--slate-2); }
.monitor__fps   { color: var(--slate); }

/* The site's existing live mark, not a new one: same 6px square, same --alliance, same 2s
   ambient beat and the same keyframes as .hero__hud-live and .beat__live. A third invention
   here would be a third dialect of "this is live" on one page. */
.comms__live {
  width: 6px;
  height: 6px;
  flex: none;
  display: block;
  background: var(--alliance);
  animation: live-pulse var(--dur-ambient) ease-in-out infinite;
}

/* ---------------------------------------------------------------- the feed pane */

.feed-wrap { position: relative; }

.feed {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--ink-0);
  flex: none;
}

/* The grade is CSS, not a re-encode: the plate is already cold and desaturated, it just needs
   pulling the last of the way onto this site's ladder. */
.feed__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.72) contrast(1.08) brightness(0.92);
}

/* Vignette to --ink-0 so the rectangle dissolves into the bezel instead of ending at an edge,
   plus a low claim wash on the road where the trail lives.

   THE SKY BAND IS THE THIRD LAYER AND IT IS NEW. The plate is a street shot and the top third of
   it is flat overcast sky — 200px of even mid-grey once the pane grew to hold the frame, which
   made it the brightest and emptiest area on the whole page, sitting directly above the log.
   The reference's equivalent frame is near-black with one lit subject in it. Rolling the sky
   down toward --ink-0 costs nothing the picture needs (there is no information up there), pulls
   the eye onto the corridor and the runner, and lets the top of the pane dissolve into the head
   bar the same way the vignette dissolves its sides — which is the "recessed screen, not a card"
   argument this panel is already built on. */
.feed__grade {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to bottom, rgba(8, 9, 12, 0.94) 0%, rgba(8, 9, 12, 0.74) 17%, rgba(8, 9, 12, 0.34) 35%, transparent 52%),
    radial-gradient(98% 84% at 50% 53%, transparent 28%, rgba(8, 9, 12, 0.86) 100%),
    linear-gradient(to top, rgba(214, 69, 37, 0.10) 0%, transparent 34%);
}

/* Converging lines and 1/z rungs, all aimed at the one vanishing point js/premise.js measures
   from. Survey data laid over the world, which is what makes the pane read as an instrument
   rather than as video with a frame around it. */
.feed__survey { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }

/* THE TRAIL. A claim-red lane growing along the road from the bottom of the frame up to her
   heels — the street turning red behind her. One clip-path polygon, redrawn off the run's own
   clock, narrowing toward the vanishing point for free. This is the mark that explains the
   whole game in one image, and it is the reason the section uses THIS plate and not another:
   a locked-off one-point perspective hands it to us with no camera solve.

   THE WEIGHT CAME DOWN FROM 0.58 TO 0.42 when the pane grew. The polygon is unchanged in the
   PICTURE — it still covers the same stretch of road — but that stretch is now magnified by the
   crop, so at full extension it is a 430x90 area instead of a 185x60 one, and at the old alpha
   it stopped reading as a lane on tarmac and started reading as a rug laid over it
   (shots/g2/premise/a5-d-end.png). Area and opacity trade against each other; the mark got four
   times the area, so it gives some of the opacity back. */
.feed__trail {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(to top, rgba(214, 69, 37, 0.42), rgba(214, 69, 37, 0.12));
  mix-blend-mode: hard-light;
  clip-path: polygon(28% 100%, 72% 100%, 72% 100%, 28% 100%);
}

/* THE RETICLE. She is dead centre for every frame of the plate, so this does not track — it
   only shrinks. Brackets plus a faint box: brackets alone at this size read as four unrelated
   ticks rather than as one lock. */
.feed__reticle {
  position: absolute;
  left: 50.5%;
  top: 64%;
  width: 9%;
  height: 49%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.feed__reticle::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid rgba(214, 69, 37, 0.20);
}
.feed__reticle i {
  position: absolute;
  width: 13px;
  height: 13px;
  border: 0 solid var(--claim);
}
.feed__reticle i:nth-child(1) { left: 0;  top: 0;    border-left-width: 1px; border-top-width: 1px; }
.feed__reticle i:nth-child(2) { right: 0; top: 0;    border-right-width: 1px; border-top-width: 1px; }
.feed__reticle i:nth-child(3) { left: 0;  bottom: 0; border-left-width: 1px; border-bottom-width: 1px; }
.feed__reticle i:nth-child(4) { right: 0; bottom: 0; border-right-width: 1px; border-bottom-width: 1px; }

/* The tag hangs off the reticle's right shoulder on a 1px leader, the way a real callout does. */
.feed__tag {
  position: absolute;
  left: calc(100% + 22px);
  top: -1px;
  white-space: nowrap;
  color: var(--bone);
  font-family: var(--font-mono);
  font-size: var(--t-label-sm);
  letter-spacing: var(--track-label-wide);
  text-transform: uppercase;
  line-height: 1.5;
  transition: opacity var(--dur-ui) linear;
}
.feed__tag::before {
  content: "";
  position: absolute;
  right: 100%;
  top: 0.6em;
  width: 22px;
  height: 1px;
  background: var(--claim);
}
.feed__tag span { display: block; color: var(--slate-2); }

/* Sector plates, pinned to the building blocks. */
.feed__plate {
  position: absolute;
  padding: 3px var(--s-2);
  background: rgba(8, 9, 12, 0.66);
  border-left: 2px solid var(--kind, var(--alliance));
  color: var(--bone-2);
  font-family: var(--font-mono);
  font-size: var(--t-label-sm);
  letter-spacing: var(--track-label-wide);
  text-transform: uppercase;
  white-space: nowrap;
}
/* 22%, not 5%. From 1280 up the undocked comms panel finishes about 90px over the picture's left
   edge, and at 5% this plate sat under it and read as a truncated label rather than as a plate.
   22% of the narrowest pane this section ever has above 1280 is 118px, which clears the widest
   overlap (94px at 1920) at every width in the band. Below 1280 the panel is docked and this is
   free to be anywhere; one position for both is simpler than a second breakpoint. */
.feed__plate--ally  { left: 22%; top: 27%; --kind: var(--alliance); }
.feed__plate--rival { right: 5%; top: 21%; --kind: var(--enemy); text-align: right; }

/* The hero's .hud-readout grammar, docked rather than floating. It sits over the picture where
   there is room and drops into the bezel where there is not — see the narrow block below. */
.feed__readout {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-2) var(--s-4) 10px;
  color: var(--slate-2);
  font-family: var(--font-mono);
  font-size: var(--t-label-sm);
  letter-spacing: var(--track-label-wide);
  text-transform: uppercase;
  background: linear-gradient(to top, rgba(8, 9, 12, 0.86), transparent);
}
.feed__readout b { font-weight: inherit; color: var(--bone); font-variant-numeric: tabular-nums; }
/* The section's one lit readout (v3 G2): the distance, because it is the live quantity. */
.feed__readout #premise-km { text-shadow: var(--g2-bone); }
.feed__readout .sp { margin-left: auto; }

/* The claim rail. A ring here would be the hero's ring a second time; a rail along the pane's
   own bottom edge is the same information carried by a different instrument. */
.feed__rail {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 0%;
  background: var(--claim);
  z-index: 4;
}

/* Ruling and scanlines, so the pane and the log share one texture. */
.feed__rule {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(to bottom, rgba(242, 238, 230, 0.030) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(to right,  rgba(242, 238, 230, 0.016) 0 1px, transparent 1px 48px),
    repeating-linear-gradient(to bottom, rgba(242, 238, 230, 0.016) 0 1px, transparent 1px 48px);
}

/* THE CLIP PLAYS ONCE AND HOLDS ON THIS. A run that finishes beats a run that loops, and it is
   also the cheapest answer to a real problem: the plate is 11.79s and does not loop — she is
   visibly smaller at the end than at the start, so any seam pops. Ending on the capture means
   there is no seam to solve. */
.feed__stamp {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 5;
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--claim);
  background: rgba(8, 9, 12, 0.72);
  color: var(--bone);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0;
  transition: opacity var(--dur-ui) linear;
}
.feed.is-captured .feed__stamp { opacity: 1; }
.feed.is-captured .feed__tag   { opacity: 0; }

/* The payoff: on capture the ally plate flips to claim red. js/premise.js changes the word. */
.feed.is-captured .feed__plate--ally { --kind: var(--claim); color: var(--bone); }

/* ---------------------------------------------------------------- the log, docked

   A real log moves, so this one does. That IS a deviation and it should be recorded as one: the
   brand brief says "never animate type", and a scrolling feed is animated type by definition.
   It was asked for deliberately, so what follows is the version that costs the least — a single
   transform on one element, no fade on the type, nothing that touches a glyph's own shape or
   opacity. The type is not animating; the surface it sits on is moving underneath the window,
   which is what a physical log printer does.

   THREE LINES, NOT FIVE OR EIGHT. Eight was a paragraph next to a picture rather than a caption
   under one. Three is what fits in one glance. The window is 3.5 rows: a part-row at each edge
   is what tells the eye the strip is MOVING rather than swapping.

   PACE. The track is not on a fixed CSS loop. js/premise.js translates it by the run's own
   progress, interpolated between the story's beats, so the log moves at the pace of the footage,
   lands each sentence on the thing it describes, and stops when she stops. A constant-speed loop
   is smooth but it is not a story — it puts each line under the eye at whatever moment the
   arithmetic happens to say. */
.monitor__log {
  /* One row is a message's line box plus its rule spacing. */
  --row: 27px;
  height: calc(3.5 * var(--row));
  position: relative;
  flex: none;
  overflow: hidden;

  /* DOCKED IS THE DEFAULT, and below 1280 it has to look docked while being a DOM sibling of the
     chassis rather than a child of it — see the note in index.html for why it moved out. The
     three sides plus the -1px are what close the seam: the log's own top border lands exactly on
     the chassis's bottom border, so the two paint as one line at the chassis's weight, and the
     sides and floor continue the bezel. Above 1280 all four are replaced by a real chamfered
     ring; see the block further down. */
  background: var(--ink-0);
  border: 1px solid rgba(63, 143, 78, 0.38);
  border-top-color: rgba(63, 143, 78, 0.24);
  margin-top: -1px;

  /* The window's own edges, softened just enough that a half-row reads as entering rather than
     as clipped. This is a mask on a moving surface, not a blur or a glow on the type — the
     glyphs themselves are never touched, which is the line css/hud.css draws. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 86%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 86%, transparent 100%);
}

/* Below 1280 .comms is nothing at all — a wrapper the grid needs above 1280 and that must not
   introduce a box below it. The head belongs to the standing panel only. */
.comms__head { display: none; }

/* ---- the two shares of the chassis height, at 1280 and up ----
   Declared HERE rather than beside the grid, because both selectors are one class deep and
   their base rules sit above: at equal specificity the later declaration wins, so an override
   placed before `.feed { flex: none }` and `.monitor__log { height: calc(3.5 * var(--row)) }`
   would lose silently.

   THE PICTURE TAKES THE SLACK, and it grows by CROPPING rather than by letterboxing: the video
   is object-fit: cover, so a taller pane keeps the full height of the plate and trims the sides.
   That is the right direction for this plate — a one-point perspective straight down a street
   with the runner dead centre, so a tighter horizontal crop is a tighter corridor and a bigger
   runner, which is what the reference's centre figure does with its own frame. She goes from
   182px tall to about 290px at 1440x900.

   `flex: 1 0 auto` — grow into the slack, NEVER shrink. Shrink past 16:9 and cover flips its
   crop axis to vertical, and every overlay percentage in js/premise.js is calibrated on the
   vertical being uncropped. The 600px floor on .monitor's height is what guarantees the slack
   is never negative; the two numbers are one decision.

   THE HORIZONTAL OVERLAYS ARE NOT CORRECTED HERE. They cannot be — the correction is a ratio of
   two measured lengths. js/premise.js reads the pane's real box and maps them; see KX. Change
   this rule and you must re-read that one. */
@media (min-width: 1280px) {
  /* A size container, so the pane can keep a 16:9 FLOOR expressed in its own chassis's width.
     `aspect-ratio: 16/9` cannot do that job here: on a flex item with `height: auto` Chrome
     treats the ratio-derived height as the used height and flex-grow never moves it — measured,
     the pane stayed at 355px inside a 700px chassis with 179px of free space beside it. So the
     ratio comes off and comes back as a min-height in container units: 56.25cqw is 9/16 of the
     chassis's inline size, which is the same number said in the one way flexbox will honour. */
  .monitor { container-type: inline-size; }

  /* THE FLEX ITEM IS .feed-wrap, NOT .feed. Easy to get wrong and it fails silently: the pane is
     one level down inside the wrap that lets the readout sit over the picture, so `flex-grow` on
     .feed grows nothing and the pane stays at its floor with the free space piling up under the
     log. Measured that way once — 539px of content in a 702px chassis. The wrap takes the
     growth; the pane fills the wrap. */
  .feed-wrap {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
  }

  /* `height: 100%` was tried here and is a trap. It works while the chassis has a fixed height
     and silently collapses the picture to ZERO the moment it does not — a percentage height
     against an auto-height parent resolves to auto, and with the aspect ratio also off there is
     no content to fall back on. That is not hypothetical: the reduced-motion rule below hands
     the chassis `height: auto`, and the panel measured 44 + 372 of empty ground + 363 of log
     with no video in it at all. The wrap is a flex column and the pane is a flex item in it, so
     the pane's size comes from growth and a floor, never from a percentage. */
  .feed {
    flex: 1 1 auto;
    aspect-ratio: auto;
    min-height: 56.25cqw;
  }

  /* ---- THE LOG UNDOCKS AND BECOMES AN ORBITING PANEL --------------------------------------
     The one object that crosses the divide. It is placed in the copy column and then pushed back
     out of it by a negative right margin, so it starts inside the left field, runs across the
     gutter and finishes 92.59px over the picture at 1440 (87.78 at 1366, 82.19 at 1280, 93.00 at
     1920). That overlap is the whole point: a critic
     comparing this section with the reference's said ours was "a card in the right half and a
     paragraph in the left half, meeting nowhere". Now one instrument is in both halves.

     THE NEGATIVE MARGIN IS SAFE HERE AND WAS NOT LAST TIME. Round 1 lost a cycle to a negative
     margin on an AUTO-WIDTH BLOCK IN NORMAL FLOW, where the containing block is the viewport and
     `width = containing block - marginLeft - marginRight` widens the element past the frame, so
     the page gains a horizontal scrollbar. This element is a GRID ITEM, and its containing block
     is its own grid area — a 638px column at 1440, not the 1440 — so the same arithmetic makes it
     803.81px wide and stops its right edge at 871.20, which is 569px inside the frame. Round 3
     changed which edge is stated (see below) and did not change this: measured right edge 871.20
     of 1440, 826.42 of 1366, 774.39 of 1280 and 1294.00 of 1920, with
     `document.documentElement.scrollWidth == innerWidth` verified at 390, 1024, 1279, 1280, 1366,
     1440 and 1920.

     THE PANEL'S LEFT EDGE IS THE COPY COLUMN'S LEFT EDGE, which is the headline's and the
     paragraph's. Round 2 anchored the RIGHT edge instead (`justify-self: end` plus an explicit
     width) and let the left fall wherever the arithmetic put it: 237.61px at 1440, against 67.39
     for the headline and the body. The blind critic that picked this composition named exactly
     that as its one embarrassment — "its left column has three different left edges … the panel's
     indent matches no grid line on the page and reads as a float that got dragged there". It was
     right. An indent that answers to nothing is an accident, and the page's own page margin is the
     only line in this field that every other object already stands on.

     So the alignment is now STRETCH, not `end`, and the width is not stated at all: a stretched
     grid item's used width is its area minus its margins, so the left edge lands on the column
     line to the pixel and the negative right margin still carries the right edge the same ~90px
     over the picture. One declaration does both jobs, and neither edge can drift from the other's
     rounding. Measured at 1440x900: headline 67.39, body 67.39, panel 67.39; panel right 871.20
     against the picture's left edge at 778.61, so 92.59px of overlap — the same overlap round 2
     had, because the right edge did not move.

     The panel is wider for it (803.81 at 1440 against 633.59). That is the trade and it is the
     right way round: the log is a console, its rows are left-set, and the slack falls on the right
     where the newest row's blip already sits over the picture. A shared edge is visible from
     across the room; 170px of empty console is not.

     THE WIDTH FLOOR IS 500px AND IT IS ARITHMETIC. The character budget in index.html is 56
     mono characters at --t-label-sm, which is 363px of message, plus the timestamp column (45),
     two 8px gaps, the blip (5), the entry's rule and indent (14) and the list's own 32px of
     padding: 475. Below that the longest entry wraps and every row after it sits half a line out
     of the track's measured geometry. Stretch clears it everywhere in the band by construction —
     the narrowest this panel ever is now is the copy column at 1280 (563.6px) plus its overhang
     (147.2), i.e. 710.8 — so the floor is recorded rather than declared.

     4.5 ROWS rather than 3.5 once there is room for it. It keeps the part-row at each edge that
     tells the eye the strip is moving, and keeps the window well under the track's own ~348px so
     the log still travels a real distance over the run rather than sitting almost still. */
  .comms {
    grid-column: 1;
    grid-row: 1;
    align-self: end;
    justify-self: stretch;
    margin: 0 calc(-1 * clamp(110px, 11.5vw, 190px)) clamp(28px, 5svh, 64px) 0;
    z-index: 1;
    position: relative;
    /* 1px of padding is what keeps the children off the ring. Without it the log window covers
       the left, right and bottom of the outline and only the head's own strip shows the line. */
    padding: 1px;

    /* A TRUE CHAMFERED RING, built the way css/surface.css builds .panel: the outline is the
       element's own background and the fill floats 1px inside it on a pseudo-element. A border
       plus a clip-path does not work — the clip slices the border off across the diagonal and
       leaves the corner open, which is the exact tell that separates a drawn chamfer from a cut
       one. 13.5px bottom-right is the bar's signature and this section carried none of it; the
       corner sits over the picture, which is where it reads.

       The line is heavier than the chassis's own 0.38, because this one has to hold its shape
       against a photograph as well as against the ground.

       .registered's offset outline is NOT used here even though this is exactly the kind of
       panel that wears one on the reference. clip-path clips descendants, pseudo-elements
       included, so an outline inset by -14px would be cut away entirely. The chamfer is the
       louder of the two signatures, so the chamfer is the one that is kept. */
    background-color: rgba(63, 143, 78, 0.62);
    clip-path: polygon(
      0 0,
      100% 0,
      100% calc(100% - 13.5px),
      calc(100% - 13.5px) 100%,
      0 100%
    );
  }

  /* The fill, 1px inside the ring. The inner cut is 13.5 - 2 + sqrt(2) = 12.914px, not 13.5: an
     inner box inset 1px all round needs that value for the gap across the diagonal to measure the
     same 1px as the gap along the four straight edges. Same arithmetic as .panel::before in
     css/surface.css. */
  .comms::before {
    content: "";
    position: absolute;
    inset: 1px;
    z-index: 0;
    background-color: var(--ink-0);
    clip-path: polygon(
      0 0,
      100% 0,
      100% calc(100% - 12.914px),
      calc(100% - 12.914px) 100%,
      0 100%
    );
  }

  /* The head, in the chassis's own grammar so the two objects read as one instrument family
     rather than as a panel and a slab. */
  .comms__head {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
    margin: 0;
    padding: var(--s-3) var(--s-4);
    color: var(--slate);
    border-bottom: 1px solid rgba(63, 143, 78, 0.24);
  }
  .comms__head-id { color: var(--bone); }

  .monitor__log {
    position: relative;
    z-index: 1;
    height: calc(4.5 * var(--row));
    margin: 0;
    border: 0;
    background: none;

    /* The window's soft edges move off the element and onto ::after, below. A mask on the
       element masks its background too, and a 1px ring that dissolves at the top and bottom
       reads as a rendering fault rather than as a fade. */
    -webkit-mask-image: none;
            mask-image: none;
  }

  /* The edge fade. It is what keeps the part-row at each end of the window reading as a line
     entering rather than as a line guillotined by a border. */
  .monitor__log::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background: linear-gradient(
      to bottom,
      rgb(8, 9, 12) 0,
      rgba(8, 9, 12, 0) 6%,
      rgba(8, 9, 12, 0) 94%,
      rgb(8, 9, 12) 100%
    );
  }
}

/* THE OVERLAP IS GIVEN UP UNDER REDUCED MOTION. With all twelve entries open the panel is 405px
   rather than 165px and, bottom-aligned in its row, its top edge lands within a pixel or two of
   the chassis's own top — so the 90px that normally runs over the picture runs over the FEED 01
   head bar instead and overprints it (measured at 1440x900). Nothing else has to move: dropping
   the negative margin stops the panel on the column's right edge, 73px clear of the chassis. The
   composition still reads — a panel in the left field above the copy — it simply stops crossing
   the divide for the readers who cannot see it cross.
   Declared after the block above so that at equal specificity this margin wins. */
@media (min-width: 1280px) and (prefers-reduced-motion: reduce) {
  .comms { margin-right: 0; }
}

/* Reduced motion opens the log window to all twelve lines (css/motion.css) — about 348px, where
   the moving version is 122. When the log was inside the chassis that was a clipping problem and
   the chassis had to give up its fixed height to solve it; now that the log is its own panel in
   its own grid row, the row's automatic minimum absorbs the extra height and the section, which
   is min-height and not height, grows if it has to. Nothing is clipped and nothing needs an
   override, which is why the rule that used to live here is gone. */

/* Ruling and scanlines ride on an overlay, not on the element: .hud-corners--left already owns
   background-image for the brackets, and a second layer there would knock them out. */
.monitor__log::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(to bottom, rgba(242, 238, 230, 0.022) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(to right,  rgba(242, 238, 230, 0.018) 0 1px, transparent 1px 48px),
    repeating-linear-gradient(to bottom, rgba(242, 238, 230, 0.018) 0 1px, transparent 1px 48px);
}

/* The track is built ONCE and then only translated. An earlier version rebuilt the list's
   innerHTML every time a line landed, which threw away and re-made every node mid-motion. */
.comms__track {
  position: relative;
  z-index: 1;
  padding-block: var(--s-3);
  will-change: transform;
}

.comms__feed {
  list-style: none;
  margin: 0;
  padding: 0 var(--s-4);
}

/* Kind is a colour, set once per row and read by the rule and the blip. Defaults to slate so a
   row with no data-kind is quiet rather than broken. */
.comms__entry {
  --kind: var(--slate);
  --kind-text: var(--kind);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--s-2);
  padding-left: var(--s-3);
  margin-bottom: var(--s-3);
  border-left: 2px solid var(--kind);
}

.comms__entry[data-kind="ally"]  { --kind: var(--alliance); }
.comms__entry[data-kind="claim"] { --kind: var(--claim); --kind-text: var(--claim-text); }
.comms__entry[data-kind="info"]  { --kind: var(--slate-2); }

/* Rival is the one kind whose brand hex cannot be set as type here. On this ground --enemy
   measures 3.64:1 — fine for the 2px rule, which is judged at 3:1, and a fail for a 12.6px
   message at 4.5:1. #6086A9 is the SAME hue and chroma lifted one rung in lightness to 5.31:1.
   The rule keeps the true --enemy so the board's colour is still the one being pointed at; only
   the type takes the lifted rung. This is the same move css/surface.css makes when Claim red has
   to survive on the paper ground, and it is why the lift is scoped here rather than added to the
   palette: --enemy has not changed, this ground just needs more of it. The other three kinds
   clear 4.5:1 as authored (alliance 5.09, claim 4.59, slate-2 7.76) and are untouched. */
.comms__entry[data-kind="rival"] { --kind: var(--enemy); --kind-text: var(--enemy-lit); }

/* v3 text tints (css/tokens.css). Claim's own red is 4.30:1 on the cool ground, so its type
   takes the text tint; the lifted #6086A9 above became v3's Enemy light, 7.81:1. The mono
   tracking is held low here because the 39-character budget above is arithmetic, not taste. */
.comms__msg,
.comms__time { letter-spacing: 0.04em; }

.comms__time {
  font-family: var(--font-mono);
  font-size: var(--t-label-sm);
  font-variant-numeric: tabular-nums;   /* so the bracketed stamps form a true column */
  color: var(--slate-2);
  white-space: nowrap;
}

/* THE COLOUR IS ON THE RULE AS WELL AS THE TYPE, and the type takes the lifted rung above.
   The 2px rule is a non-text mark judged at 3:1, which all four kinds clear, so the coding
   survives at full strength. */
/* --t-label-sm is what buys the width. See THE CHARACTER BUDGET note in index.html: the
   narrowest column this panel ever has is a 390px phone, which leaves 39 advances. */
.comms__msg {
  font-family: var(--font-mono);
  font-size: var(--t-label-sm);
  line-height: 1.35;
  text-transform: uppercase;
  color: var(--kind-text);
}

/* The blip. Sits in the row's third column so it never reflows the message, and takes the row's
   own kind colour rather than a fixed green — the newest line is not always good news. It is
   hidden on every row but the newest, which js/premise.js moves as the story advances. */
.comms__blip {
  width: 5px;
  height: 5px;
  align-self: center;
  background: var(--kind);
  animation: live-pulse var(--dur-ambient) ease-in-out infinite;
}

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

   16:9 inside a 390px column is a 190px strip, and at that height the pane cannot carry
   annotation AND picture. So it stops trying: the plates and the callout go, the readout drops
   out of the picture into its own strip in the bezel, and what is left is the two marks that
   actually say something — the lock on her and the ground turning red behind her. The log is
   unchanged, because a log is the one thing a narrow column is good at. */
@media (max-width: 720px) {
  .feed__plate,
  .feed__tag { display: none; }

  .feed__readout {
    position: static;
    background: none;
    border-top: 1px solid rgba(63, 143, 78, 0.24);
    padding: var(--s-3) var(--s-4);
    gap: var(--s-3);
    flex-wrap: wrap;
  }
  .feed__readout .sp { margin-left: auto; }

  /* The stamp is the payoff and it has to survive the narrow column, so it comes down a size
     rather than staying big and touching both edges. */
  .feed__stamp { font-size: var(--t-label-sm); padding: 6px var(--s-3); }
}

/* Under 480 even the stripped readout wraps, and cadence is the one number of the three that is
   flavour rather than fact. It goes; the claim distance stays. */
@media (max-width: 480px) {
  .feed__readout .sp { display: none; }
}


/* ---------------------------------------------------------------- founding commanders

   A CLASSIFIED DOSSIER OPEN ON A MONITOR. This replaced .proof, which was four numbers on a
   paper sheet, and the swap changed the section's job as well as its look: the numbers proved
   the map, and the page already proves the map twice above this point. What was missing before
   the ask was a reason to enlist now.

   THE SECTION IS DARK. .proof ran the light-side ladder, so with .travel and .get either side of
   it the page had three consecutive paper sections. Dark here restores the alternation the
   inversion was introduced for — see css/surface.css section 0.

   THE PANEL TAKES BRACKETS, NOT THE CHAMFER, and that is the one rule in this file worth
   defending. css/surface.css argues the split: brackets say "you are looking THROUGH this", a
   cut corner says "this is a sheet of material you are looking AT". A dossier open on a screen
   is the first thing, so it is framed the way the hero and the travel viewport are framed —
   plus a real 1px border, because brackets alone read as a viewfinder and a file needs an edge.

   THE BORDER IS CLAIM RED, which is the only place on the site a full panel outline carries the
   accent. It is affordable because it is the section's ONE red field: the counter, the title and
   the perk text are all on the bone ladder, and the other two marks in here are alliance green
   and the lifted enemy blue. It is the same construction the comms log uses forty lines up —
   a 38% alliance-green border with matching brackets — in the other territory colour, so the
   page now has two instrument panels drawn to one rule rather than one panel and one exception.

   THE GROUND IS --ink-0, NOT THE --ink-2 PLANE, for the reason the comms log states: the accent
   marks in here are all mid-luminance, and on --ink-2 they measure 3.3-4.1:1 against 4.6-5.1:1
   on the well. A dossier is a screen recessed into the page, not a card standing on it, which is
   what the well value means everywhere else on this site. The panel then lifts back toward its
   own centre with the same warm radial .carto uses on the board, so it is a LIT surface rather
   than a hole — a flat near-black rectangle is the one thing a monitor never looks like. */

.founders {
  padding-block: var(--s-24);
  border-top: 1px solid var(--hairline-strong);
}

/* Room above the panel for the folder tab, which hangs off its top edge. */
/* minmax(0, 1fr), not the implicit auto column: a grid item's min-width is auto, so the dossier's
   widest unbreakable line widened the column past a 390px phone (to 406px) and took the whole
   document to 425px wide. That also shifted every position: fixed element on the page, which is
   how it was found — section 002's pinned header sat off the edge of the screen. */
.founders__inner { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-16); }

.dossier {
  /* --claim-edge is the instrument-weight red, defined in css/tokens.css and shared with the
     four beat plates in css/travel.css. It is the sibling of the comms log's
     `rgba(63, 143, 78, 0.38)` — same construction, other territory colour. Claim red carries less
     luminance than alliance green, so it takes a little more alpha to land on the same rung.
     Kept behind a local alias so the panel's edge can be retuned in one place if it ever needs
     to differ from the plates'. */
  --dossier-edge: var(--claim-edge);
  --hud-line: var(--claim);
  --hud-arm: 13px;

  /* NOT overflow:hidden, unlike the comms log. The folder tab is a child positioned ABOVE the
     panel's top edge, and clipping the panel clips the tab off the page entirely. Nothing else in
     here leaves the box: the overlay is inset 0 and the stamp rotates about its own right edge. */
  position: relative;
  margin-top: 26px;                       /* the tab's own height, so it does not overlap the label */
  border: 1px solid var(--dossier-edge);
  background-color: var(--ink-0);         /* THE WELL. A screen recessed into the page. */
  /* The lit middle. Same warm radial css/surface.css puts on .hero__map and .carto — the site's
     one and only way of saying "this surface is a display" — but at 5% rather than their 8.5%,
     and the difference is the ground under it. The board's copy of this sits on --ink-2 (L* 15.9)
     where a 8.5% wash is a small proportional lift; on the well (L* 1.5) the same wash is most of
     the panel's luminance, and rendered at 8.5% it turned the whole dossier warm brown. The brief
     asks for near-black with a texture, not for a red panel. */
  background-image: radial-gradient(
    92% 136% at 50% 38%,
    rgba(214, 69, 37, 0.05) 0%,
    transparent 72%
  );
  /* The screen cue proper: a 1px bone wash along the top inside edge, which is the highlight any
     real bezel throws, and a soft darkening at the foot so the panel sits INTO the page rather
     than on it. Inset only — this page casts no outer shadow anywhere. */
  box-shadow:
    inset 0 1px 0 rgba(242, 238, 230, 0.055),
    inset 0 -40px 60px -40px rgba(0, 0, 0, 0.85);
}

/* ---- the overlay: grain, scanlines, graticule ----
   The comms log's exact recipe — 1px-on-2px-off scanlines under a 32px rule, at 2.2% and 3.0% —
   plus grain, which the log does not need and this panel does. The note at --grain in
   css/surface.css says why: a near-black fill has nowhere to put a 2% gradient, and the radial
   lift above IS a 2% gradient over 900px of panel, so without noise to dither it back into a
   continuous field it posterises into visible rings. The log is flat and has no such problem.

   THE PITCH IS 48px, NOT the log's 32, and the alpha is 1.8% rather than 3.0%. Both are the same
   correction and the reason is width: the log is a 480px panel carrying 15 rules, this one is
   1290px and at the log's pitch it carried 40. Rendered, that stopped reading as a faint ruled
   ground and started reading as a TABLE with the copy sitting in its cells. 48px is --s-12, so
   the ruling is still on the space scale and still on the same lattice as the padding.

   Masked to a soft ellipse so the ruling fades out at the edges, where the foot shadow is doing
   its work and a hard grid line crossing it would read as a seam. */
.dossier::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    var(--grain),
    repeating-linear-gradient(to bottom, rgba(242, 238, 230, 0.022) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(to right,  rgba(242, 238, 230, 0.030) 0 1px, transparent 1px 32px),
    repeating-linear-gradient(to bottom, rgba(242, 238, 230, 0.030) 0 1px, transparent 1px 32px);
  -webkit-mask-image: radial-gradient(118% 132% at 50% 42%, #000 0%, #000 54%, transparent 97%);
  mask-image: radial-gradient(118% 132% at 50% 42%, #000 0%, #000 54%, transparent 97%);
}

/* Everything real sits above the overlay. Same idiom .panel uses in css/surface.css. */
.dossier > * { position: relative; z-index: 1; }

/* ---- corner brackets ----
   TWO corners, not four, which is css/hud.css's own rule for this case: "for panels that already
   carry a full border and would read as over-drawn with four... enough to say instrument, not
   enough to say sticker".

   THEY MARK THE BODY, NOT THE PANEL. Built on the panel they were inset 10px from its border,
   which put the top-left arm inside the filled title bar — a red tick floating in a strip, not a
   mark on a corner. The body is the part of a file you look THROUGH, which is what a bracket is
   for, and it also gives the arms a plain ground to be drawn on at every width.

   8px in rather than flush: flush, the arms merge into the edge they are drawn on and read as a
   thicker corner. Full Claim red — these are the only thing in the panel wearing the accent
   undiluted, which is what keeps them legible beside a border at 46%. */
.dossier__marks {
  position: absolute;
  inset: 8px;
  z-index: 2;
  pointer-events: none;
}

/* ---- the folder tab ----
   Sits ON the top border and overlaps it by 1px, so the two shapes read as one object rather
   than as a caption floating above a box. No bottom border for the same reason: the panel's own
   top edge is the tab's floor. */
.dossier__tab {
  position: absolute;
  bottom: calc(100% - 1px);
  left: var(--s-8);
  z-index: 2;
  padding: var(--s-2) var(--s-4) 5px;
  border: 1px solid var(--dossier-edge);
  border-bottom: 0;
  background: var(--ink-0);
  color: var(--slate-2);
}

/* ---- the title bar ----
   --ink-2 rather than the --ink-4 .window__bar takes, and the difference is which rung it is
   standing ON. A window's fill is --ink-2, so its bar needs --ink-4 to be a step above it. This
   panel's fill is --ink-0, so --ink-2 is already dL* 14.5 up — the same perceptual step, one rung
   lower down the same ladder. --ink-4 here would be dL* 29.6 and the bar would read as a
   different, lit object pasted onto a dark one. */
.dossier__bar {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-6);
  background: var(--ink-2);
  border-bottom: 1px solid var(--dossier-edge);
}

/* Alliance green, not the accent: this mark means "intake is running", which is the alliance's
   colour in the game's own key and the same meaning the comms log's live mark carries. */
/* 6px, which is .comms__live's size and not the 5px of the section-label square: this is a live
   mark in a panel head, the same object in the same position, and a third size for it would be a
   third dialect of "this is live" on one page. */
.dossier__live {
  flex: none;
  display: block;
  width: 6px;
  height: 6px;
  background: var(--alliance);
  animation: live-pulse var(--dur-ambient) ease-in-out infinite;
}

/* An h2 set at --t-data in mono, which is smaller than any other h2 on the page and is meant to
   be: the section's large object is the counter, not its title. A 57.6px headline here would be
   a second one arguing with the enlist headline two screens down. */
.dossier__title {
  flex: 1;
  min-width: 0;
  margin: 0;
  /* Archivo Black, per Brand Guidelines v3: unit and place names set in the brand face. */
  font-family: var(--font-brand);
  font-size: 1.625rem;
  font-weight: 900;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  color: var(--bone);
}

.dossier__ref { color: var(--slate); white-space: nowrap; }

/* ---- the body ----
   Two columns: the brief and the grant. The brief is not told a width — .dossier__intro carries
   its own measure — so the split is a ratio and the copy sets to the same measure it would set
   to anywhere else on the page. */
.dossier__body {
  position: relative;        /* the corner brackets hang off this, not off the panel */
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: var(--s-16);
  padding: var(--s-12) var(--s-8);
}

/* space-between, not start, and it is the fix for a void rather than a preference. The grant
   column is the taller of the two — a label plus three ruled rows plus the stamp — so with the
   brief packed to the top the panel carried 70-odd pixels of empty ground under the counter, at
   the exact point where the eye leaves the number. Distributed, the intro holds the top of the
   file and the reading sits on its floor, which is where a reading belongs. --s-8 is the FLOOR,
   not the gap: the two blocks never come closer than that if the columns ever match in height. */
.dossier__brief {
  display: grid;
  align-content: space-between;
  gap: var(--s-8);
}

.dossier__intro {
  color: var(--bone-2);
  max-width: 42ch;
}

/* ---- the counter ----
   Ruled above rather than boxed. The number is the one thing in the panel allowed to be large,
   and it is mono at --t-title so it reads as an instrument taking a reading, not as a headline
   making a claim. Tabular figures because the digits are a count and a count should not shift
   its own comma when it changes. */
.dossier__count {
  display: grid;
  gap: var(--s-2);
  margin: 0;
  padding-top: var(--s-6);
  border-top: 1px solid var(--dossier-edge);
}

.dossier__num {
  font-family: var(--font-mono);
  font-size: var(--t-title);
  font-weight: 500;
  line-height: 0.95;
  /* The display tracking, which is the only negative value on the site and the only one that is
     allowed to be. Mono at 28.8px sets a comma on a full advance, so "3,842" renders with visible
     air on both sides of the comma and reads as "3 , 842"; --track-display pulls it back to one
     number without touching the figures' own tabular grid. */
  letter-spacing: var(--track-display);
  font-variant-numeric: tabular-nums;
  color: var(--bone);
}

.dossier__countlabel { color: var(--slate-2); }

/* ---- the grant ---- */
.dossier__grant {
  display: grid;
  align-content: start;
  gap: var(--s-4);
}

.dossier__grantlabel { color: var(--slate); }

.perks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-4);
}

/* Kind is one attribute per row and the icon reads it, the same construction .comms__entry uses.
   Defaults to slate so a perk with no data-kind is quiet rather than broken. */
.perk {
  --kind: var(--slate-2);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--s-3);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--hairline);
}

.perk:last-of-type { border-bottom: 0; padding-bottom: 0; }

.perk[data-kind="claim"] { --kind: var(--claim-lit); }
.perk[data-kind="ally"]  { --kind: var(--alliance-lit); }
/* The same lift the comms log applies to its rival type, and for a narrower reason here. On the
   well the true --enemy measures 3.64:1, which clears the 3:1 a non-text mark is judged at — but
   the log spends it on a 2px rule and this spends it on a 1.25px stroke, and a hairline mark at
   the bare minimum disappears on a low-end panel. #6086A9 is the identical hue one rung up, at
   5.31:1. The palette has not changed; a thinner mark just needs more of it. */
.perk[data-kind="rival"] { --kind: var(--enemy-lit); }

/* A 6px square in the perk's signal colour, v3's wire-row mark, in place of the stock shield,
   clock and map icons it replaced. */
.perk__mark {
  display: block;
  width: 6px;
  height: 6px;
  margin-top: 0.55em;
  background: var(--kind);
}

.perk__text { color: var(--bone-2); line-height: 1.35; }

/* ---- the stamp ----
   Hollow and rotated, at 62% of Claim red, because a stamp is ink that has already been pressed
   once — a solid one at full strength would be the loudest object in a section whose job is to
   support the CTA below it. In flow at the end of the grant column rather than absolutely
   placed, so it cannot land on top of a perk at an awkward width. */
.dossier__stamp {
  justify-self: end;
  margin-top: var(--s-4);
  padding: 5px var(--s-3);
  border: 1px solid rgba(214, 69, 37, 0.42);
  font-family: var(--font-mono);
  font-size: var(--t-label-sm);
  text-transform: uppercase;
  letter-spacing: var(--track-label-wide);
  /* Not --claim itself and not --signal: a stamp is ink that has already been pressed once, so
     the red is pulled toward the midtone rung. Lands at 3.9:1 on the well — above the 3:1 a
     decorative mark is judged at, and deliberately below the counter beside it, which is the
     thing in this panel that is actually meant to be read first. */
  color: #B0523A;
  transform: rotate(-6deg);
  transform-origin: 100% 50%;
}

/* ---- the file footer ----
   Three fields on the raised rung, which closes the panel the way the bar opens it. This is the
   last of the file cues and it is the one carrying real information: the realm, the clearance,
   and whether intake is running. */
.dossier__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-8);
  padding: var(--s-3) var(--s-6);
  background: var(--ink-2);
  border-top: 1px solid var(--dossier-edge);
}

.dossier__field {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
}

.dossier__field .label { color: var(--slate); }

.dossier__value {
  font-size: var(--t-label);
  text-transform: uppercase;
  color: var(--bone-2);
}

/* The one field that is a state rather than a coordinate carries the colour — the same rule the
   hero readout follows in css/surface.css, and green because intake being open is good news. */
.dossier__value--open { color: var(--alliance); }

/* ---------------------------------------------------------------- footer wordmark

   The reference closes on its name set edge to edge in its accent — see shots/ref/home-d-02.png,
   where INVERSA runs the full width and is the last thing on the page.

   SVG rather than a <p>, and the reason is the device itself: the word has to touch BOTH margins
   at every viewport width, and a font-size in vw cannot promise that. It only approximates, and
   the approximation is wrong for any word whose character widths differ from the one it was
   tuned against — measured at 15.2vw, DOMINIA left roughly 230px of slack on each side at 1440.
   `textLength` with `lengthAdjust="spacingAndGlyphs"` makes the browser fit the word to an exact
   measure, so the fill is guaranteed rather than eyeballed, and it survives changing the word. */
.footer__word {
  display: block;
  width: 100%;
  height: auto;
  padding-block: var(--s-8) var(--s-2);
  overflow: visible;
}

.footer__word text {
  font-family: var(--font-brand);
  font-weight: 900;          /* Archivo Black. The brand brief's wordmark weight, not the page's. */
  font-size: 128px;          /* the viewBox's own units; the SVG scales it to the page */
  /* Bone, not the accent. The reference closes on its name in its accent colour and this used to
     copy that, but Archivo Black at full measure is roughly 4% of the viewport in solid ink — at
     that area the accent stops being a signal and becomes a field, which is the "loud" reading.
     Bone puts the word on the type ladder where a sign-off belongs and leaves the square as the
     only coloured thing in the footer, which is also what the brand's one-red-element rule
     wants. The colour did not leave the lockup; it concentrated into the mark that carries it. */
  fill: var(--bone);
  /* Tracking is left to lengthAdjust, which distributes the fit across letterspacing AND glyph
     width. A letter-spacing here would fight it. */
}

/* var(--claim), NOT var(--signal), and the distinction matters even now that the two resolve to
   the same hex: chrome.css rule 1 is that the wordmark square is always Claim red and is never
   recoloured. Pointing it at --signal would silently recolour it the next time the accent moves,
   which it has done twice. */
.footer__word-square { fill: var(--claim); }

/* Clipped to the page rather than allowed to cause a scrollbar: at some widths the display
   face's side bearings push a hair past the margin. */
.footer { overflow: hidden; }


/* ================================================================ 003 · why play
 *
 * The page's second paper section. It was also, for one build, the only picture on the site with
 * nothing drawn over the top — and that is no longer true, so the note that used to say so has
 * been rewritten rather than left standing. What replaced it is not the same chrome section 001
 * carries; see the telemetry block below for what it does carry and why.
 *
 * THE SECTION IS PAINTED BY css/surface.css, not here. It carries `.invert`, and that file's own
 * note is emphatic about the trap: a section that redefines the light-side tokens without also
 * painting a ground shows the dark body through and then sets near-black type on it. The note
 * describes this happening twice already (.get, then .proof). Adding a third list to keep in sync
 * would have been the same bug waiting, so `.invert` itself now paints — see section 0 there.
 */

.why {
  padding-block: var(--s-24);
  /* No border-top. Every other section on the page carries one because it is dividing two
     surfaces of the same value; here the ground changes from near-black to bone at the seam,
     which is a harder edge than any hairline can draw, and a rule on top of it reads as a
     leftover. Same reason .get carries a border and this does not: .get's inversion arrives off
     the dossier's own panel, so the seam there is panel-to-paper rather than page-to-paper. */
}

.why__inner {
  /* The columns are grid.css's job. Centred rather than top-aligned: the copy block is about
     half the plate's height, and hung from the top it leaves a void under it that reads as a
     missing third element. */
  align-items: center;
  row-gap: var(--s-12);
}

/* ---- the plate ----
   Media, so it takes --chamfer-lg (24px) rather than the 12px panels use — css/surface.css
   splits those two on purpose. `.edge` is the geometry-only cut: no fill and no outline, which
   is right here because the clip IS the surface and a hairline round a photograph on paper reads
   as a border rather than as an edge. */
.why__plate {
  --chamfer: var(--chamfer-lg);
  clip-path: polygon(
    0 0,
    100% 0,
    100% calc(100% - var(--chamfer)),
    calc(100% - var(--chamfer)) 100%,
    0 100%
  );
  /* 4:3, and it is a compromise struck against the source rather than a shape chosen freely.
     The clip is 752x416 — 1.81:1 — so any upright box is a centre crop of a landscape frame, and
     the two costs run against each other: the taller the box, the more of the street is thrown
     away, and the harder the remaining pixels are upscaled. Measured at 1440, where the plate is
     a six-column 637px cell: at 5:4 the box is 510px tall, keeps 69% of the frame's width and
     upscales 1.22x; at 4:3 it is 478 tall, keeps 74% and upscales 1.15x; at 3:2 it is 425 tall,
     keeps 83% and upscales 1.02x. 4:3 is the tallest of the three that still holds both jambs of
     the doorway, which is the composition — the shot is a man framed BY a doorway, and cropped
     past the jambs it is just a man. */
  aspect-ratio: 4 / 3;
  margin: 0;
  overflow: hidden;
  background-color: var(--ink-3);   /* what shows for the instant before the poster paints */
  /* The containing block for .why__hud. Also what makes `overflow: hidden` above the clip's
     crop AND the alert's slide-in — the card arrives from six pixels above its resting place and
     would otherwise ride over the paper. */
  position: relative;
}

.why__clip {
  display: block;
  width: 100%;
  height: 100%;
  /* `cover` decides the crop; the position decides which part survives it. 56%, not centre: the
     doorway and the figure sit right of middle for the whole clip — he starts at about 64% of
     the frame and walks out to 61% — so a centred crop cuts the open door off its own right
     jamb. Probed at 4:3 across frames 1, 120, 240 and 355. */
  object-fit: cover;
  object-position: 56% 50%;
  /* The plate is the one ungraded image on the site, so this is the one place a filter would be
     a lie. Nothing is applied. */
}


/* ---- the telemetry ----
 *
 * Section 003 was, for one build, the only picture on the site with nothing drawn over it, and
 * this file argued for that at length. The argument is superseded rather than forgotten: the
 * section now has a story to tell — a notification lands, and he goes — and a story that turns
 * on a notification cannot be told without showing one.
 *
 * IT IS SECTION 001'S VOCABULARY, DELIBERATELY. A feed bar naming what you are looking at,
 * plates on the picture naming the ground and the people on it, a readout along the bottom
 * carrying live numbers. Reusing the grammar is the point rather than a shortcut: a reader who
 * has scrolled past the premise monitor recognises this instantly as the same instrument pointed
 * at a different moment, and recognition is the whole job of a landing page's second use of a
 * device.
 *
 * An earlier pass built this as a small in-world card positioned to read as the screen of the
 * phone he is holding. Recorded because the reasoning generalises: an overlay whose meaning
 * depends on the reader first working out WHERE it is supposed to live has been given a puzzle
 * instead of a sentence. The card stays — it is still an object in the scene rather than a
 * browser notification — but it is sized and placed to be read as the game's own chrome, not
 * mistaken for a phone display.
 *
 * PLACEMENT IS THE LEFT HALF for the alert and the RIGHT for the rival plate, and both are
 * chosen off the plate rather than by taste. For the first six seconds the left of frame is
 * unlit interior wall — the darkest, emptiest region in the clip — and it stays dark after he
 * leaves, because the door jamb takes the same edge. The right half is the doorway: the only lit
 * ground in the picture, and the only place a dark plate has something brighter than itself to
 * sit on. Section 001 splits its own two plates the same way and for the same reason.
 */

.why__hud {
  position: absolute;
  inset: 0;
  z-index: 2;
  /* Illustration, not furniture — nothing here is clickable, and a transparent box over a video
     should not be able to swallow a pointer. */
  pointer-events: none;
  font-family: var(--font-mono);
  letter-spacing: var(--track-label-wide);
  text-transform: uppercase;
}

/* ---- the feed bar ----
   .monitor__head from section 001, laid OVER the picture rather than above it, so the paper
   section's geometry is untouched. The gradient is what gives the type a ground; a solid strip
   would put a fifth rectangle between the reader and the picture. */
.why__feed-bar {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin: 0;
  padding: var(--s-3) var(--s-4) var(--s-6);
  font-size: var(--t-label-sm);
  color: var(--slate);
  background: linear-gradient(to bottom, rgba(8, 9, 12, 0.80), transparent);
}

.why__feed-id { color: var(--bone-2); }
.why__feed-state { display: flex; align-items: center; gap: var(--s-2); margin-left: auto; }

/* The live square, on the site's ambient beat: `live-pulse` in css/motion.css, shared by every
   live mark on the page. */
.why__live,
.why__alert-mark {
  flex: none;
  width: 6px;
  height: 6px;
  background: var(--claim);
  animation: live-pulse var(--dur-ambient) ease-in-out infinite;
}

/* ---- the alert ----
   The loudest thing in the section and the one carrying the message, so it is set a full rung
   above the rest of the chrome — --t-label at 12.6px against --t-label-sm at 10.8 — and given a
   solid Claim-red edge rather than the --claim-edge wash the quieter panels take. It is the only
   element on this picture allowed to shout.
   A FULL BORDER, unlike .feed__plate in section 001, and the ground is the reason. That plate's
   `rgba(8,9,12,0.66)` reads because it sits on a mid-grey street; on this near-black wall a dark
   fill on dark is not a plate at all, it is a smudge. No outer glow — css/surface.css is explicit
   that this page casts no outer shadow anywhere, and a glowing panel would be the one thing on
   the site that does. */
/* The threat stack: positioned once, its two strips in flow. `align-items: start` so each strip
   is only as wide as its own text — stretched, the shorter one would grow to match the longer and
   the pair would read as a single mis-set block. */
.why__threat {
  position: absolute;
  left: 5.5%;
  top: 20%;
  display: grid;
  justify-items: start;
  gap: var(--s-1);
  max-width: 89%;
}

.why__alert {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: 0;
  padding: var(--s-3) var(--s-4);
  max-width: 88%;
  border: 1px solid var(--claim);
  background: rgba(8, 9, 12, 0.84);
  font-size: var(--t-label);
  color: var(--signal);
  max-width: 100%;
  /* NO CHAMFER, and it was tried. `clip-path` cuts the element including its border, so the
     diagonal came out as the one unlined edge on a panel defined by its outline — a notch rather
     than a cut corner. .panel solves that with a two-layer fill and a 0.586px inset; this is a
     strip, not a panel, and section 001's .feed__plate — the thing it is a sibling of — carries
     no cut either. */

  /* Armed. js/why.js adds .is-in at the beat. Nothing is ever hidden by script that the script
     did not first arm — the rule css/motion.css states for the whole site — so with JS off the
     alert is simply there from the start. */
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity var(--dur-ui) var(--ease), transform var(--dur-ui) var(--ease);
}

.why__alert.is-in { opacity: 1; transform: translateY(0); }

/* Answered. The words change in script; what changes here is the temperature — the edge drops
   back to the wash and the type goes to bone, so the panel stops being an alarm and becomes a
   status. He has not tapped anything: the picture is already carrying the decision. */
.why__alert.is-answered {
  border-color: var(--claim-edge);
  color: var(--bone);
}

/* ---- the rival plate ----
   Section 001's .feed__plate--rival in everything but name: a dark strip with the rival's colour
   on its leading edge, naming who is on the ground you are looking at. */
.why__plate-tag {
  margin: 0;
  max-width: 100%;
  padding: 3px var(--s-2);
  border-left: 2px solid var(--enemy);
  background: rgba(8, 9, 12, 0.66);
  font-size: var(--t-label-sm);
  color: var(--bone-2);
  white-space: nowrap;

  opacity: 0;
  transition: opacity var(--dur-ui) var(--ease);
}

.why__plate-tag.is-in { opacity: 1; }

/* The stack shrinks on a phone, and the alert loses its extra rung to do it. At --t-label the two
   strips came to 71% of a 360pt plate's width and sat across the doorway; at --t-label-sm the
   pair is about 58% and the picture is a picture again. The alert still outranks the plate under
   it — it keeps the red edge, the signal colour and the mark — so the rung was carrying less of
   the hierarchy than the colour was. */
@media (max-width: 900px) {
  .why__threat { top: 14%; }
  .why__alert {
    padding: var(--s-2) var(--s-3);
    font-size: var(--t-label-sm);
  }
}

/* ---- the readout ----
   The bottom-of-frame grammar the hero uses for its coordinates and section 001 for its numbers:
   mono, quiet, on a gradient that gives the type a ground rather than a bar. */
.why__readout {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin: 0;
  /* The right and bottom padding are set by the CHAMFER, not by the space scale. .why__plate
     cuts 24px off its bottom-right corner, and at --s-3/--s-4 the last field ran straight through
     the diagonal — the one place on this plate where type had no plate left to sit on. Both are
     --s-6, which is the 24px the cut removes, so the readout's box stops where the corner does. */
  padding: var(--s-6) var(--s-6) var(--s-4) var(--s-4);
  font-size: var(--t-label-sm);
  color: var(--slate);
  background: linear-gradient(to top, rgba(8, 9, 12, 0.86), transparent);

  opacity: 0;
  transition: opacity var(--dur-ui) var(--ease);
}

.why__readout.is-in { opacity: 1; }

/* THREE FIELDS, AND THEY ARE SECTION 001'S THREE: where you are, how far you have gone, how fast.
   That panel reads "Street 44B | Anna 0.00 / 2.00 km | 164 steps a minute" and this one is the
   same instrument, so it takes the same shape rather than inventing another.
   The first field used to read "Defending MG Road" and the word was cut: the alert directly above
   it already says DEFENDING YOUR TERRITORY, and a panel that states its one verb twice in two
   places has weakened it in both. The place name alone is what section 001 prints here. */
.why__readout-place { color: var(--bone-2); }
/* The third field is pushed to the far edge, the pattern .feed__readout's own `.sp` uses. */
.why__readout-spm { margin-left: auto; }

/* Tabular, or a counting number shoves its own label sideways on every tick. */
.why__readout b {
  font-weight: inherit;
  color: var(--bone);
  font-variant-numeric: tabular-nums;
}

/* ---- reduced motion ----
   The clip never plays and js/why.js returns before it can set anything, so the still state is
   set here — and both halves have to AGREE, which is the trap js/premise.js documents at its own
   reduced-motion path. The poster is the clip's first frame: him in the doorway, phone in hand,
   not yet moving. So the alert and the rival plate are on screen and the readout is not, because
   "DEFENDING MG ROAD · 0.00 KM" printed under a man who is still indoors is the panel
   contradicting its own picture. */
@media (prefers-reduced-motion: reduce) {
  .why__alert,
  .why__plate-tag {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .why__readout { opacity: 0; }
  .why__live,
  .why__alert-mark { animation: none; }
}


/* ---- the copy ---- */

.why__label { color: var(--slate-2); }

.why__title {
  /* 10ch and `balance` TOGETHER, and neither works without the other. Both were picked off a
     probe rather than by eye — the sentence was set at every width from 10ch to 16ch in both
     wrap modes and the breaks read back:
        16ch  balance   The game gets | you out the door.
        16ch  wrap      The game gets you out | the door.
        11-15ch         no change from 16 in either mode
        10ch  wrap      The game gets | you out the | door.
        10ch  balance   The game | gets you out | the door.
     Everything from 11ch up sets TWO lines with a long second one, which is a sentence rather
     than a claim. Only 10ch breaks to three, and of the two modes there only balance avoids
     stranding "door." on a line by itself.
     WHAT SHIPS IS NOT WHAT THE PROBE PREDICTED, and the difference is worth recording because it
     will mislead the next person who runs the same test. The figures above come from setting the
     sentence as plain text; the page splits it into per-line spans for the reveal (splitLines in
     js/motion.js), and that pass measures inline-block word probes, which balance treats
     differently from a text run. Read back off the live element the lines are:
        The game | gets you | out the door.
     which is the better of the two — the fold lands after "you" instead of after "out", and the
     payoff sets as the longest line of the three rather than the shortest. Checked at 1440.
     The &nbsp; in the markup is the third part of this — it holds "the door." together so that
     no mode, and no future change to the measure, can split the phrase the section turns on.
     2026-09-27: the splitter is now GSAP SplitText, which breaks exactly as plain text does, so
     the probe-only break above would have been lost. It is held in the markup instead — "out the
     door." is bound with two &nbsp; — which also gives it to readers without script or with
     reduced motion, who were getting the plain-text break all along. Re-measured at 1440, 1280
     and 1024: The game | gets you | out the door. Phones unchanged. */
  max-width: 10ch;
  text-wrap: balance;
  margin-block: var(--s-12) var(--s-6);
}

.why__body {
  /* Deliberately wider than .body's own 20em. The two sentences are a pair — the thing you do
     not need, then the thing you do — and the turn only lands if the second starts on a line of
     its own rather than wrapping out of the middle of the first. 26em puts the break at the full
     stop at every width from 1024 up. */
  max-width: 26em;
  color: var(--bone-2);
}


/* ================================================================ 004 · contest
 *
 * The stakes beat: the one mechanic on the page that happens TO you rather than one you choose.
 * Copy left, board right — the mirror of section 001, where the picture is left and the copy is
 * right, so the two media sections do not stack into one long column down the same side.
 */

.contest {
  padding-block: var(--s-24);
  /* This one DOES carry a rule. It arrives off paper, so the seam above it is already the
     page's hardest edge and needs nothing; the rule is at the top of the dark side, marking
     where the board comes back. */
  border-top: 1px solid var(--hairline-strong);
}

.contest__inner { align-items: center; row-gap: var(--s-12); }

.contest__title {
  max-width: 15ch;
  /* Same reason as .why__title's, found the same way. Greedy wrapping put "into" alone on the
     middle line at the 360pt floor — the section's least important word given a line of 42px
     display type to itself. Balance sets it "Someone is walking / into your territory." at that
     width, and the &nbsp; on "your territory." keeps the object of the sentence whole. */
  text-wrap: balance;
  margin-block: var(--s-8) var(--s-8);
}

/* ---- the alarm ----
   The page's one genuine alert, and the only element outside the CTA chips allowed to run
   --signal at full strength. A bordered strip, not a chip: a chip is a control you can press and
   this is a state being reported. The fill is --signal-soft at 13%, which lands the strip a
   little above the page ground without becoming a red panel. */
.contest__state {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: 0 0 var(--s-8);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--claim-edge);
  background: var(--signal-soft);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  color: var(--signal);
  /* The bottom-right cut, at the chip rung rather than the media rung — it is panel-scale. */
  clip-path: polygon(
    0 0,
    100% 0,
    100% calc(100% - var(--chamfer)),
    calc(100% - var(--chamfer)) 100%,
    0 100%
  );
}

/* The brand's square, pulsing on the site's own ambient beat (`live-pulse`, css/motion.css). */
.contest__live {
  flex: none;
  width: 7px;
  height: 7px;
  background: var(--claim);
  animation: live-pulse var(--dur-ambient) ease-in-out infinite;
}

/* ---- the facts ----
   A description list, because that is what it is: three labelled readings off one contest. The
   rows are separated by hairlines rather than by space, which is how every instrument panel on
   this site divides a stack — see the dossier's own meta footer. */
.contest__facts {
  display: grid;
  gap: 0;
  margin: 0;
  border-top: 1px solid var(--hairline);
}

.contest__fact {
  display: grid;
  gap: var(--s-2);
  padding-block: var(--s-4);
  border-bottom: 1px solid var(--hairline);
}

.contest__fact dt { color: var(--slate-2); }
.contest__fact dd { margin: 0; }

/* A place name, so Archivo Black uppercase (Brand Guidelines v3). */
.contest__territory {
  font-family: var(--font-brand);
  font-weight: 900;
  font-size: 1.625rem;
  line-height: 1.05;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--bone);
}

@media (max-width: 759px) {
  .contest__territory { font-size: clamp(1.25rem, 6.4vw, 1.625rem); }
}

/* The separator is a rule turned on its side, not a slash glyph — same reasoning as
   .hud-readout's, which the brand states plainly: no decorative glyphs. It is written as a
   character in the markup and hidden from the accessible name, then drawn as colour. */
.contest__slash { color: var(--slate-2); }

/* ---- attacker against defender ----
   One comparison in two cells. The bar under each is drawn from that side's own `data-km` by
   js/main.js against whichever side has walked further, so the geometry cannot contradict the
   number printed above it. */
/* Two labelled sides in two columns. The dt/dd pairs alternate in source order — attacker
   label, attacker reading, defender label, defender reading — so the placement has to be
   explicit rather than left to auto-flow, which would run them down a single column in pairs.
   Stated as four rules rather than `grid-auto-flow: column`, because that would also stretch the
   implicit rows to match and the two cells are not the same height. */
.contest__fact--sides {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--s-6);
}

.contest__fact--sides > .contest__role[data-side="attacker"] { grid-column: 1; grid-row: 1; }
.contest__fact--sides > .contest__side[data-side="attacker"] { grid-column: 1; grid-row: 2; }
.contest__fact--sides > .contest__role[data-side="defender"] { grid-column: 2; grid-row: 1; }
.contest__fact--sides > .contest__side[data-side="defender"] { grid-column: 2; grid-row: 2; }

.contest__side { display: grid; gap: var(--s-2); }

.contest__role { color: var(--slate-2); }

.contest__who {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-data);
  color: var(--bone);
}

/* The swatches are the board's, and the mapping is the one the hero key states: red is your
   ground, blue is a rival. Repeating the fill percentages rather than reaching into carto.css
   keeps this section's chrome readable on its own; the tokens are shared, which is the part that
   has to stay in step. */
.contest__who i,
.contest__key i {
  flex: none;
  width: 9px;
  height: 9px;
  border: 1px solid;
}

.contest__who i[data-key="claim"],
.contest__key i[data-key="claim"] {
  background: color-mix(in srgb, var(--claim) var(--fill-claim, 56%), transparent);
  border-color: var(--claim);
}

.contest__who i[data-key="enemy"],
.contest__key i[data-key="enemy"] {
  background: color-mix(in srgb, var(--enemy) var(--fill-rival, 56%), transparent);
  border-color: var(--enemy);
}

.contest__km { color: var(--bone); }
/* The section's one lit readout (v3 G2): your distance. */
.contest__side[data-side="defender"] .contest__km { text-shadow: var(--g2-bone); }

/* The bar. A track at the hairline value with a fill sized by --walked, which arrives from
   js/main.js as a percentage of the leading distance. 3px, so it reads as a gauge rather than as
   a progress bar — this is a comparison of two readings, not a task completing. */
.contest__bar {
  position: relative;
  display: block;
  height: 3px;
  margin-top: var(--s-1);
  background: var(--hairline-strong);
}

.contest__bar::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--walked, 0%);
  background: var(--side-colour, var(--slate));
  /* Grows in when the section arrives. The custom property is set on load, so the transition
     needs something to run from: `width` starts at 0 through the --walked fallback until the
     script writes the real value. */
  transition: width 900ms cubic-bezier(0.16, 1, 0.3, 1) 200ms;
}

.contest__side[data-side="attacker"] { --side-colour: var(--enemy); }
.contest__side[data-side="defender"] { --side-colour: var(--claim); }

/* The verdict. One word, and it is one of the four the enlist headline ends on. */
.contest__verdict {
  font-family: var(--font-mono);
  font-size: var(--t-body-lg);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  color: var(--signal);
}

.contest__fact--status {
  /* Label and value on one line: it is a single short reading and a stacked pair wastes a row
     at the foot of the list where the eye is already leaving. */
  grid-template-columns: auto 1fr;
  align-items: baseline;
  gap: var(--s-6);
}

/* ---- the plate ----
   .panel supplies the chamfer, the hairline and the fill. The board inside takes the same
   viewfinder brackets (.scene__marks in css/travel.css), added by js/main.js. */
.contest__plate { margin: 0; }

.contest__head { color: var(--bone); }

/* `margin-left: auto` on the last child rather than a wrapper: the pattern the hero HUD's clock
   already uses to push a third item to the far edge without stranding the second one in the
   centre. */
.contest__coords { margin-left: auto; color: var(--slate); }

.contest__map {
  position: relative;
  /* 6:5. The window js/main.js fits is 440 board units across and the two must agree, or the
     crop is centred but the wrong shape. */
  aspect-ratio: 6 / 5;
  overflow: hidden;
}

/* THE VIEW IS BORROWED FROM css/travel.css, class name and all, and that is worth stating rather
   than leaving to be discovered. js/main.js builds the contest board's inner wrapper as
   `.scene__view.on-plate`, the name it had when the old how-it-works travel plates used it, so
   this map gets the satellite plate under it, the measured grade (saturate .30 / contrast 1.06 /
   brightness .42) and the --plate-size/x/y registration arithmetic for free.
   The travel is gone and this map is now the only user; css/travel.css keeps just the rules the
   page still borrows. Renaming them is the tidier change and buys nothing a comment cannot. */
.contest__map svg {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
}

/* The contest boundary, dashed — which is what the key beside it promises and what separates a
   block being FOUGHT over from one simply held. The march is slow and on the site's ambient
   beat; a fast crawl reads as a selection marquee. */
.contest__map .claim__route {
  stroke-dasharray: 7 5;
  animation: contest-march 3s linear infinite;
}

@keyframes contest-march {
  to { stroke-dashoffset: -24; }
}

@media (prefers-reduced-motion: reduce) {
  .contest__map .claim__route { animation: none; }
  .contest__bar::after { transition: none; }
}

/* ---- the key ----
   In flow at the foot of the panel rather than floated over the board. The hero's legend is
   absolutely placed because it sits on a full-bleed image with nowhere else to go; this panel has
   a bezel, and a key belongs on the bezel. */
.contest__key {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-6);
  margin: 0;
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--hairline);
  list-style: none;
  font-family: var(--font-mono);
  font-size: var(--t-label-sm);
  text-transform: uppercase;
  letter-spacing: var(--track-label-wide);
  color: var(--slate);
}

.contest__key li { display: flex; align-items: center; gap: var(--s-2); }

/* The contest swatch is the route in miniature: a dashed rule, not a filled square, because the
   thing it stands for on the board is a line and not an area. */
.contest__key i[data-key="contest"] {
  width: 18px;
  height: 0;
  border: 0;
  border-top: 2px dashed var(--claim);
}


/* ---------------------------------------------------------------- responsive */

@media (max-width: 900px) {
  .lede { padding-block: var(--s-24) var(--s-16); }

  /* ---- 003 ----
     The plate goes WIDER, not narrower. It is 4:3 on a desktop because it shares its row with a
     copy column and has to leave room for it; stacked, there is no neighbour to leave room for,
     and the source is a 1.81:1 landscape frame that has been paying a 74% centre crop for the
     privilege. 3:2 gives 83% of the frame back and stops the phone showing the tightest crop of
     the picture on the smallest screen — which is backwards, because a phone is where a doorway
     and a street have the least room to read as two things. */
  .why { padding-block: var(--s-16); }
  .why__plate { aspect-ratio: 3 / 2; }
  .why__title { max-width: none; margin-block: var(--s-8) var(--s-6); }
  .why__body { max-width: none; }

  /* ---- 004 ----
     The two sides stay SIDE BY SIDE at every width, which is the one thing in this section that
     does not stack. They are a comparison — the row exists so two numbers can be read against
     each other — and a comparison stacked into two rows is just two facts. At 360pt each cell is
     about 155px, which holds "Commander_02" at 14.4px mono with room to spare. */
  .contest { padding-block: var(--s-16); }
  .contest__title { max-width: none; margin-block: var(--s-6) var(--s-6); }
  .contest__fact--sides { column-gap: var(--s-4); }

  /* 18px, down from 21.6. "Mahatma Gandhi Road / 01" is the longest single string in this
     section and it is mono, so its width is arithmetic rather than luck: at 21.6px it measures
     310px against the 326px column a 360pt phone gives it — 16px of slack, which is one
     character. The name is real and comes from the board, so it cannot be shortened to fit, and
     a wrapped street name under a label reading TERRITORY reads as two territories. At 18px it
     measures 258px and has 68px in hand, which survives a longer street on a future board.
     It stays a step above --t-data so the reading still outranks the two distances below it. */
  .contest__territory { font-size: var(--t-body); }

  /* The board squares up. 6:5 is a desktop shape for a panel taking half a wide row; full width
     on a phone it becomes a 320px-tall band with the crop's subject squeezed into the middle of
     it. 1:1 gives the contested block and the rival's ground the same vertical room they have on
     a desktop — and the window js/main.js fits is unchanged, because the SVG is on slice and the
     BOX is what decides the shape. Which means this and the aspect-ratio above it are the two
     places to look if the crop ever stops framing the front. */
  .contest__map { aspect-ratio: 1 / 1; }
  .lede__title { max-width: none; margin-block: var(--s-8) var(--s-6); }
  .lede__body { max-width: none; }


  /* The dossier stacks. Its two columns are a brief and a grant, not a comparison, so stacking
     costs the layout nothing — but the gap has to come down from --s-16 to --s-12, because a
     64px void between two stacked blocks inside a bordered panel reads as a gap in the file. */
  .dossier__body {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-12);
    padding: var(--s-8) var(--s-6);
  }

  /* Stacked, there is no taller neighbour to fill against, so the brief packs to the top again
     and space-between would only stretch the gap to whatever the section happened to be. */
  .dossier__brief { align-content: start; }
  .dossier__intro { max-width: none; }

  /* The stamp goes left with everything else. Right-aligned under a full-width perk list it
     hangs off on its own with no edge to sit against, and rotated at the page margin it clips. */
  .dossier__stamp { justify-self: start; transform-origin: 0 50%; }

  /* The file reference takes its own row under the title. Side by side, "COMMANDERS" is one
     unbreakable word in the brand face and the two collided; before .founders__inner was fixed to
     minmax(0, 1fr) that collision was hidden by the panel quietly widening past the screen. */
  .dossier__bar { flex-wrap: wrap; row-gap: var(--s-2); }
  .dossier__ref { flex-basis: 100%; padding-left: calc(6px + var(--s-3)); }

  /* Brackets pull in with the padding. At 10px on a panel whose content now starts 24px from
     the border they were floating in the middle of the inset rather than marking a corner. */
  .dossier { --hud-arm: 11px; }
  .dossier__marks { inset: 8px; }

  /* The wordmark stays edge to edge — that is the whole device — but a seven-letter word at
     15.2vw is only ~59px on a 390pt phone, which reads as a caption rather than as a sign-off.
     Two lines of four and three would break the word, so instead it simply runs bigger. */
  /* Nothing to do: textLength fits the word to the page at every width by construction. */
}
