/* ============================================================
   iMPACT website — OVERHAUL stylesheet.
   Rebuilt 29 July 2026 against art-direction/direction-charter-v2.md
   (Route B, "the line leads"). Supersedes the pre-overhaul sheet in
   full: the field grammar, the section-number eyebrows, the bento
   step-card grid and the elevation scale of the released direction
   are all gone, released by the OVERHAUL.

   WHAT THIS SHEET IS BOUND BY, and where to look it up:
   - Invariants I1-I10, charter section 3. Not negotiable here.
   - Composition grammar, charter section 5.
   - Type roles, charter section 6.
   - Material and surface, charter section 9.
   - Intensity capped at 6 across the whole site, charter section 10.

   THREE BUILD DECISIONS RECORDED AT SOURCE (charter section 4 clause 2
   requires an exception be written down where it is taken):

   1. ACCENT POLICY — SETTLED BY STUART, 29 July 2026, and I5 is
      AMENDED in the charter rather than excepted here (charter section
      4 clause 1: an invariant that needs an exception means the
      charter has a fault, and the route is back to the document).
      An earlier build of this file read I5 literally and gave the
      free-side primary action teal, reserving fuchsia for the gate.
      Stuart's correction, and it is the better reading: the
      conversion button IS the customer crossing into the service, so
      fuchsia on it is the accent doing its job rather than being
      spent elsewhere. The practical half is decisive — teal is a
      background colour, it recedes by design, and the actions we want
      taken have to stand out. FUCHSIA therefore carries primary
      actions and the payment gate. Links stay teal so they do not
      compete with the calls to action.
      White on #9E1F68 measures 7.37:1, so the filled button clears AA
      comfortably.

   2. GATE POSITION — --gate-at, single source of truth.
      Moved from 55% to 62% on Stuart's instruction, 29 July 2026, to
      show more of the value that arrives before payment. The gate must
      stay strictly between stop 3 (42%, the last free decision) and
      stop 4 (68%, the first paid action) or it reclassifies a step and
      stops being true. Safe band 46%-64%; 64% is the ceiling, where
      clearance from stop 4 drops to roughly 45px and the gate starts
      to read as tethered to that mark rather than standing alone.
      Within the band the position carries no honesty cost — it is
      rhetorical, not a claim. measure.js reads this value from CSS, so
      it is changed HERE and nowhere else.

   3. LIME GREEN, charter production dependency 9 (Graeme, 28 July
      2026). Returns as a sparing second accent for family resemblance
      only. It fails AA at 2.12:1, so it is used ONLY as a non-text
      mark and never as text, as a fill behind text, or as a state
      colour. It must not compete with I5, so it never appears on or
      near the measure.

   CONTRAST. Every non-black text colour here is checked against white
   or against --ground-alt, per charter production dependency 4. The
   released direction shipped two failures (#7d888b at 3.64:1 and
   #8a9598 at 3.07:1); neither value appears in this file.
   ============================================================ */

/* ---------- self-hosted faces, charter production dependency 2 ----------
   B232's F1 found both faces specified across eight pages with no
   @font-face and no webfont link anywhere, so almost every visitor
   received San Francisco or Segoe UI and the apparatus voice had never
   rendered in Roboto for anyone. This block is that defect's fix.
   Both files are latin-subset variable woff2, Apache 2.0.
     OpenSans.woff2  42,964 bytes
     Roboto.woff2    37,520 bytes
     combined        80,484 bytes, inside the 200KB per-page budget. */
@font-face {
  font-family: 'Open Sans VF';
  src: url('fonts/OpenSans.woff2') format('woff2');
  font-weight: 300 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Roboto VF';
  src: url('fonts/Roboto.woff2') format('woff2');
  font-weight: 300 800;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ground and ink */
  --ink: #333333;            /* 12.6:1 on white */
  --ink-2: #3F4B4E;          /* secondary body        9.9:1 on white */
  --ink-mute: #5A6B71;       /* apparatus, captions   5.56:1, verified */
  --ground: #FFFFFF;
  --ground-alt: #F4F6F7;
  --rule: #D9DEE0;
  --rule-strong: #B9C6CA;

  /* the two commercial colours */
  --teal: #246172;           /* 7.4:1 on white. The free side, links, and quiet actions */
  --teal-deep: #1A4855;
  --fuchsia: #9E1F68;        /* 7.6:1 on white. Crossings: primary actions and the gate */
  --fuchsia-deep: #7D1852;
  --fuchsia-wash: #FBF4F8;   /* the one permitted fuchsia ground; carries no text of its own */

  /* action roles — the single point of control for build decision 1.
     Fuchsia carries the actions we want taken, because taking them is
     the customer crossing into the service. Teal recedes. */
  --act-primary: var(--fuchsia);
  --act-primary-deep: var(--fuchsia-deep);
  --act-quiet: var(--teal);
  --act-quiet-deep: var(--teal-deep);

  /* gate position — build decision 2. Safe band 46%-64%.
     measure.js reads this value; do not hard-code it anywhere else. */
  --gate-at: 62%;

  /* family resemblance, non-text only — build decision 3 */
  --lime: #91C13E;

  /* type roles, charter section 6 */
  --font-display: Georgia, 'Times New Roman', 'Nimbus Roman', serif;
  --font-body: 'Open Sans VF', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
  --font-ui: 'Roboto VF', Roboto, -apple-system, 'Segoe UI', Arial, sans-serif;

  --radius-pill: 30px;
  --s1: 8px; --s2: 16px; --s3: 24px; --s4: 32px; --s5: 48px; --s6: 64px; --s7: 96px;
  --measure-text: 68ch;
}

* { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%; scroll-behavior: smooth;
  /* PROXIMITY, never mandatory (Stuart, 29 July 2026): it makes the
     measure easy to land on centred without ever taking the scroll away
     from the reader. Mandatory snapping is the hijack we deliberately
     refused — it fights momentum scrolling, breaks find-in-page, and
     routinely fails reflow at 400% zoom. Proximity only engages when
     the reader has already come to rest nearby. */
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.62;
  color: var(--ink);
  background: var(--ground);
}

h1, h2, h3 { font-family: var(--font-display); font-weight: 500; color: var(--ink); }
h1 { font-size: clamp(2.2rem, 4.3vw, 3.35rem); line-height: 1.05; letter-spacing: -.022em; margin: 0 0 var(--s3); max-width: 18ch; }
h2 { font-size: clamp(1.65rem, 3vw, 2.3rem); line-height: 1.16; letter-spacing: -.014em; margin: 0 0 var(--s3); max-width: 26ch; }
h3 { font-size: 1.3rem; line-height: 1.25; margin: 0 0 var(--s2); }
p  { margin: 0 0 var(--s3); max-width: var(--measure-text); }
ul, ol { max-width: var(--measure-text); }
/* Links stay teal so body copy does not compete with the calls to
   action, which are the only fuchsia-filled things on the page. */
a  { color: var(--act-quiet); }
a:hover { color: var(--act-quiet-deep); }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 99;
  background: var(--ink); color: #fff; padding: 12px 20px; text-decoration: none;
}
.skip-link:focus { left: 0; }

:focus-visible { outline: 3px solid var(--act-primary); outline-offset: 3px; }

/* ---------------- shell ---------------- */
.wrap { max-width: 1180px; margin: 0 auto; padding: 0 var(--s4); }

.site-header {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  max-width: 1180px; margin: 0 auto; padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--rule); position: relative;
}
/* The lime underline is gone: the parent does not have one, and the
   lime now does its family-resemblance work as the vertical separator
   (measured on imultiplyresourcing.com, 29 July 2026 — a 1px rule in
   #91C13E, 68px tall, beside the logo). Still a non-text mark, so build
   decision 3 holds. */
.brand-lockup { display: flex; align-items: center; gap: var(--s2); }
/* A long unbroken URL in link text is unbreakable by default and pushed
   the privacy page sideways at 305px. `anywhere` rather than
   `break-word` so it applies to the min-content size the grid and flex
   layouts above are calculated from, which is the half that actually
   stops the overflow. */
a { overflow-wrap: anywhere; }
.logo-link { display: flex; align-items: center; gap: 10px; text-decoration: none; }
.logo-link svg { width: 34px; height: 34px; display: block; }
.wordmark { font-family: var(--font-body); font-weight: 700; font-size: 21px; letter-spacing: -.01em; color: var(--teal); }
.by-imultiply {
  display: flex; align-items: center; gap: 6px; text-decoration: none;
  border-left: 1px solid var(--rule); padding-left: var(--s2);
}
.by-imultiply .by { font-size: 12.5px; color: var(--ink-mute); }
.by-imultiply img { height: 17px; width: auto; display: block; }

.main-nav { display: flex; align-items: center; gap: var(--s3); font-size: 15px; }
/* :not(.btn) is load-bearing, not tidiness. Without it this selector's
   specificity (0,1,1) beats .btn-primary's (0,1,0) and the header's
   Register button renders charcoal on teal at roughly 1.7:1 — a hard AA
   failure on the site's only conversion action. Found by measuring
   computed styles on 29 July 2026, not by eye, which is the third
   instance of this defect class in this project. */
.main-nav a:not(.btn) { color: var(--ink); text-decoration: none; }
.main-nav a:not(.btn):hover { text-decoration: underline; }
.main-nav a[aria-current="page"] { text-decoration: underline; text-underline-offset: 5px; }

.btn {
  display: inline-block; font-family: var(--font-body); font-weight: 600; font-size: 15.5px;
  padding: 13px 26px; border-radius: var(--radius-pill); text-decoration: none;
  border: 2px solid var(--act-primary); cursor: pointer; line-height: 1.3;
}
/* Filled fuchsia, white label at 7.37:1. This is the action we want
   taken, and taking it is the crossing into the service (I5 as
   amended, 29 July 2026). */
.btn-primary { background: var(--act-primary); color: #fff; }
.btn-primary:hover { background: var(--act-primary-deep); border-color: var(--act-primary-deep); color: #fff; }
/* Secondary recedes deliberately: teal outline, so it never competes
   with the primary action next to it. */
.btn-secondary { background: transparent; color: var(--act-quiet); border-color: var(--act-quiet); }
.btn-secondary:hover { background: #EEF3F4; color: var(--act-quiet-deep); }

section { max-width: 1180px; margin: 0 auto; padding: var(--s6) var(--s4); }
section.ground-alt { max-width: none; background: var(--ground-alt); }

/* ---- PHOTOGRAPHIC GROUND (Stuart, 29 July 2026) ----
   iMultiply's own photograph of two of its consultants, reused with
   permission, under a pale-teal wash at 0.91. It carries the family
   resemblance to the parent site, whose own treatment is the same
   photograph under teal at 0.66.

   CHARTER POSITION, RECORDED. Section 7's "no photography, no
   photography of people" is OVERRIDDEN — Graeme directly requested the
   photography be reused (Stuart, 29 July 2026). Section 9's flat-ground
   rule is amended by the same decision. Both need writing into the
   charter. Note section 7's stated reason for banning people-imagery
   was that a faceless crowd contradicts per-candidate anonymity; two
   identifiable iMultiply consultants are the opposite of that, and the
   picture supports the "someone behind it" line rather than fighting it.

   WHY 0.91 AND NOT LIGHTER OR DARKER. Measured against the darkest and
   lightest pixels of the actual composited image, not an average:
     dark teal 0.66 (the parent's)  body 3.28:1  kicker 2.47:1  FAIL
     dark teal 0.82                 body 4.61:1  kicker 3.47:1  FAIL
     pale teal 0.86                 body 7.62:1  kicker 4.18:1  FAIL
     pale teal 0.91                 body 8.52:1  kicker 4.68:1  PASS
     pale teal 0.95                 body 9.29:1  kicker 5.10:1  PASS
   0.91 is the lightest wash that clears AA on everything; 0.95 is near-
   indistinguishable from the grey it replaces and buys the imagery
   nothing. If the copy, the image, or a text colour changes, re-measure
   against the extremes — an average will pass while a corner fails. */
section.ground-photo {
  max-width: none; position: relative; background: #DFEAED;
  isolation: isolate;
}
section.ground-photo::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: url('img/iMultiply-consultants.webp') center/cover no-repeat;
}
section.ground-photo::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: #DFEAED; opacity: .91;
}
/* FULL-BLEED SECTIONS ALIGN TO THE SAME TEXT COLUMN AS EVERY OTHER
   SECTION (Stuart, 29 July 2026: the shared-background text sat left of
   the rest, on How it works and About).

   A normal section is a 1180px box with var(--s4) padding, so its text
   column is 1180 - 64 = 1116 wide and starts 32px inside that box. A
   bleed section keeps the padding but drops the max-width, so a child
   capped at 1180 centred itself in the FULL width and landed exactly
   one --s4 further left — 123px against 155px at a 1425px viewport.
   Capping the child at the text-column width instead puts it back on
   the same line. Written as a calc so it tracks the token rather than
   hard-coding 1116. */
section.ground-photo > * { position: relative; z-index: 2; max-width: calc(1180px - var(--s4) * 2); margin-left: auto; margin-right: auto; }
section.ground-alt > * { max-width: calc(1180px - var(--s4) * 2); margin-left: auto; margin-right: auto; }

.kicker {
  font-family: var(--font-ui); font-weight: 600; font-size: .72rem;
  letter-spacing: .12em; text-transform: uppercase; color: var(--teal);
  margin: 0 0 var(--s2);
}
.category-line {
  font-family: var(--font-display); color: var(--teal);
  font-size: 1.22rem; line-height: 1.5; max-width: 48ch;
}
.lede { font-size: 18.5px; color: var(--ink-2); max-width: 60ch; }

/* ---------------- the crossed-out categories (Stuart, 29 July 2026) ----
   Three negations that used to be one long headline, now the section's
   visual event. Every selector here carries .category as well, so these
   cannot leak onto another section and cannot lose to a later rule on
   source order.

   NO LIST MARKER. The strike IS the mark; a bullet beside a crossed-out
   word is a second mark competing with it, and the site's small-square
   marker already belongs to the measure's vocabulary. */
/* HORIZONTAL AND SEPARATED (Stuart, 29 July 2026), to pull the section
   into a single view. Stacked, the three items cost three display lines
   plus their gaps; on one row they cost one. Wraps on narrow widths
   rather than shrinking, so the words never get smaller than the type
   scale intends. */
.category .crossed {
  /* `auto` on the inline sides, NOT 0. This section became a full-bleed
     `.ground-alt` on 29 July 2026, and in a bleed section the alignment
     comes from `section.ground-alt > *`'s auto margins centring a
     1116px column. A `margin` SHORTHAND with 0 on the sides silently
     overrode those (higher specificity, later in the file) and dropped
     this list to the section's padding edge — 130px left of every
     sibling. The shorthand looked completely innocent. */
  list-style: none; padding: 0; margin: var(--s4) auto var(--s4);
  display: flex; flex-wrap: wrap; align-items: baseline; column-gap: .5em; row-gap: .2em;
}
.category .crossed li {
  display: flex; align-items: baseline; gap: .5em;
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(1.4rem, 2.9vw, 1.7rem); line-height: 1.32;
  color: var(--ink);
}
/* The separator lives on the row, never inside the word, which is why
   the stroke had to move inside the <s>: anything in the <li> would be
   struck along with the word. */
.category .crossed li + li::before { content: "\00B7"; color: var(--ink-mute); }
/* The <s> is there for meaning, not for its default line — the stroke
   replaces it — and it is what the stroke is measured against. */
.category .crossed s { position: relative; display: inline-block; text-decoration: none; }
/* THE STROKE IS SIZED IN em, NOT PER CENT OF THE LINE BOX, and that is
   the whole trick. Mapped to 100% height, the viewBox stretched by the
   line-height as well as the font size — about 3.7x at display scale —
   so a gentle waver in the path became a wave that left the text band
   entirely. Tying the band to the font size makes the amplification
   constant at every size, so the stroke reads the same on a phone as on
   a desktop.

   0.42em down: with line-height 1.32 the baseline sits near 0.91em and
   Georgia's x-height is about 0.48em, which puts the middle of the
   x-height near 0.66em. The band is 0.46em tall and the path runs
   through its centre, so 0.42em lands the stroke there. At 0.30em it
   crossed the tops of the letters instead, which reads as underlining
   the line above rather than striking this one. */
.category .crossed svg {
  position: absolute; left: -1.5%; top: .42em; width: 103%; height: .46em;
  overflow: visible; color: var(--teal); pointer-events: none;
}
/* Thickness in em so it stays proportionate to the word, with
   non-scaling-stroke stopping the horizontal stretch from flattening it
   into a wedge. */
.category .crossed svg path { stroke-width: .13em; }

/* ---- the strokes score themselves in, once (Stuart, 29 July 2026) ----
   CHARTER §8 AMENDED for this; it is the second admitted motion after
   the wave, and §8 required returning there rather than excepting it in
   the build. The amendment's terms are held here: it runs ON SCROLL INTO
   VIEW, never on load (§8 forbids autoplay on load), it PLAYS ONCE and
   stays scored (§8 forbids anything that loops without end), nothing
   decreases, and Home is not a trust-critical surface.

   DRAWN BY DEFAULT, HIDDEN ONLY BY SCRIPT. The strokes are fully visible
   in CSS and strike.js hides them at the moment it takes over, so a
   reader with no JavaScript sees three struck-out words rather than
   three intact ones — which would say the opposite of what is meant. */
.category .crossed.is-armed svg path { stroke-dashoffset: var(--len); }
.category .crossed.is-armed li.is-scored svg path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset var(--dur, 460ms) var(--ease, cubic-bezier(.5,.05,.35,1));
}
/* The reset, which only ever runs off-screen. transition:none is the
   point: reversing the draw would read as the pen retracing itself
   backwards, a motion nothing in the physical world does, so the dash
   snaps back instead while nobody is looking at it. */
.category .crossed.is-armed li.is-reset svg path {
  stroke-dashoffset: var(--len); transition: none;
}

/* Reduced motion: no drawing at all, the strokes are simply there.
   strike.js checks the same query and never arms, so this is defence in
   depth rather than the only guard. */
@media (prefers-reduced-motion: reduce) {
  .category .crossed.is-armed svg path { stroke-dashoffset: 0; transition: none; }
}

/* THE ANSWER STAYS LARGER THAN THE REFUSAL, at every width, and this is
   a governance constraint rather than taste. The route's
   do-not-recombine list (charter §11) says refusal content never takes
   benefit scale, and three crossed words at display size is exactly the
   pressure that breaks it: at the first attempt the struck words were
   31.2px against the answer's 24.8px, which is refusal outranking
   benefit by a quarter. The crossed items are now capped at 1.7rem and
   the answer sits at 1.78rem, so the answer leads on desktop as it
   already did on mobile. If either number moves, re-check the pair. */
/* THE NARROWER-CHILD CASE, and it needs the opposite of auto margins.
   `section.ground-alt > *` centres each child in the full bleed width,
   which lands on the right left-edge only when the child is the full
   1116px column. This line is capped at 42ch (734px), so auto margins
   centred it 191px right of every sibling — the misalignment Stuart
   spotted. Setting the inline start margin to the column's own offset
   left-aligns it to the same edge instead. max() guards viewports
   narrower than the column, where the offset would go negative. */
.category .category-line {
  font-size: 1.78rem; max-width: 42ch;
  margin-left: max(0px, calc((100% - (1180px - var(--s4) * 2)) / 2));
  margin-right: auto;
}

/* Visually hidden, available to screen readers. The site had no such
   utility; .hp is the honeypot and is aria-hidden, so it is the opposite
   of what is needed. */
.vh {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.actions { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s4); }
.hero { padding-top: var(--s6); padding-bottom: var(--s3); }

/* ============================================================
   THE MEASURE — the page's spine, charter section 5.
   Never on an elevated surface (charter section 9). The gate never
   moves and is never operable (I1). The axis denotes commercial
   position, never elapsed time (I2).
   ============================================================ */
/* ---- the sticky track ----
   The measure and its text follow the reader through a tall section, so
   the words are on screen for the whole progression. This replaces the
   proximity snap, which existed only to make a short section easier to
   land on and now has nothing to do.

   NOT scroll-jacking, and the distinction is the whole point: no scroll
   event is intercepted and nothing is pinned by script. Native sticky
   keeps momentum scrolling, find-in-page and keyboard paging intact,
   and falls back to a normal static block where it is unsupported.

   The track is deliberately SHORT for the pattern — about half a
   viewport per step rather than the customary full one. A tall sticky
   step-through is a recognisable template signature (module 5.9), and
   the less of the reader's scrolling it consumes the less it reads as
   one. */
.measure-scroller { position: relative; height: 300vh; }

/* PADDING, not margin, and this is load-bearing. The gate label, the
   price and the "Free" mark are positioned ABOVE the line with negative
   offsets — MEASURED at 83px of overhang, from the Free label. Margin
   sits outside the box, so a sticky offset smaller than the overhang
   cuts the tops of those labels off: 9vh is 135px at a 1500px viewport
   and fine, but only 81px at 900px, where they clipped (found 29 July
   2026). Padding brings the overhang inside the box the sticky offset
   is measured against, so it cannot clip at any viewport height. */
/* padding-top MEASURED at 83px of overhang from the Free label, so 88
   clears it with 5px. It was 100; the title above now provides the
   separation the extra was doing, and every pixel here is height budget
   for the sticky pin. Stage top margin cut 48 -> 16 for the same reason. */
.line-stage { margin: 16px 0 0; padding-top: 88px; }

/* THE TITLE OF THE PINNED THING PINS WITH IT. It sits inside the pin
   rather than above the track, so it cannot scroll away and leave a
   frozen diagram with no heading (Stuart, 29 July 2026). */
/* max-width: none because the global h2 rule caps at 26ch, which is a
   reading measure for display type and about 145px at this size — it
   broke a six-word label across two lines. */
.measure-title { margin: 0; font-size: .72rem; max-width: none; }

/* The frozen header takes the top 103px of every viewport, so the
   measure sticks BELOW it rather than sliding underneath. That is the
   one real cost of freezing the header: it spends about 100px of the
   height budget the sticky diagram needs.

   OFFSET LOWERED 120 -> 104 (Stuart, 29 July 2026: his own desktop was
   not sticking). 104 is the header's own 103px plus one, so this is the
   lowest the pin can sit without sliding under it — there is nothing
   further to give here. The title costs its own height back, so the
   .now gap gives it up: 108 -> 76, which is why the threshold below
   still falls rather than rises. */
/* 76, not less: the gate price hangs 40px below the line and is about
   23px tall, so anything under about 68 puts the panel through it.
   That is the floor on this one, not a preference. */
.line-stage .now { margin-top: 76px; }
.measure-pin { position: relative; }

/* THRESHOLD IS THE PIN'S OWN HEIGHT PLUS THE OFFSET, measured rather
   than assumed: MEASURED pin 581 + 104 = 685. It was 730 before this session's
   trims, and Stuart's own desktop was falling under it. */
@media (min-height: 685px) {
  .measure-pin { position: sticky; top: 104px; }
}

/* Below that the pin cannot stick without clipping its own text even
   with the header out of the way, so the track collapses and the
   control carries the progression instead. */
@media (max-height: 592px) {
  .measure-scroller { height: auto; }
}
@media (prefers-reduced-motion: reduce) {
  .measure-scroller { height: auto; }
  .measure-pin, .line-stage { position: static; }
}
.line { position: relative; height: 4px; background: #CBD6D9; }
/* THE FREE RUN, not a progress bar (Stuart, 29 July 2026).
   It previously grew with the reader's position, which meant teal said
   "how far you have looked" on Home and "free" on How it works — the
   same signal carrying two meanings on a site whose whole proposition
   is that the line denotes commercial position. It is now fixed to the
   gate on both pages. Position is carried by the enlarged current mark
   and the "N of 6" counter, which already do that job. */
.line-fill { position: absolute; left: 0; top: 0; height: 4px; width: var(--gate-at); background: var(--teal); }
.line::before, .line::after {
  content: ""; position: absolute; top: -8px; width: 4px; height: 20px; background: var(--teal);
}
.line::before { left: 0; }
.line::after { right: 0; }

.gate { position: absolute; top: -30px; left: var(--gate-at); width: 4px; height: 64px; background: var(--fuchsia); }
/* buds out of the line, never crosses it — the counterpart to the gate */
.free-mark { position: absolute; left: 27%; top: -40px; width: 4px; height: 40px; background: var(--teal); }
.free-mark b {
  position: absolute; left: 50%; bottom: 48px; transform: translateX(-50%);
  white-space: nowrap; font-family: var(--font-display); font-weight: 500;
  font-size: 1.35rem; color: var(--teal);
}
.gate-label {
  position: absolute; left: var(--gate-at); top: -58px; transform: translateX(-50%);
  font-family: var(--font-ui); font-weight: 600; font-size: .72rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--fuchsia); white-space: nowrap;
}
.gate-price {
  position: absolute; left: var(--gate-at); top: 40px; transform: translateX(-50%);
  font-family: var(--font-display); font-size: 15px; color: var(--fuchsia); white-space: nowrap;
}

/* every mark stays visible at all times — I6, Stuart's own correction */
/* --at is set by measure.js as a bare percentage, so the axis is the
   stylesheet's choice: left here, top in the narrow-width block. */
.stop {
  position: absolute; top: -11px; left: var(--at); transform: translateX(-50%);
  width: 26px; height: 26px; padding: 0; border: 0; background: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.stop i {
  display: block; width: 13px; height: 13px;
  background: var(--teal); transition: transform .18s ease, background .18s ease;
}
/* outlined marks are iMPACT's two jobs, and are subordinate — I8 */
.stop--system i { background: transparent; border: 1.5px solid #7E9298; }
.stop[aria-current="true"] i { transform: scale(1.55); }
.stop:hover i { background: var(--ink); }
.stop--system:hover i { background: transparent; border-color: var(--ink); }
/* past the gate is a fact about the mark, not about the reader's
   progress, so these are fuchsia at all times — matching How it works */
.stop--paid i { background: var(--fuchsia); }

/* Height is pinned, not merely floored. MEASURED across all six states
   on 29 July 2026: 250, 281, 341, 348, 361, 312px — so at 250px the
   panel grew by up to 111px as the reader stepped, which did two bad
   things. It shifted every section below the measure on each move, and
   it changed the stage's height enough to flip the floating control's
   "fully in view" test off mid-interaction. 380px clears the tallest
   state with room for a wrap at narrower widths.
   If the copy changes, re-measure. */
.now { margin-top: 108px; min-height: 384px; max-width: 44ch; }
@media (max-width: 860px) { .now { min-height: 440px; } }
.now .who {
  font-family: var(--font-ui); font-weight: 600; font-size: .72rem; letter-spacing: .12em;
  text-transform: uppercase; margin: 0 0 var(--s2); color: var(--fuchsia);
}
.now.is-system .who { color: var(--ink-mute); }
.now h2 {
  font-size: clamp(1.9rem, 3.5vw, 2.75rem); line-height: 1.12;
  letter-spacing: -.018em; margin: 0 0 var(--s3); max-width: none;
}
.now.is-system h2 {
  font-family: var(--font-body); font-weight: 400;
  font-size: clamp(1.25rem, 2vw, 1.6rem); color: var(--ink-2);
}
.now p { font-size: 18.5px; line-height: 1.5; color: var(--ink-2); margin: 0; max-width: none; }
/* THE STATUS BOX. Large, shaded, definitive (Stuart, 29 July 2026):
   "free, still free, last free step, paid". It replaces both the small
   cost sentence that used to sit here and the shaded crossing note
   below it, which read as internal language and only ever appeared
   after the gate. One element now states the commercial status of
   every step, in the same place, in one or three words.

   The shading carries the same meaning the measure does, so the box and
   the rail always agree: teal ground on the free side, fuchsia ground
   after the gate. Fuchsia here is I5-compliant — it denotes the paid
   side of the crossing, which is the one thing the accent is for, and
   --fuchsia-wash is the charter's single permitted fuchsia ground. */
.now .cost {
  display: block; margin-top: var(--s4);
  font-family: var(--font-display); font-size: 1.65rem; line-height: 1.1;
  letter-spacing: -.01em;
  padding: 14px 20px; border-left: 4px solid var(--teal);
  background: #F1F6F7; color: var(--teal);
}
.now.is-paid .cost {
  border-left-color: var(--fuchsia);
  background: var(--fuchsia-wash); color: var(--fuchsia);
}


/* progression control. I3: moves backwards as freely as forwards, and
   nothing seen is withdrawn on the way back. Every control is a button
   or a native range, so no drag is ever required (WCAG 2.2 SC 2.5.7). */
/* The in-flow Back/Next control was retired 29 July 2026 (Stuart): the
   floating stepper now carries slide, click and keyboard, so a second
   control in the page was duplicate machinery. The reversible note
   below stays, because it states I3 in words and that is copy, not UI. */
.reversible { margin-top: var(--s3); font-size: 14px; color: var(--ink-mute); max-width: 56ch; }

/* ---- the wave that makes the marks readable as targets ----
   Replaced a pulsing ring on the next mark (Stuart, 29 July 2026): a
   ring sitting ON a node reads as "you are here", which is exactly the
   wrong message when the node it marks is the one you have NOT been to.
   A pulse that travels ALONG the line says direction instead of
   position, and nudges the mark it arrives at.

   This also sits better against charter section 8 than the ring did.
   Section 8 asks for motion that is "positional and reader-driven" and
   that shows "where you are on the measure" — a wave running from the
   current mark to the next one is literally positional. It still draws
   the eye, so the section 8 amendment flagged for the ring is still
   needed, but the case for it is stronger.
   Reader-driven, finite, and silent under prefers-reduced-motion. */
/* THE WAVE — peristalsis, a swell that PROPAGATES along the line.
   Earlier attempts slid a single rigid block from one mark to the next,
   which reads as a moving object rather than as a wave in the line
   itself (Stuart, 29 July 2026).

   This is built from a row of short segments spanning the gap between
   the current mark and the next. Each one swells and subsides over
   ~420ms, and each starts fractionally later than the one to its left,
   so the bulge travels. It is the same mechanism as a peristaltic wave:
   nothing moves along the line, the line swells in sequence.

   A rope waveform — vertical displacement — was the alternative and was
   NOT taken: it would make the line undulate, and the line has to keep
   reading as a rule denoting real product events. That is the standing
   defence against the anti-field's Tier 1.3 broadsheet-hairline entry
   (charter section 11), and a wobbling rule forfeits it. A swell keeps
   the line straight.

   Deep teal, so it reads against both halves: 2.07:1 on the teal free
   run, 9.67:1 on the pale grey after the gate, and 14.34:1 where the
   swell rises clear of the line onto the page. Fuchsia is unavailable —
   I5 reserves it for the crossing. */
.line-wave {
  position: absolute; top: 0; height: 4px; pointer-events: none;
  left: var(--wave-from, 0%); width: var(--wave-span, 0%);
}
.wave-seg {
  position: absolute; top: 0; left: var(--seg); width: 6px; height: 4px; margin-left: -3px;
  background: #0E2E37; border-radius: 3px;
  opacity: 0; transform: scaleY(1);
}
.line-wave.run .wave-seg { animation: peristalsis 420ms ease-in-out both; }
@keyframes peristalsis {
  0%   { opacity: 0; transform: scaleY(1); }
  40%  { opacity: 1; transform: scaleY(4); }
  100% { opacity: 0; transform: scaleY(1); }
}
@media (prefers-reduced-motion: reduce) {
  .line-wave { display: none; }
}
/* the arrival nudge, on the mark the wave reaches */
.stop.wobble i { animation: markWobble .42s ease-in-out 2; }
@keyframes markWobble {
  0%, 100% { transform: translateX(0); }
  30%      { transform: translateX(-2.5px); }
  70%      { transform: translateX(2.5px); }
}
@media (prefers-reduced-motion: reduce) {
  .stop.wobble i { animation: none; }
}

/* ---- the floating stepper ----
   Stuart's request: keep Back and Next with the reader while they are
   in the section the control affects, and let them go when the reader
   scrolls on to the static parts of the page.

   Only shown where there is genuine margin to put it in. The content
   column is 1180px, so below about 1400px the margins are too narrow
   to hold it without crowding the measure; there, the in-flow control
   under the line does the job on its own. An IntersectionObserver, not
   a scroll handler, so nothing runs on every frame. */
/* Vertical at every width (Stuart, 29 July 2026). The first version
   was a horizontal pill below 1400px, which sat across the start of the
   line and could obscure the first mark. Stacking it keeps the
   horizontal footprint to roughly 72px, so it stays clear of the
   content column at ordinary widths.

   Two treatments, one shape: below 1400px it carries pill chrome,
   because it is floating over content and needs to read as a control
   rather than as part of the page. Above 1400px there is genuine margin
   beside the 1180px column, so the chrome comes off and it sits in the
   white space. */
.stepper-float {
  position: fixed; left: 10px; bottom: 16px; z-index: 20;
  display: flex; flex-direction: column; align-items: stretch; gap: 6px;
  background: #fff; border: 1px solid var(--rule); border-radius: 18px;
  padding: 8px; box-shadow: 0 2px 10px rgba(51,51,51,.10);
  opacity: 1; transition: opacity .2s ease;
}
@media (min-width: 1400px) {
  .stepper-float {
    left: 24px; top: 50%; bottom: auto; transform: translateY(-50%);
    gap: 10px; background: none; border: 0; box-shadow: none; padding: 0;
  }
}
.stepper-float > button {
  font-family: var(--font-body); font-size: 13px; font-weight: 600;
  width: 72px; padding: 8px 0; border-radius: var(--radius-pill); cursor: pointer;
  background: #fff; color: var(--teal); border: 1.5px solid var(--rule-strong);
}
/* the mini rail carries position at every width the mini rail exists at,
   so the worded counter is off unless the narrow-width block turns it on */
.mini-count { display: none; }
.stepper-float > button:hover:not(:disabled) { border-color: var(--teal); }
.stepper-float > button:disabled { opacity: .4; cursor: default; }
@media (min-width: 1400px) { .stepper-float > button { width: 88px; padding: 10px 0; font-size: 14px; } }

/* ---- the mini measure ----
   A small vertical correlate of the diagram on the page: same order,
   same colours, same single threshold, no annotation. It exists so the
   reader can see where they are on the commercial line without
   scrolling back to it, and can move by sliding or by clicking a
   numbered step.
   The numbers are real buttons, so click and keyboard both work and the
   drag has a single-pointer alternative (WCAG 2.2 SC 2.5.7). */
.mini { position: relative; height: 168px; margin: 2px 0 2px 8px; touch-action: none; cursor: pointer; }
.mini-rail { position: absolute; left: 8px; top: 0; width: 3px; height: 100%; background: #CBD6D9; }

/* Colour arrives only as the reader reaches it (Stuart, 29 July 2026):
   filled behind the thumb, grey ahead of it. The hue still says which
   side of the threshold the reader is on, so the control reports both
   position AND commercial side without needing a legend — teal up to
   the gate, fuchsia beyond it. */
.mini-fill { position: absolute; left: 0; top: 0; width: 3px; height: 0; background: var(--teal); }
.mini-fill-paid { position: absolute; left: 0; width: 3px; height: 0; background: var(--fuchsia); }

/* the threshold, the one place money enters */
.mini-gate { position: absolute; left: 2px; top: var(--gate-at); width: 15px; height: 3px; background: var(--fuchsia); }

.mini-step {
  position: absolute; left: 0; transform: translateY(-50%);
  display: flex; align-items: center; gap: 6px;
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: var(--font-ui); font-size: 10px; color: var(--ink-mute);
}
/* grey until met */
.mini-step i { display: block; width: 9px; height: 9px; margin-left: 5px; background: #CBD6D9; }
.mini-step.sys i { background: #fff; border: 1.5px solid #CBD6D9; }
.mini-step.is-met i { background: var(--teal); }
.mini-step.is-met.sys i { background: #fff; border-color: #7E9298; }
.mini-step.is-met.paid i { background: var(--fuchsia); }
.mini-step[aria-current="true"] b { color: var(--ink); font-weight: 700; }
.mini-step b { font-weight: 400; }

/* the thing being dragged */
.mini-thumb {
  position: absolute; left: 2.5px; top: 0; transform: translateY(-50%);
  width: 14px; height: 14px; border-radius: 50%;
  background: #fff; border: 2.5px solid var(--teal);
  box-shadow: 0 1px 3px rgba(51,51,51,.25); pointer-events: none;
}
.mini-thumb.paid { border-color: var(--fuchsia); }

.mini-step:focus-visible { outline: 2px solid var(--act-primary); outline-offset: 2px; }

/* HIDDEN STATE. This rule went missing when the control was rewritten
   from a horizontal pill to a vertical stack on 29 July 2026, so the
   script was toggling a class that styled nothing and the control stayed
   on screen at every scroll position — including the top of the page,
   where it governs nothing the reader can see. */
.stepper-float.off { opacity: 0; visibility: hidden; pointer-events: none; }

/* First appearance only. Charter section 8 permits motion that is
   positional and reader-driven, and forbids anything that autoplays,
   loops, or attracts attention to itself. This is reader-driven — it
   fires because they scrolled — runs once, and never repeats however
   often the control comes and goes afterwards. It IS a device that
   draws the eye, so it is deliberately short and small. Flagged for the
   interface-design review with the rest of the control.

   Animates `translate`, not `transform`, on purpose: above 1400px the
   float is centred with transform: translateY(-50%), and animating
   transform would fight it. The two properties compose. */
@keyframes stepperIn {
  from { opacity: 0; translate: -16px 0; }
  to   { opacity: 1; translate: 0 0; }
}
.stepper-float.reveal { animation: stepperIn .32s cubic-bezier(.2,.7,.3,1) both; }

@media (prefers-reduced-motion: reduce) {
  .stepper-float { transition: none; }
  .stepper-float.reveal { animation: none; }
}

/* THE STATIC SUMMARY on Home — charter I7 as amended 29 July 2026.
   Self-sufficient prose carrying the argument when the control is
   unused or unavailable. The complete equivalent lives on How it works,
   one click away, rather than being repeated here. */
.static-summary { margin-top: var(--s6); max-width: 66ch; border-left: 3px solid var(--teal); padding-left: var(--s4); }
.static-summary p { font-size: 16px; }
.static-summary p:last-child { margin-bottom: 0; }
.static-summary strong { color: var(--ink); }

/* the six-row equivalent — retained for How it works, and for any page
   that needs the complete form rather than the summary above */
.all { margin: var(--s4) 0 0; max-width: 76ch; }
.all-row {
  display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: var(--s3);
  padding: var(--s3) 0; border-top: 1px solid var(--rule);
}
.all-row:last-child { border-bottom: 1px solid var(--rule); }
.all-row i { display: block; width: 12px; height: 12px; background: var(--teal); margin-top: 7px; }
.all-row.sys i { background: transparent; border: 1.5px solid #7E9298; }
.all-row.paid i { background: var(--fuchsia); }
.all-row b { display: block; font-family: var(--font-display); font-weight: 500; font-size: 1.15rem; margin-bottom: 4px; }
.all-row.sys b { font-family: var(--font-body); font-weight: 400; font-size: 1rem; color: var(--ink-2); }
.all-row span { font-size: 15px; color: var(--ink-2); }
.gate-row { padding: var(--s3) 0; border-top: 2px solid var(--fuchsia); }
.gate-row b {
  font-family: var(--font-ui); font-weight: 600; font-size: .74rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--fuchsia); display: block; margin-bottom: 4px;
}
.gate-row span { font-size: 15px; color: var(--ink-2); }

/* ============================================================
   THE WHOLE-SERVICE VIEW — charter section 7a, a REQUIRED element.
   Stuart's requirement: one composition in which the entire service is
   visible at once, on How it works.

   IT IS NOT A NEW DEVICE. It is this charter's own measure at full
   extent, with every stop present and NONE foregrounded, plus the two
   windows on their own registers. Gate 4's one-dominant-mechanism rule
   therefore holds: this is the same mechanism seen whole.

   EXCEPTION RECORDED HERE, per charter section 4 clause 2: section 5's
   "one dominant instance per page" is excepted on How it works by
   design. That page carries the whole-service view as its dominant
   instance and therefore carries NO second full-scale measure.
   Expected to generalise: no. It is specific to this one page.

   STATIC, WITH NO PROGRESSION CONTROL, AND THAT IS THE POINT. A
   composition whose purpose is seeing everything at once must not hide
   parts of itself to be operated. Section 7a permits interaction only
   where it ADDS; foregrounding a single stop would subtract, and would
   contradict "none foregrounded" directly. The cheapest regime that
   carries the meaning wins, and here the meaning is simultaneity.

   SEAM, stated honestly: charter section 7a assigns production of a
   semantic diagram to `information-design`, with `asset-production`
   building it. What is built here is the charter's own measure at full
   extent, which section 7a specifies directly. A different diagram
   carrying the S7 model remains that mode's to produce.
   ============================================================ */
.measure-whole { margin: var(--s6) 0; }

/* ---- wide: an annotated diagram. The detail lives ON the line ----
   Annotations alternate above and below so each has room; six captions
   on one side collide below about 1100px.

   The notes are absolutely positioned, so they contribute nothing to
   layout height and the space for them is reserved by hand. MEASURED at
   1440px: the tallest below-note is 231px and sits 74px under the rail,
   so it needs about 305px. Note width is fixed at 185px, so these
   heights do not change with viewport. If a note gains a line,
   re-measure. */
.mw-rail { position: relative; height: 4px; background: #CBD6D9; margin: 250px 0 330px; }
.mw-rail::before, .mw-rail::after {
  content: ""; position: absolute; top: -8px; width: 4px; height: 20px; background: var(--teal);
}
.mw-rail::before { left: 0; }
.mw-rail::after { right: 0; }
.mw-free { position: absolute; left: 0; top: 0; height: 4px; width: var(--gate-at); background: var(--teal); }

/* THE FREE MARK. Different in kind from the gate rather than a weaker
   copy of it: the gate CROSSES the rail in fuchsia, this BUDS OUT of it
   in teal and never crosses. Extent is already carried by the rail
   itself, teal to the gate and grey after, so the mark names the
   territory rather than having to measure it. The word is set like the
   price, so the two commercial facts read as a matched pair. */
.mw-freemark { position: absolute; left: 27%; top: -44px; width: 4px; height: 44px; background: var(--teal); }
.mw-freemark b {
  position: absolute; left: 50%; bottom: 52px; transform: translateX(-50%);
  white-space: nowrap; font-family: var(--font-display); font-weight: 500;
  font-size: 1.35rem; color: var(--teal);
}

/* the gate: fixed, never operable — I1 */
.mw-gate { position: absolute; left: var(--gate-at); top: -40px; width: 4px; height: 84px; background: var(--fuchsia); }
.mw-gate-price {
  position: absolute; left: var(--gate-at); top: -86px; transform: translateX(-50%);
  font-family: var(--font-display); font-size: 1.35rem; color: var(--fuchsia); white-space: nowrap;
}
.mw-gate-label {
  /* the label leads and the price sits beneath it, so the line reads
     "nothing to pay until here", then what it costs if you go on */
  position: absolute; left: var(--gate-at); top: -118px; transform: translateX(-50%);
  font-family: var(--font-ui); font-weight: 600; font-size: .7rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--fuchsia); white-space: nowrap;
}
/* above the line with the label: below it ran through the stop-4 note */
.mw-gate-sub {
  position: absolute; left: var(--gate-at); top: -50px; transform: translateX(-50%);
  font-family: var(--font-ui); font-size: 12px; color: var(--ink-mute); white-space: nowrap;
}

/* every stop present, none foregrounded — no aria-current anywhere here */
.mw-stop { position: absolute; left: var(--at); top: -6px; width: 0; }
.mw-stop > i { display: block; width: 13px; height: 13px; margin-left: -6.5px; background: var(--teal); }
.mw-stop.sys > i { background: transparent; border: 1.5px solid #7E9298; }
.mw-stop.paid > i { background: var(--fuchsia); }

.mw-note { position: absolute; left: 50%; transform: translateX(-50%); width: 185px; }
.mw-stop.at-start .mw-note { left: -6px; transform: none; }
.mw-stop.at-end   .mw-note { left: auto; right: -6px; transform: none; }
.mw-above .mw-note { bottom: 30px; }
.mw-below .mw-note { top: 74px; }
/* the leader, so each annotation is unambiguously attached to its mark */
.mw-note::after {
  content: ""; position: absolute; left: 50%; width: 1px; background: var(--rule-strong);
}
.mw-stop.at-start .mw-note::after { left: 6px; }
.mw-stop.at-end   .mw-note::after { left: auto; right: 6px; }
.mw-above .mw-note::after { bottom: -22px; height: 18px; }
.mw-below .mw-note::after { top: -66px; height: 62px; }

.mw-note b {
  display: block; font-family: var(--font-ui); font-weight: 600; font-size: .66rem;
  letter-spacing: .12em; text-transform: uppercase; color: var(--fuchsia); margin-bottom: 5px;
}
.mw-stop.sys .mw-note b { color: var(--ink-mute); }
.mw-note strong {
  display: block; font-family: var(--font-display); font-weight: 500;
  font-size: 1.05rem; line-height: 1.25; color: var(--ink); margin-bottom: 5px;
}
.mw-stop.sys .mw-note strong { font-family: var(--font-body); font-weight: 400; font-size: .95rem; color: var(--ink-2); }
.mw-note span { display: block; font-size: 13.5px; line-height: 1.45; color: var(--ink-2); }
.mw-note em { display: block; font-style: normal; font-family: var(--font-display); font-size: 13.5px; color: var(--teal); margin-top: 5px; }
.mw-stop.paid .mw-note em { color: var(--fuchsia); }

.mw-key { display: flex; flex-wrap: wrap; gap: var(--s3); font-family: var(--font-ui); font-size: 12.5px; color: var(--ink-mute); }
.mw-key b { display: inline-flex; align-items: center; gap: 8px; font-weight: 400; }
.mw-key i { width: 11px; height: 11px; background: var(--teal); display: block; }
.mw-key .sys i { background: transparent; border: 1.5px solid #7E9298; }
.mw-key .cross i { background: var(--fuchsia); }

/* ---- narrow: the SAME markup reflows into a vertical annotated list ----
   Not a second copy of the content. The stops stop being absolutely
   positioned and stack, so the identical annotations carry the whole
   service in one column. I7 is satisfied by the same nodes, not by a
   duplicate written underneath. */
@media (max-width: 1000px) {
  .mw-rail { height: auto; background: none; margin: var(--s4) 0 0; padding-left: 22px; border-left: 3px solid #CBD6D9; }
  .mw-rail::before, .mw-rail::after { display: none; }
  .mw-free { display: none; }
  .mw-freemark { position: static; width: auto; height: auto; background: none;
                 border-left: 4px solid var(--teal); padding-left: 14px; margin: 0 0 var(--s3); }
  .mw-freemark b { position: static; transform: none; display: block; }
  .mw-stop { position: relative; left: auto; top: auto; width: auto; padding: var(--s3) 0; }
  .mw-stop > i { position: absolute; left: -30px; top: 30px; margin: 0; }
  .mw-note, .mw-stop.at-start .mw-note, .mw-stop.at-end .mw-note {
    position: static; transform: none; width: auto; max-width: 52ch;
  }
  .mw-note::after { display: none; }
  .mw-gate { position: relative; left: auto; top: auto; width: auto; height: auto;
             background: none; border-top: 2px solid var(--fuchsia); margin: var(--s3) 0; padding-top: var(--s3); }
  .mw-gate-label, .mw-gate-price, .mw-gate-sub { position: static; transform: none; display: block; white-space: normal; }
  .mw-gate-price { font-size: 1.25rem; margin: 4px 0; }
  .mw-key { margin-top: var(--s4); }
}


/* ---------------- the two clocks, charter section 7a ----------------
   Carried on their OWN separate instances of the measure, never on the
   commercial axis — I2. These are the only places on the site where a
   span denotes elapsed time, and I4 keeps every one of them away from
   an action prompt. */
/* min() on the track floor, not a bare 300px: a bare floor is wider than
   a 305px viewport's content column, so the grid refuses to shrink and
   pushes the whole page sideways. Found 29 July 2026 by measuring
   scrollWidth on all eight pages, not by looking. */
.clocks { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: var(--s5); margin-top: var(--s5); max-width: 900px; }
.clock h3 { font-family: var(--font-ui); font-weight: 600; font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-mute); margin: 0; }
.clock-bar { position: relative; height: 3px; background: #CBD6D9; margin: var(--s4) 0 var(--s2); }
.clock-bar > span { position: absolute; left: 0; top: 0; height: 3px; background: #7E9298; }
.clock-bar::before, .clock-bar::after { content: ""; position: absolute; top: -6px; width: 3px; height: 15px; background: #7E9298; }
.clock-bar::before { left: 0; }
.clock-bar::after { right: 0; }
.clock-ends { display: flex; justify-content: space-between; font-family: var(--font-ui); font-size: 12.5px; color: var(--ink-mute); }
.clock p { font-size: 15px; color: var(--ink-2); margin: var(--s2) 0 0; }

/* ---------------- panels and documents ---------------- */
.panel { background: var(--ground-alt); padding: var(--s4); margin: 0 0 var(--s3); max-width: 76ch; }
section.ground-alt .panel { background: #fff; }
.panel ul { margin: 0; padding-left: 1.15em; }
.panel li { margin-bottom: var(--s2); max-width: 64ch; }
.panel li:last-child { margin-bottom: 0; }
.panel p:last-child { margin-bottom: 0; }

/* Exhibits read as documents the employer receives — I9. No chrome, no
   status chips, no invented counts, no dashboard framing. Elevation
   belongs to object cards only (charter section 9). */
.exhibit {
  margin: 0; border: 1px solid var(--rule); background: #fff;
  box-shadow: 0 1px 3px rgba(51,51,51,.08); padding: var(--s4); max-width: 46ch;
}
.exhibit-caption {
  display: flex; justify-content: space-between; gap: var(--s3); align-items: baseline;
  font-family: var(--font-ui); font-size: .72rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-mute); border-bottom: 1px solid var(--rule); padding-bottom: var(--s2); margin-bottom: var(--s3);
}
.exhibit-caption .free { color: var(--teal); }
.exhibit-rows { margin: 0; }
.exhibit-rows > div { display: grid; grid-template-columns: 8.5em minmax(0,1fr); gap: var(--s2); padding: 10px 0; border-bottom: 1px solid #EDF0F1; }
.exhibit-rows dt { font-family: var(--font-ui); font-size: 12.5px; color: var(--ink-mute); margin: 0; }
.exhibit-rows dd { margin: 0; font-size: 14.5px; color: var(--ink-2); }
/* Route E's approved withheld treatment: a ruled gap carrying its own
   reason, never a blur and never a teaser. Greys re-specified at AA per
   charter production dependency 4 — the released #7d888b (3.64:1) and
   #8a9598 (3.07:1) both failed and appear nowhere in this file. */
.withheld {
  display: inline-block; color: var(--ink-mute); font-size: 13.5px;
  border-bottom: 1px dashed var(--rule-strong); padding-bottom: 2px;
}
.exhibit-label { font-size: 12.5px; color: var(--ink-mute); margin: var(--s3) 0 0; font-style: italic; }

/* ---------------- FAQ, familiar expand/collapse (charter section 5) ---------------- */
.faq-item { border-top: 1px solid var(--rule); max-width: 76ch; }
.faq-item:last-of-type { border-bottom: 1px solid var(--rule); }
.faq-item summary {
  cursor: pointer; padding: var(--s3) 0; font-family: var(--font-display);
  font-size: 1.12rem; color: var(--ink);
}
/* THE DISCLOSURE TRIANGLE IS TEAL, NOT BLACK (Stuart, 29 July 2026).
   The browser draws the marker in the element's own colour, so it was
   inheriting --ink and reading as punctuation rather than as the control
   it is. Colouring it independently of the label lets the label stay
   --ink when closed while the affordance stays teal throughout.
   ::-webkit-details-marker is the fallback for Safari versions that do
   not yet honour ::marker on a summary. */
.faq-item summary::marker { color: var(--teal); }
.faq-item summary::-webkit-details-marker { color: var(--teal); }
.faq-item summary:hover { color: var(--teal); }
.faq-item[open] summary { color: var(--teal); }
.faq-item .faq-body { padding: 0 0 var(--s3); }
.faq-item .faq-body p:last-child { margin-bottom: 0; }
/* Clear of the last question's rule, and the action on its own line. */
.faq-tail { margin-top: var(--s5); }
.faq-tail p { margin-bottom: var(--s3); }
.faq-tail p:last-child { margin-bottom: 0; }

/* ---------------- forms, high-contrast and labelled (charter section 5) ---------------- */
/* EVERY .register-form IS A CARD, and it is the same card on every page
   (Stuart, 29 July 2026: the exclusions form and the register form must
   match). The treatment lives on the component rather than on one page,
   which is what stops the two drifting apart again.

   FLUSH, NOT ELEVATED. Charter section 9 is explicit: "Elevation belongs
   to object cards only — exhibits and step cards. Panels and forms sit
   flush." So this is the .panel treatment (tinted ground, one hairline
   rule) and deliberately not .exhibit's box-shadow. A drop shadow here
   would need a charter amendment, not a CSS decision. */
.register-form {
  max-width: 30rem; margin-top: var(--s4);
  background: var(--ground-alt); border: 1px solid var(--rule);
  padding: var(--s4);
}
.register-form label { display: block; font-weight: 600; font-size: 14.5px; margin: var(--s3) 0 6px; }
.register-form label:first-of-type { margin-top: 0; }
.register-form > p:last-child { margin-bottom: 0; }
.register-form label.required::after { content: " *"; color: var(--fuchsia); }
.register-form input, .register-form select, .register-form textarea {
  width: 100%; font-family: var(--font-body); font-size: 16px; color: var(--ink);
  padding: 12px 14px; border: 1.5px solid var(--rule-strong); border-radius: 4px; background: #fff;
}
.register-form input:focus-visible, .register-form select:focus-visible, .register-form textarea:focus-visible {
  outline: 3px solid var(--teal); outline-offset: 1px; border-color: var(--teal);
}
.form-hint { font-size: 13.5px; color: var(--ink-mute); margin: 6px 0 0; }

/* ---- eligibility gate (Stuart, 29 July 2026) ----
   A real fieldset/legend, so the four boxes are announced as one group
   with its question, rather than four unrelated checkboxes. */
.eligibility { border: 0; padding: 0; margin: var(--s3) 0 0; min-width: 0; }
.eligibility legend {
  padding: 0; font-weight: 600; font-size: 14.5px; color: var(--ink); margin-bottom: var(--s2);
}
/* THE WHOLE ROW IS THE TARGET, not the 16px box. WCAG 2.2 SC 2.5.8 asks
   for 24px, and a bare checkbox does not reach it — wrapping each in its
   own label makes the text clickable and takes the row past 24px. */
.eligibility label {
  display: flex; align-items: flex-start; gap: 10px;
  font-weight: 400; font-size: 15px; color: var(--ink-2);
  margin: 0; padding: 7px 0; cursor: pointer;
}
/* .register-form sets width:100% on every input, which would stretch a
   checkbox across the card. */
.eligibility input[type="checkbox"] {
  width: 16px; height: 16px; min-width: 16px; margin: 3px 0 0; padding: 0;
  accent-color: var(--fuchsia); cursor: pointer;
}
.eligibility .form-hint { margin-top: var(--s2); }

/* The gated submit. Visibly inert, and it says so rather than just
   looking faded. */
.register-form button[type="submit"]:disabled {
  opacity: .45; cursor: not-allowed;
}
/* The submit row. Added 29 July 2026 to replace per-page inline
   `style="margin-top:…"` on the register and not-a-fit forms. Inline
   styles are the style-src half of the same problem inline scripts are
   for script-src: a CSP without 'unsafe-inline' kills them silently, and
   silently is the bad part — the button keeps working and only the
   spacing dies, so nothing fails loudly enough to be noticed. */
.form-actions { margin-top: var(--s3); }

.form-status { margin-top: var(--s2); font-size: 14.5px; min-height: 1.4em; }

/* honeypot, B204 — off-screen, never focusable, never announced */
.hp { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }

/* ---------------- lists, signposts, footer ---------------- */
/* same bare-floor defect as .clocks above, same fix */
.fit-lists { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: var(--s5); max-width: 900px; }
.fit-lists ul { padding-left: 1.15em; }
.fit-lists li { margin-bottom: var(--s2); }

/* ---------------- register.html (B233) ----------------
   THE FORM HAS TO BE IN VIEW, and that is the whole layout rule
   (Stuart, 29 July 2026). Anyone landing here has already read the
   argument somewhere else and decided to register; further exposition
   in front of the fields is an obstacle, not persuasion. So the page
   carries a compact hero, the form, and links out to the explanation
   that already exists elsewhere on the site. Nothing else.

   The interior-page hero is deliberately shorter here than .page-head
   gives every other page: at its standard var(--s6) top padding the
   submit button fell below the fold on a laptop, which is the one
   thing this page must not do. */
/* TWO CLASSES ON EVERY SELECTOR HERE, DELIBERATELY. This block sits
   earlier in the file than .article and .page-head, so single-class
   selectors lose to them on source order and the layout silently does
   nothing — which is exactly what happened on the first build. Raising
   specificity fixes it wherever the block sits, rather than making the
   file's running order load-bearing. */
.page-head.reg-head { padding-top: var(--s4); padding-bottom: 0; }
.page-head.reg-head h1 { max-width: 20ch; margin-bottom: 0; }
.reg-copy { margin-top: var(--s4); }
.reg-copy .lede { margin-bottom: var(--s3); }
.reg-more { font-size: 15px; margin-bottom: 0; }
/* The card itself is defined once on .register-form, above. This is only
   the register page's spacing on top of it. */
.article.reg-page .register-form { margin-top: var(--s3); }
.article.reg-page .form-note { margin-top: var(--s3); margin-bottom: 0; }

/* Side by side once there is width for it, so the fields clear the fold
   on a short laptop as well as a tall one. Stacked, the submit button
   sat at 802px and any window under about 810 buried it — the single
   thing this page is not allowed to do. Below 900px it stacks, where
   the hero is short enough that the fields still start high. */
@media (min-width: 900px) {
  .article.reg-page {
    display: grid; align-items: start;
    grid-template-columns: 1fr minmax(20rem, 30rem);
    /* Row 1 takes the headline's own height and row 2 absorbs the rest.
       Left implicit, both rows are auto, and the browser shares the
       form's height (it spans both) across them — which pushed the
       headline and the copy under it roughly 150px apart for no reason.
       The fr row gives that spare height somewhere to go. */
    grid-template-rows: auto 1fr;
    column-gap: var(--s6); padding-top: var(--s5);
  }
  .page-head.reg-head { grid-column: 1; grid-row: 1; padding: 0; max-width: none; }
  .article.reg-page .register-form { grid-column: 2; grid-row: 1 / span 2; margin-top: 0; max-width: none; }
  .reg-copy { grid-column: 1; grid-row: 2; margin-top: var(--s3); }
}

.signpost {
  max-width: 1180px; margin: 0 auto; padding: var(--s5) var(--s4);
  font-size: 15px; color: var(--ink-2); border-top: 1px solid var(--rule);
}
.site-footer { background: var(--ground-alt); margin-top: var(--s7); padding: var(--s5) var(--s4); }
.site-footer nav { display: flex; flex-wrap: wrap; gap: var(--s3); max-width: 1180px; margin: 0 auto var(--s3); }
.site-footer a { color: var(--teal); font-size: 14.5px; }
.site-footer p { max-width: 1180px; margin: 0 auto; font-size: 14.5px; color: var(--ink-mute); }

/* ---------------- narrow widths ----------------
   Charter section 4 permits the orientation to turn vertical at narrow
   widths provided I1-I6 survive the turn, and below 760px it does turn
   (Stuart, 29 July 2026: "we want them scrolling through").

   Superseded: the measure used to be WITHDRAWN below 760px, leaving a
   phone reader with the static summary and no diagram at all. I7 as
   amended permitted that, but it spent the direction's whole mechanism
   on desktop only, and a majority of first views are phones. */
@media (max-width: 860px) {
  .main-nav { gap: var(--s2); font-size: 14px; }
  .main-nav a:not(.btn) { display: none; }
  section, .site-header, .signpost, .site-footer, .wrap { padding-left: var(--s3); padding-right: var(--s3); }
  .site-header::after { left: var(--s3); }
  .gate-label { font-size: .62rem; }
  .gate-price { font-size: 13px; }
  .now { margin-top: 92px; }
}
/* ===== THE MEASURE TURNS VERTICAL BELOW 760px =====================
   ONE SET OF NODES, TURNED — not a second diagram. Every mark, the
   gate, the free run and the wave are the same elements the desktop
   composition uses; only the axis their percentages sit on changes.
   measure.js hands the stylesheet bare percentages (--at, --seg,
   --wave-from, --wave-span) precisely so this is possible: an inline
   `left` would have forced a duplicate set of nodes, which is how a
   responsive diagram quietly becomes two diagrams that drift apart.

   COMPOSITION: a compact rail with its six marks in the upper block,
   the current decision at reading width beneath it. NOT the rail beside
   the panel, which was tried first and measured: at 390px a rail plus
   gutter leaves roughly 250px of text column, which is unusable at
   display scale, and it leaves the gate's label nowhere to go that is
   not on top of the body copy. Stacking gives the label the full width
   to the right of the crossbar and gives the copy the full width below.

   The invariants all survive the turn, which is section 4's condition:
   I1 the gate is a fixed crossbar and no control touches it, I2 the
   axis is still commercial position, I3 the progression still reverses,
   I5 fuchsia is still only the gate and the crossing, I6 all six marks
   are on screen at once, I7 the static summary is untouched below. */
@media (max-width: 760px) {
  /* THE TITLE COSTS NO HEIGHT HERE, because it goes in space that is
     already empty. Stacked above the rail it added 41px to the pin and
     pushed the sticky floor to 752, which is close enough to an 844px
     iPhone's usable height with Safari's URL bar showing that the
     measure would flip between sticky and static as the bar collapsed.
     To the right of the rail's top there is nothing until the Free mark
     at 27%, so the title sits there instead and the floor stays low. */
  .measure-title {
    position: absolute; top: 0; left: 44px; right: 0; margin: 0;
  }
  /* 6px so the 4% mark, which overhangs the rail's top by about 4px,
     is not clipped by the pin's own edge when it freezes */
  .line-stage { display: block; padding-top: 6px; margin-top: 0; }

  /* THE RAIL, and its length is set by the tightest pair on it, not by
     taste. The stops sit at 4/22/42/68/84/97% and the gate at 62%, so
     the two tightest gaps are gate-to-stop-4 at 6% and stop-5-to-6 at
     13%. MEASURED 29 July 2026: at 208px the 6% gap is 12.5px, which
     put the first paid mark ON the gate crossbar. At 264px it is 17.7px
     and the mark clears the bar by about 7px, which is tight and
     truthful — the first paid action does come immediately after the
     gate — while 13% gives 34px between the last two marks, clear of
     the 26px hit targets. Longer would be more comfortable and does not
     fit: every pixel here comes off the sticky height budget below.
     Settled at 248 rather than 264 to buy that budget back — 6% is then
     14.9px and the mark still clears the bar by about 6px. */
  .line { width: 4px; height: 236px; margin-left: 12px; }
  .line-fill { width: 4px; height: var(--gate-at); }

  /* END CAPS DROPPED, and they were turned first before this was found.
     The outer stops sit at 4% and 97%, which is 10px and 7px from the
     ends of a 248px rail. Turned, the caps landed underneath those two
     marks and read as one malformed blob rather than as a cap and a
     mark (screenshotted, 29 July 2026). On the 1116px horizontal
     measure the same percentages are 45px and 33px clear, which is why
     this never showed up there. The rail is a finite bar with a mark
     near each end, so the ends are legible without them. */
  .line::before, .line::after { display: none; }

  /* Marks: --at is now a vertical position. Hit target unchanged. */
  .stop { left: -11px; top: var(--at); transform: translateY(-50%); }

  /* the free run's stub buds RIGHT out of the rail rather than up, and
     keeps the desktop's own 27% so the free run is labelled at the same
     point on the measure at every width */
  .free-mark { top: 27%; left: 4px; width: 24px; height: 4px; }
  .free-mark b {
    left: 34px; bottom: auto; top: 50%; transform: translateY(-50%);
    font-size: 1.2rem;
  }

  /* THE GATE, and this is the composition's whole reason for stacking.
     A crossbar THROUGH the rail, with both labels stacked to its right
     in space nothing else occupies. On desktop they sit above and below
     a vertical bar; here they sit above and below a horizontal one, so
     the relationship reads identically. */
  .gate { top: var(--gate-at); left: -14px; width: 32px; height: 4px; }
  .gate-label {
    top: var(--gate-at); left: 30px; transform: translateY(-140%);
    font-size: .64rem;
  }
  .gate-price {
    top: var(--gate-at); left: 30px; transform: translateY(45%);
    font-size: 14px;
  }

  /* the wave runs down the rail; the swell has to grow ACROSS it */
  .line-wave {
    left: 0; width: 4px;
    top: var(--wave-from, 0%); height: var(--wave-span, 0%);
  }
  .wave-seg {
    left: 0; top: var(--seg); width: 4px; height: 6px;
    margin-left: 0; margin-top: -3px;
  }
  .line-wave.run .wave-seg { animation-name: peristalsis-across; }
  .stop.wobble i { animation-name: markWobble-across; }

  /* THE CURRENT DECISION, at reading width under the rail.
     min-height is pinned for the same reason it is on desktop: an
     unpinned panel grows as the reader steps, which shifts every
     section below it and changes the stage height enough to flip the
     floating control's visibility test mid-interaction. MEASURED across
     all six states at four narrow widths (29 July 2026) — the tallest
     natural state is 324px at a 375px viewport and 345px at 345px.
     Below 340px the copy wraps further and takes its own floor. If the
     copy changes, re-measure.

     THE 56px OF padding-bottom IS RESERVED FOR THE CONTROL, and it is
     not rhythm. The control is fixed to the bottom of the viewport and
     the stage is sticky near the top, so at the sticky floor exactly
     one thing can happen: the control lands on the last strip of this
     panel. Reserving it means the control lands on nothing at every
     viewport height, rather than on whichever sentence happened to be
     last — which on the paid states is "You have crossed the gate", the
     one sentence the gate exists to earn. */
  .line-stage .now { margin-top: 12px; }
  .now { min-height: 348px; padding-bottom: 60px; max-width: none; }
  .now h2 { font-size: clamp(1.45rem, 6vw, 1.9rem); }
  .now.is-system h2 { font-size: 1.1rem; }
  .now p { font-size: 16.5px; }
  .now .cost { margin-top: var(--s3); font-size: 1.45rem; padding: 12px 16px; }

  /* THE CONTROL comes back with the diagram it drives, and turns into a
     slim horizontal pill along the bottom.
     THE TALL VERTICAL FORM WAS TRIED FIRST AND IT COVERED THE CROSSING
     NOTE (screenshotted, 29 July 2026): at 274px tall it reached 196px
     up into the sticky stage, and the text it sat on was "You have
     crossed the gate", which is the single sentence the gate exists to
     earn. A phone has no margin to put a control in, so the answer is a
     control short enough to sit below the stage, plus the reserved
     strip on .now above so it cannot reach text even at the floor.
     The mini rail goes: the real measure is on screen, its marks are
     the same buttons, and a miniature of a diagram you can already see
     and touch is duplicate machinery. The counter carries position. */
  .stepper-float {
    left: 50%; right: auto; bottom: 10px; transform: translateX(-50%);
    flex-direction: row; align-items: center; gap: 10px;
    padding: 6px 8px; border-radius: var(--radius-pill);
  }
  .stepper-float .mini { display: none; }
  .stepper-float > button { width: auto; padding: 8px 18px; }
  .mini-count {
    display: block; font-family: var(--font-ui); font-weight: 600;
    font-size: .72rem; letter-spacing: .1em; text-transform: uppercase;
    color: var(--ink-mute); white-space: nowrap;
  }
}

/* Very narrow phones wrap the copy further, so the panel takes its own
   floor rather than letting the stage grow between states. */

/* The rail travels through the viewport exactly as the horizontal
   measure does, so the reader scrolls and the decisions advance.
   ARITHMETIC, not a round number, and MEASURED rather than derived on
   paper — an earlier version of this comment was arithmetically tidy
   and 77px wrong, because a `.line-stage .now` rule outranked the
   single-class one it was supposed to override. The pin measures 602 here
   (6 top + rail 236 + 12 gap + panel 348, the title costing nothing
   because it sits in empty space beside the rail) and the narrow-width
   header is 84, so 602 + 85 = 687.
   THIS NUMBER IS TIED TO THE HEADER'S HEIGHT. If the narrow-width
   header changes height, both figures below change with it.
   BELOW THE FLOOR THE STAGE STAYS PUT rather than being withdrawn, which
   is the whole difference from the old behaviour: a short phone loses
   the scroll-through and keeps the measure, with the floating control
   carrying the progression. That is the same fallback the desktop
   takes below its own floor, not a separate design. */
@media (max-width: 760px) and (min-height: 687px) {
  .measure-scroller { height: 300vh; }
  .measure-pin { position: sticky; top: 85px; }
}
@media (max-width: 760px) and (max-height: 613px) {
  .measure-scroller { height: auto; }
  .measure-pin { position: static; }
}

/* The swell propagates the same way; only the axis it swells on turns.
   A separate name rather than a redefined one, because redefining
   @keyframes inside a media query is a real source of silent breakage
   when two queries overlap. */
@keyframes peristalsis-across {
  0%   { opacity: 0; transform: scaleX(1); }
  40%  { opacity: 1; transform: scaleX(4); }
  100% { opacity: 0; transform: scaleX(1); }
}
@keyframes markWobble-across {
  0%, 100% { transform: translateY(0); }
  30%      { transform: translateY(-2.5px); }
  70%      { transform: translateY(2.5px); }
}

/* LAST, AND IT HAS TO BE LAST. The narrow-width block above re-asserts
   the track, the sticky stage and the two animation names, all of which
   sit in earlier reduced-motion blocks. Later rules win at equal
   specificity, so without this every one of those exemptions would be
   silently undone for exactly the readers they were written for. */
@media (prefers-reduced-motion: reduce) {
  .line-fill, .stop i { transition: none; }
  .measure-scroller { height: auto; }
  /* .measure-pin, NOT just .line-stage. When the sticky element was
     renamed from the stage to the pin, this block still named the old
     one and the track came back on for reduced-motion readers — caught
     by re-running the check, not by reading the diff. Second time this
     exact block has caught that, which is the argument for it existing
     rather than relying on the earlier blocks being complete. */
  .measure-pin, .line-stage { position: static; }
  .line-wave { display: none; }
  .stop.wobble i { animation: none; }
}

@media print {
  .site-header, .progress, .site-footer nav, .skip-link { display: none; }
  .line-stage { display: none; }
  body { font-size: 12pt; color: #000; }
  a { color: #000; }
}


/* ============================================================
   THE iMULTIPLY-MIRROR HEADER AND HERO — adopted 29 July 2026.
   Graeme's requirement: hit iMultiply recognition in the first moment
   of landing. Mirrors the parent's own first view — white header bar,
   logo, vertical rule, justified menu, two buttons right, then a dark
   teal photographic hero beneath.
   ============================================================ */
/* ---- header, mirroring the parent's structure ----
   MEASURED on imultiplyresourcing.com, 29 July 2026, rather than
   estimated: their bar is 103px tall and white, their logo renders at
   171x48, and the vertical rule beside it is 1px of lime #91C13E at
   68px tall. These match those.

   FROZEN ON SCROLL, like the parent's. Theirs is position: fixed; this
   uses sticky, which gives the identical result on a page that starts
   at the top and does not require the body padding-top compensation
   that fixed does. */
/* FULL-BLEED BAR, CENTRED CONTENT — corrected 29 July 2026 (Stuart
   asked whether the capped header was intentional; it was not).
   `max-width: 1320px` sat on the element that also carries the white
   background and the 1px bottom rule, so on any viewport wider than
   1320px the BAR stopped short, not just its contents. Because the
   header is sticky, that is a functional defect rather than a matter of
   taste: at 1585px the dark hero photograph scrolled up through a
   133px band on each side of the frozen white bar (screenshotted).
   The parent's own bar is full width. The cap now lives in the padding,
   so the content column is still 1320px and nothing else moves. */
.mirror-header {
  display: flex; align-items: center; gap: var(--s3);
  max-width: none; min-height: 103px;
  padding-top: var(--s2); padding-bottom: var(--s2);
  padding-left:  max(var(--s4), calc((100% - 1320px) / 2));
  padding-right: max(var(--s4), calc((100% - 1320px) / 2));
  position: sticky; top: 0; z-index: 30;
  background: #fff;
}
.head-rule { width: 1px; height: 68px; background: var(--lime); flex: none; }
.mirror-header .logo-link svg { width: 46px; height: 46px; }
.mirror-header .wordmark { font-size: 27px; }
.mirror-nav { flex: 1; justify-content: center; gap: var(--s4); }
.head-actions { display: flex; align-items: center; gap: var(--s2); }
.head-actions .btn { padding: 11px 22px; font-size: 14.5px; }

/* The parent's lime button, made legible. Its own version is white on
   #91C13E at 2.12:1, which fails AA; charcoal on the same lime is
   5.96:1 and passes, so the button stays recognisable and readable. */
.btn-parent {
  background: var(--lime); border-color: var(--lime); color: var(--ink);
}
.btn-parent:hover { background: #83b032; border-color: #83b032; color: var(--ink); }

/* ---- photographic hero ---- */
.hero-photo {
  position: relative; max-width: none; isolation: isolate;
  padding-top: var(--s7); padding-bottom: var(--s7);
  background: #1B4F5C;
}
.hero-photo::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: url('img/iMultiply-consultants.webp') center/cover no-repeat;
}
/* 0.85, not the parent's 0.66. At 0.66 the lightest region of this
   photograph leaves white body copy at 3.28:1 — the parent gets away
   with it because it carries only one very large headline, which is a
   3:1 bar. This hero carries a kicker and a lede too. */
.hero-photo::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: #1B4F5C; opacity: .85;
}
/* Same correction as section.ground-alt > * (29 July 2026), same cause.
   .hero-inner carried its own fixed var(--s4) on top of the section's
   padding. Above 1280 the 1180 cap bound and it happened to land right;
   below it the cap stopped binding, the two paddings added up, and the
   hero's text sat 32px right of every other section — the mirror image
   of the ground-alt defect. Capping at the text-column width and letting
   the section's own padding do the work aligns it at every size, narrow
   widths included, where the section padding drops to var(--s3). */
.hero-inner { position: relative; z-index: 2; max-width: calc(1180px - var(--s4) * 2); margin: 0 auto; padding: 0; }
.hero-photo h1 { color: #fff; max-width: 20ch; }
/* #BFE0E8 measured 4.36:1 against the lightest region of the photo at
   this wash — just under the 4.5 bar. Lightened until it clears. */
.hero-photo .kicker { color: #DCEEF2; }
.hero-photo .lede { color: #EAF2F4; max-width: 58ch; }

/* secondary action on a dark ground: outlined white, not teal */
.btn-onphoto { background: transparent; color: #fff; border-color: rgba(255,255,255,.75); }
.btn-onphoto:hover { background: rgba(255,255,255,.12); color: #fff; border-color: #fff; }

@media (max-width: 1000px) {
  .mirror-nav { display: none; }
  .head-rule { display: none; }
}

/* THE HEADER MADE THE PAGE SCROLL SIDEWAYS ON A PHONE, and it was doing
   so before this session's work — found 29 July 2026 by measuring
   scrollWidth against clientWidth rather than by looking, since the
   only visible symptom was the Register button being clipped at the
   right edge. At 375px the bar wanted 435px: a 46px mark, a 27px
   wordmark and two full-size buttons do not fit, and nothing capped
   them. The mark and wordmark go back to their base sizes and the
   buttons tighten; both buttons stay, because the lime one is the
   parent-brand tie Graeme asked for and the fuchsia one is the page's
   only conversion. The bar also loses 19px of height, which the sticky
   measure below gets back as budget. */
@media (max-width: 760px) {
  .mirror-header {
    gap: var(--s2); min-height: 84px;
    padding-left: var(--s3); padding-right: var(--s3);
  }
  .mirror-header .logo-link svg { width: 34px; height: 34px; }
  .mirror-header .wordmark { font-size: 21px; }
  .head-actions { gap: 8px; }
  .head-actions .btn { padding: 9px 13px; font-size: 13px; }
}
/* THE HEADER'S HEIGHT IS LOAD-BEARING, not just its width, and that is
   why this tier is wider than the overflow that prompted it.
   MEASURED across eleven widths (29 July 2026): the bar was 84px at
   375 and above but 101px between about 341 and 370, because the
   button labels wrapped to two lines rather than the bar overflowing.
   That broke the sticky measure rather than the header — the pin's
   offset is derived from the header's height, so a header 17px taller
   than assumed put the top of the diagram underneath it. Stuart saw it
   as the header being cut off.
   So: labels never wrap, and the tight tier covers every width where
   they would otherwise have to. */
.head-actions .btn, .mirror-header .wordmark { white-space: nowrap; }
@media (max-width: 420px) {
  .mirror-header { gap: 10px; padding-left: var(--s2); padding-right: var(--s2); }
  .mirror-header .logo-link svg { width: 30px; height: 30px; }
  .mirror-header .logo-link { gap: 7px; }
  .mirror-header .wordmark { font-size: 19px; }
  .head-actions { gap: 6px; }
  .head-actions .btn { padding: 8px 10px; font-size: 12.5px; }
}


/* ---------------- candidate register ----------------
   Charter section 10 puts For candidates at design variance 2, motion 0,
   density 3: a warmer register, no conversion goal, and NO commercial
   measure — nothing commercial is being claimed on this page, and the
   audience is the person being protected by the model rather than the
   one buying it.

   The consent guarantee is the one raised element. It carries the single
   most important trust message for this audience (iMPACT's AGENTS.md
   requires it stated plainly at least once per candidate-facing piece),
   so it is given a surface of its own rather than left in the flow. */
.consent-guarantee {
  background: var(--ground-alt); border-left: 3px solid var(--teal);
  padding: var(--s4); margin: var(--s4) 0; max-width: 68ch;
}
.consent-guarantee p { margin: 0; font-size: 17.5px; color: var(--ink); }
.candidate-q { border-top: 1px solid var(--rule); max-width: 76ch; padding: var(--s4) 0; }
.candidate-q:last-of-type { border-bottom: 1px solid var(--rule); }
.candidate-q h3 { font-family: var(--font-display); font-size: 1.28rem; margin: 0 0 var(--s2); }
.candidate-q p:last-child { margin-bottom: 0; }


/* ---------------- interior-page structure ----------------
   Carried over from the pre-overhaul pages so their approved copy keeps
   its shape. The released direction's decorations do NOT come with it:
   .node, .sep, .motif and .closing-band were part of the field grammar
   the OVERHAUL voided, and they are stripped from the markup rather
   than restyled. */
.page-head { max-width: 1180px; margin: 0 auto; padding: var(--s6) var(--s4) var(--s3); }

/* THE .article PAGES SAT ONE --s4 RIGHT OF THE SECTION-BUILT PAGES, at
   every width (Stuart, 29 July 2026: "When not and FAQ headers are not
   in same location as How it works and about").

   Cause: .article and section are both 1180px boxes with var(--s4)
   padding, but on an .article page the two NEST — .article pads, then
   .page-head or a child <section> pads again — so the text landed 64px
   inside the box against 32px on index/about/how-it-works. Consistent
   within each page, which is why it read as a deliberate reading column
   rather than a defect, and why only a cross-page comparison caught it.

   .article is now the single source of the inset and its children carry
   none. Everything on the site is now on one left edge. */
.article > .page-head,
.article > section { padding-left: 0; padding-right: 0; }

/* 24ch, not 22ch (Stuart, 29 July 2026). "When iMPACT is not the right
   fit" wants 742px on one line and the 22ch cap computed to 723.8px, so
   it dropped the single word "fit" to a second line — an orphan caused
   by an 18px shortfall, inside a column that is actually 1116px wide.
   24ch clears it with roughly 47px of headroom rather than sitting on
   the exact boundary, and is still a tight measure for display type.
   The only interior headline long enough to notice the change is this
   one; the register page sets its own 20ch and is unaffected. */
.page-head h1 { max-width: 24ch; }
.article { max-width: 1180px; margin: 0 auto; padding: 0 var(--s4) var(--s6); }
.article h2 { margin-top: var(--s6); }
.article h2:first-child { margin-top: 0; }

/* THIS RULE MUST STAY BELOW `.article h2` ABOVE. Both are (0,1,1), so at
   equal specificity source order decides, and written any higher up the
   file the margin-top here loses silently and the columns gain a 64px
   gap nobody asked for. Same trap that cost three fixes on 29 July.

   Why the headings are h2 at all: the not-a-fit columns were h3 sitting
   directly under the page h1, which skipped a level in the outline
   (screen-reader pass, 29 July 2026). The level is structural and had to
   change; the appearance did not, so the old h3 metrics are restored
   here rather than the visual design being altered to satisfy the
   outline. */
.fit-lists h2 {
  font-size: 1.3rem;
  line-height: 1.25;
  margin: 0 0 var(--s2);
  max-width: none;
}

/* .article now carries the whole inset for its pages, so it has to
   follow the same narrow-width reduction sections get, or the .article
   pages sit 8px right of the rest below 860px.

   THIS MUST STAY BELOW THE .article RULE ABOVE. Both are single-class
   selectors, so source order decides, and putting it any earlier makes
   it silently do nothing. That mistake was made three times in this
   file in one session — on the register block, on .page-head, and on
   this rule itself — each time producing CSS that looked correct and
   changed nothing. Check the rendered value, never the source. */
@media (max-width: 860px) {
  .article { padding-left: var(--s3); padding-right: var(--s3); }
}
.form-note { font-size: 14px; color: var(--ink-mute); max-width: 60ch; }
.diagram-figure { margin: var(--s4) 0; max-width: 100%; overflow-x: auto; }
.diagram-figure svg { max-width: 100%; height: auto; }
.s9-ladder { margin: var(--s4) 0; }


/* ============================================================
   SHORT-WINDOW BANDS — THESE MUST BE LAST, AFTER .mirror-header.
   They were written 850 lines earlier at first and did nothing:
   .mirror-header re-declares `position: sticky` below its own
   definition, so at equal specificity the later rule won, the header
   stayed frozen, and the diagram slid underneath it instead (Stuart
   saw the header cut off; the check that would have caught it is a
   render, which is how it was found). Anything that overrides a
   component declared late in this file has to come after it.
   ============================================================ */
/* THE HEADER UNFREEZES ON A SHORT WINDOW, and this is the single
   biggest lever available (Stuart, 29 July 2026).
   The frozen header is 104 of that 695. Freezing it is what raised the
   threshold in the first place: `git log -S` puts the frozen bar and
   the original 730 threshold in the same commit, and the comment
   written there says the freeze "spends about 100px of the height
   budget the sticky diagram needs". So on a window too short to carry
   both, the header gives way and the diagram keeps the behaviour --
   the reader loses a frozen bar they can scroll back to in a moment,
   rather than losing the progression that carries the argument.
   Pin 581 + 12 = 593, so the diagram now sticks on any window down to
   593px rather than the 730 it started at.

   UNFREEZING AT 695 RATHER THAN THE 800 FIRST DISCUSSED, deliberately:
   between 695 and 800 the frozen header and the sticky diagram both
   fit, and giving up the parent-mirror effect there would cost
   Graeme's requirement for nothing in return. It gives way only where
   it actually blocks something. */
/* min-width GUARD, and it is not decoration. Without it this band also
   matched phones, and because it sits after the narrow-width fallback
   it switched sticky back on below the phone's own floor of 642 — at
   375x641 the pin stuck and clipped its own last stop. Caught by the
   band sweep, which is the reason the sweep tests one pixel either
   side of every boundary rather than a few round numbers. */
@media (min-width: 761px) and (max-height: 684px) and (min-height: 593px) {
  .mirror-header { position: static; }
  .measure-pin { position: sticky; top: 12px; }
}

/* Same trade as the desktop band above, and it matters more here: the
   84px header is a bigger share of a phone's screen than 103px is of a
   laptop's. Pin 602 + 12 = 614, which brings an SE-class 667px phone
   inside the progression for the first time. */
@media (max-width: 760px) and (max-height: 686px) and (min-height: 614px) {
  .mirror-header { position: static; }
  .measure-scroller { height: 300vh; }
  .measure-pin { position: sticky; top: 12px; }
}

/* and the reduced-motion exemptions go after those, for the same
   last-rule-wins reason they were given their own trailing block */
@media (prefers-reduced-motion: reduce) {
  .measure-scroller { height: auto; }
  .measure-pin, .line-stage { position: static; }
}

/* ============================================================
   WCAG 2.2 SC 2.4.11 — FOCUS NOT OBSCURED (MINIMUM), AA.
   Added 29 July 2026 by the interface-design review of the
   progression control.

   THE DEFECT THIS FIXES WAS SITE-WIDE, NOT MEASURE-SPECIFIC. The
   header is `position: sticky` and 103px tall at desktop (84px on a
   phone), and NOT ONE of the 35 focusable elements on Home carried a
   `scroll-margin-top`, with no `scroll-padding-top` on :root or body
   either. That is exactly the failing condition: tab to a control that
   is off-screen, the browser scrolls the minimum needed to reveal it,
   and the sticky bar lands on top of it. It bites hardest on the six
   decision stops, because a keyboard reader moves through them in
   sequence and the diagram sits mid-page.

   IT ALSO CORRECTS THE RECORD. B232's F5 assessed 2.4.11 as "likely
   clean (no sticky/fixed anywhere in the stylesheet)". That was true
   when it was written and the rebuild inverted it by introducing the
   frozen header — the assumption outlived the condition it rested on.

   scroll-padding on the scroll container is used rather than
   scroll-margin on ~35 individual elements: one declaration, no way for
   a new focusable to be added later and quietly miss it.

   The values clear the header plus a little breathing room, and are
   tied to the same breakpoint the header's own height changes at. If
   the header height changes, THESE CHANGE WITH IT.
   ============================================================ */
html {
  scroll-padding-top: 115px; /* 103px header + 12px */
}

@media (max-width: 760px) {
  html { scroll-padding-top: 96px; } /* 84px header + 12px */
}

/* When the header unfreezes on a short window it is `position: static`,
   so it no longer covers anything and the padding is dead weight that
   would push focused elements needlessly far down the page. These two
   conditions mirror the two unfreeze bands above exactly. */
@media (min-width: 761px) and (max-height: 684px) {
  html { scroll-padding-top: 12px; }
}
@media (max-width: 760px) and (max-height: 686px) and (min-height: 614px) {
  html { scroll-padding-top: 12px; }
}
