/* ==========================================================================
   EVAA — THE EXCELSIOR NETWORK
   Built 2026-07-30 from the CLIENT'S OWN copy doc
   (1Y1WiPkbCDz9W2tq8Vhds_s8_ynG22Ok89v0Rch04X6g), VERBATIM, in the doc's order.

   ⚠️ ADDITIVE ONLY. Nothing appended to site.css, v4.css, about.css or
   team.css. This page loads site.css + v4.css + this file.

   🔴 SEAN ASKED FOR THIS PAGE PERSONALLY AND IT WAS SIX DAYS OWED.
   "I think this page needs to be highlighted and is potentially one of the most
   important parts of the website and our mission." So it gets the site's full
   vocabulary — triptych hero, spine, held argument, image close — plus the two
   blocks below that genuinely had no existing home.

   ---- SECTION VOCABULARY (rewritten 7/31 — the old list was three rounds
        stale and still described §1 as a triptych and §2 as a spine) --------
     §1 full-bleed video + masked line rise   ⚠️ REJECTED by Brees round 4 —
                                             the .74 scrim leaves the footage
                                             muddy. Rebuild pending.
     §2 centred statement + BENTO BOXES       ✅ approved round 5
     §3 two-column + band image               ⚠️ rejected, no device yet
     §4 "Forward in ___" rotator              ✅ approved
     §5 the don't/do split                    ⚠️ rejected round 4
     §6 THE CONSTELLATION                     ✅ "the strongest one so far"
                                              — DO NOT TOUCH
     §7 THE CHARTER (native <details>)        ✅ approved
     §8 measures + larger-vs-better           ⚠️ rejected — "looks like Word"
     §9 the creed + closing rise              ✅ approved

   ---- COLOUR DISCIPLINE ---------------------------------------------------
   Red belongs to CREAM grounds, gold to NAVY, neither crosses (site.css).
   🔴 GOLD APPEARS EXACTLY TWICE — the rule above the hero motto and §4's
   rotating word. The rule WAS "exactly once"; Brees was shown it and chose a
   single deliberate exception on 7/31 to get colour into an all-type section.
   **Do not add a third.** v1 died as "a bank or lawyer site" on navy + gold
   everywhere — two is not everywhere. Asserted in network-measure.js, and the
   same amendment is recorded in site.css's palette block. ⚠️ Those two notes
   must always agree.
   Ground order: navy → navy → cream → navy → cream → band → navy → cream →
   dark close. The hero/intro dark pair is the site's established lead-in.
   No gradients. No coloured left borders. (Standing house rules.)

   ---- 📌 THE CASCADE LESSON, SEVEN FOR SEVEN NOW --------------------------
   Six bugs on About, PWU and Team came from a rule silently losing and looking
   like a design choice. Five were specificity COUNT (a bare class beaten by an
   element-descendant rule); the sixth was the right count with a wrong
   COMBINATOR (`.v4-sec--dark .tm-intro` where both classes sat on ONE element).
   THE RULE: remove the competition where you can, out-specify it where you
   can't, and ASSERT THE COMPUTED OUTCOME either way.
   🔴 THIS PAGE PRODUCED THE SEVENTH, AND ITS CAUSE WAS THIS COMMENT.
   An earlier version of this header asserted that "on THIS page every
   pull-statement lives in a container that has no competing element rule, so
   single classes are correct and deliberate." That was written BEFORE
   measuring, and it was false: five of them sit inside `.net-why__col`,
   `.net-member__body` and `.net-lasts__body`, whose shared body rule is
   (0,2,1). They shipped at 17px body size in a serif face until
   `.shots/network-measure.js` measured them.
   ⚠️ Only §2's thesis genuinely sits outside its body container, and it is the
   only single-class pull statement here. Every other one is (0,3,0).
   `node .shots/network-measure.js` asserts the computed sizes and colours —
   run it, do not reason about it.
   ========================================================================== */

/* --------------------------------------------------------------------------
   §1 — HERO — THE ASCENT
   🔴 REBUILT 7/30. It was `.triptych`, which is ALSO the hero on About, Team
   and Partner With Us — so the site's most important page opened exactly like
   the three ordinary ones. Brees: this page must be "more unique as the
   homepage is to the inner pages."

   THE MECHANIC IS THE MEANING: each line is masked and RISES into place.
   "Excelsior" means Ever Upward; §4 spends a whole section defining it; so the
   first thing the page does is perform it. Adapted from 21st.dev's masked
   line-reveal family (18595 / 19257), translated to vanilla CSS like every
   other set piece here.

   🟢 ZERO JAVASCRIPT, DELIBERATELY. This is a keyframe with per-line delays,
   not a `.reveal`. `.reveal` depends on main.js adding `.is-in` — which is
   exactly why every page here carries a <noscript> escape — and the hero is the
   one thing guaranteed to be seen, so it should not depend on a script that may
   not run. These lines animate with JS disabled.
   -------------------------------------------------------------------------- */
/* 🔴 SPLIT, NOT FULL-BLEED — REBUILT ROUND 5. See the markup note.
   The section no longer needs `position:relative` for a scrim stack, and it no
   longer has padding of its own: the copy panel carries the padding so the
   video panel can run edge to edge. */
/* ⚠️ `padding: 0` IS LOAD-BEARING. `.phero--dark` in site.css carries a 96px
   bottom padding for the full-bleed heroes it was written for, which left a
   96px navy band hanging below the video panel — the section measured 766px
   against a 670px grid. Same weight (0,1,0) as `.phero--dark`, and network.css
   loads after site.css, so source order settles it. Scoped to `.net-hero` so
   the four other pages using `.phero--dark` are untouched. */
.net-hero { background: var(--navy-deep); padding: 0; }
/* 🔴 NOT 50/50 ANY MORE — 8/4. Brees: "have equal amount of space to the right
   and left of the map."
   Measured first: the map was 26px from the viewport edge and 279px from the
   copy, and **the 253px difference was a constant at every width**. That
   constant is the copy panel's empty LEFT half — the copy is a narrow 30ch
   block anchored to the page gutter, so a 50/50 split leaves a void between the
   map and the text that no amount of centring inside the media panel can fix.
   The map was already perfectly centred in its own column; the column was the
   problem.

   🟢 THE STRUCTURAL FIX, WHICH NEEDS NO MAGIC NUMBERS: size the copy column to
   its CONTENT (`auto`) and give the video column everything else (`1fr`). The
   video column then ends exactly where the copy begins, so `object-fit: contain`
   centring the map inside it produces equal space on both sides automatically,
   at every width, with nothing to tune.
   ⚠️ It only holds while the copy panel has NO horizontal padding — any
   padding-left there pushes the text right and re-opens the gap by exactly that
   amount. That is why the panel's inline padding is 0 below.
   📌 I nearly built this with `calc(253px - 8vw)` instead, solved from the
   split, the gutter and the 303px text width. Three coupled magic numbers — the
   exact species of bug that put the hero 44px off the page grid this morning.
   Content-sizing the column makes the relationship structural instead.

   🔴 THE RIGHT GUTTER MOVED UP TO THE GRID, AND THAT IS LOAD-BEARING. It used
   to live on the copy panel as `calc(100% - maxw/2 + gutter)`, which silently
   assumed the panel was exactly half the section — true only for a 50/50 split.
   With the columns no longer equal, that formula sends the copy past the page
   grid. Here on the grid, `100%` IS the full section width, so
   `(100% - maxw)/2 + gutter` is the real distance to the page's right gutter —
   and it is scrollbar-exact, unlike `100vw`. */
.net-hero__grid {
  /* 🔴 AN EXPLICIT COLUMN WIDTH, NOT `auto`. The first cut used `auto` and the
     column came out 375px against a 303px text block — grid's max-content
     sizing did not respect the child's `max-width: 30ch` — so the copy sat
     left-aligned in a too-wide column and its right edge landed 72-80px SHORT
     of the page gutter. That silently broke the alignment with §8's prose that
     was established this morning, while the map's gaps still measured equal.
     📌 Two things can be right and the layout still wrong: the gap check passed
     throughout because it never looked at the copy's right edge.
     Pinning the column to the copy's own width makes the text fill it, so its
     right edge is the grid's content edge — which IS the page gutter. */
  --hero-copy-w: 30ch;
  display: grid; grid-template-columns: minmax(0, 1fr) var(--hero-copy-w);
  align-items: stretch; min-height: clamp(440px, 58vh, 620px);
  padding-right: max(clamp(32px, 5vw, 80px),
                     calc((100% - var(--maxw)) / 2 + var(--gutter)));
}
/* ⚠️ Below 860px the copy column would win the fight for space and squeeze the
   video to nothing, which is worse than the 50/50 it replaces. The hero stacks
   instead — and this is also the first mobile rule the hero has ever had; it
   previously held a 50/50 split down to 390px, where the copy column was 195px
   wide with a 139px measure. */
@media (max-width: 860px) {
  .net-hero__grid { grid-template-columns: minmax(0, 1fr); padding-right: 0; }
  .net-hero__media { grid-column: 1; grid-row: 2; min-height: 46vh; }
  .net-hero__panel { grid-column: 1; grid-row: 1;
    padding-inline: var(--gutter); }
}
/* The copy panel keeps the site's gutter on the left but lets the type breathe
   toward the split; `--wrap-max` logic does not apply here because the panel is
   a half, not a centred column. */
/* 🔴 FLIPPED 8/4 — VIDEO LEFT, COPY RIGHT. Brees' call.
   ⚠️ DONE IN CSS, NOT BY REORDERING THE MARKUP, AND THAT IS DELIBERATE. The
   <h1> stays first in the DOM so the reading and tab order still open with the
   page's heading rather than with a decorative, aria-hidden video panel. Visual
   order and DOM order are allowed to disagree here precisely because the thing
   moving left carries no content — if that video ever gains a caption or a
   control, this needs revisiting (WCAG 1.3.2 meaningful sequence).
   ⚠️ `grid-row: 1` on both is explicit. With one item placed by hand, leaving
   the other to auto-placement is how a second row silently appears. */
/* 🔴 SCOPED TO ≥861px ON 8/10, AND THE SCOPE IS THE WHOLE POINT.
   These two declarations were appended here on 8/4 with the flip. They carry
   the SAME SPECIFICITY (0,1,0) as the mobile rules in the `max-width: 860px`
   block above, and they come LATER in the file — so source order won at every
   viewport and the stack above never applied even once.
   The cost was invisible on desktop and total on a phone: at 390px the grid
   stayed two-column (`grid-template-rows: 522px`, one row), the video track
   collapsed to 42px against a 341px copy column, and the US map — a 1014×588
   landscape clip — rendered as a 42×442 vertical sliver over the lede.
   📌 This file's own round-7 notes already recorded the rule: AN OVERRIDE
   WRITTEN ABOVE THE RULE IT OVERRIDES NEVER LANDS. A media query placed before
   an unconditional rule is exactly that shape.
   ⚠️ Do not un-scope these to "simplify". The min-width guard is what makes the
   860px stack reachable. */
@media (min-width: 861px) {
  .net-hero__panel { grid-column: 2; grid-row: 1; }
  .net-hero__media { grid-column: 1; grid-row: 1; }
}

.net-hero__panel {
  display: flex; align-items: center;
  /* 🔴 ZERO INLINE PADDING, AND IT IS LOAD-BEARING. The grid column is exactly
     the copy's width and the grid's own padding-right pays the page gutter, so
     the panel must add nothing horizontally. Any padding here pushes the copy
     inward and re-opens the very gap this layout closed.
     🔴 A STALE `padding-right: max(…, calc(100% - maxw/2 + gutter))` SURVIVED
     HERE FOR ONE ROUND AND COST 80px. It was the old 50/50 gutter rule; with
     the panel now only 303px wide its calc went negative, the `max()` fell back
     to the 80px floor, and the copy stopped 80px short of the gutter — while
     every gap measurement still read "equal". 📌 **A `max()` with a floor fails
     SOFT: when its real term stops making sense it quietly serves the floor
     instead of erroring.** Delete superseded declarations; do not leave them to
     be overridden.
     ⚠️ `justify-content: flex-end` is also gone and must not return — the panel
     is now exactly its content's width, so it has nothing to push against, and
     the 253px void it used to create is what started this.
     🔴 `padding-block`, NOT THE `padding: … 0` SHORTHAND (8/10). The shorthand
     also wrote inline padding to 0, and because this rule sits AFTER the
     `max-width: 860px` block it silently cancelled that block's
     `padding-inline: var(--gutter)`. Once the hero actually started stacking,
     the mobile copy ran flush into the left edge of the screen with no gutter
     at all. The zero is still correct at ≥861px — it is just not correct
     everywhere, and a shorthand cannot say "here but not there".
     📌 Second instance in this one file of a later unconditional rule eating a
     media query. When a rule is load-bearing for ONE breakpoint, scope it. */
  padding-block: clamp(48px, 7vw, 104px);
}
@media (min-width: 861px) {
  /* the load-bearing zero, now stated where it is actually true */
  .net-hero__panel { padding-inline: 0; }
}
/* 🔴 `width: 100%`, AND `max-width` IS GONE. This is a FLEX ITEM, so it
   shrink-wrapped to its longest line — "Excelsior" at 228px — rather than
   filling the 303px column. The copy's right edge therefore stopped 72px short
   of the page gutter even though the grid column was exactly the right width.
   The cap now lives on the grid column (`--hero-copy-w`); a second cap here
   would just be a duplicate that can disagree with it. */
.net-hero__text { width: 100%; }
.net-hero__media { position: relative; overflow: hidden; }
/* 🔴 `contain`, NOT `cover` — 8/4, when the hero clip became the US map.
   Brees boxed the panel and asked for the map to be larger and fill more of it.
   `cover` cannot do that here and made it WORSE: the map is a single wide
   composition spanning nearly the whole frame, so any cover-crop eats coastline.
   The first attempt padded the source to 1280x1000 so cover would eat padding
   instead — which protected the map but shrank it, because cover then scaled to
   the padded frame rather than the map.
   🟢 `contain` inverts the problem: the map is scaled to FIT, so it fills the
   panel's width at every viewport and is never cropped. Measured at 1900px it
   renders ~950px wide against ~769px under the padded-cover version — about 23%
   larger — and the wasted vertical padding is gone entirely.
   ⚠️ THIS ONLY WORKS BECAUSE THE LETTERBOX IS INVISIBLE. `contain` leaves bars
   above and below, and they are only acceptable because the clip's own
   background was colour-matched to `--navy-deep` (#121D3B) in the encode — a
   black-point lift, verified as an exact match. If the clip is ever re-encoded
   without that lift, the bars become visible and this must go back to `cover`.
   ⚠️ Also only safe because the source is cropped tight to the map. A source
   with its own framing would float inside the panel. */
/* 🔴 INSET 7.7%, NOT `inset: 0` — 8/4. Brees marked a box: the map should be a
   bit smaller. Measured, the box is 940px wide against a rendered map of 1112px,
   i.e. 0.845 — so 7.7% off each side.
   ⚠️ AN INSET IN PERCENT, NOT PADDING ON THE PANEL. `contain` is bound by
   whichever axis runs out first, and that axis CHANGES with viewport — the map
   is height-bound at 1900px and width-bound at 1440px. Padding only bites on the
   binding axis, so the same padding would have shrunk the map by 15% at one
   width and 8% at another. A percentage inset scales the box on both axes, so
   the reduction is the same everywhere.
   🟢 It also preserves the equal left/right gaps: the box stays centred, so both
   gaps grow by the same amount. */
/* ⚠️ EXPLICIT WIDTH/HEIGHT, NOT `inset` ALONE. `inset: 7.7%` with `width: auto`
   looks right and does nothing: a <video> is a REPLACED element, so `auto`
   resolves to its INTRINSIC size (1014x588 here), the insets over-constrain, and
   the browser drops the `right`/`bottom` ones. The box came out 1014px wide at
   every viewport — the map rendered an identical 958px at 1440 AND 2560, which
   is the tell. Replaced elements need the size stated. */
.net-hero__video {
  position: absolute; top: 7.7%; left: 7.7%;
  width: 84.6%; height: 84.6%;          /* 100% - 2 x 7.7% */
  object-fit: contain; z-index: 0;
}
/* `.net-hero__scrim` DELETED round 5 — the split hero has no scrim at all.
   That is the whole point of the rebuild: type and video no longer compete for
   the same pixels, so nothing needs compositing and nothing needs deriving. */
/* 🔴 NOT `ch` ON THE CONTAINER. The first cut wrote `max-width: 24ch` here and
   it resolved against THIS element's font-size — the 16px body face, not the
   57.6px title inside it — so the box came out 242px and crammed a three-word
   headline onto three lines while two thirds of the hero sat empty. Measured,
   not spotted: .shots was reading 242px while the title's own cap was 557px.
   ⚠️ `ch` is only meaningful on the element whose text it measures. Cap
   containers in px/%, cap the type in ch. */
.net-hero .phero__lede { margin-bottom: var(--sp-2); max-width: 30ch; }

/* 🔴 RESTORED 7/30 — THESE TWO RULES WERE ACCIDENTALLY DELETED AND THE MARKUP
   STILL USED THEM. The §1 rewrite replaced everything between the "§1 — HERO"
   and "SHARED — headings" comments, and `.net-motto` lived inside that range,
   so the page's ONE gold element silently vanished — computed background came
   back rgba(0,0,0,0). A screenshot showed "just some space" above the motto,
   which is indistinguishable from intent.
   ⚠️ When replacing a whole commented CSS block, check what else lives between
   the markers. This is the mirror image of the orphan problem the project keeps
   fighting: not a rule left with no consumer, but a consumer left with no rule.
   ⚠️ Left-aligned now (it was `margin: 0 auto` for the centred triptych). */
.net-motto {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-statement); letter-spacing: .01em;
  color: var(--cream); margin: 0;
}
.net-motto::before {
  content: ""; display: block;
  width: 28px; height: 2px; background: var(--gold);
  margin: 0 0 var(--sp-0);
}

/* THE MASK. The parent clips; the <span> is the part that travels.
   ⚠️ overflow:hidden here is the mask and is LOAD-BEARING. Safe in this
   section because nothing inside the hero is position:sticky — unlike homepage
   §7 and every [data-pinned-bg] section, where the same property silently
   turns sticky into a no-op. */
.net-rise { overflow: hidden; }
.net-rise > span {
  display: block;
  transform: translateY(110%);
  animation: netRise .95s var(--ease) forwards;
  animation-delay: var(--rise-delay, 0s);
}
@keyframes netRise { to { transform: translateY(0); } }

@media (prefers-reduced-motion: reduce) {
  /* No rise. The text is simply present — which is also what it looks like
     after the animation finishes, so nothing is lost. */
  .net-rise > span { transform: none; animation: none; }
}

/* --------------------------------------------------------------------------
   SHARED — headings
   Same reasoning as .ab-h2 / .tm-h2: the doc gives each section a title AND a
   subtitle, so the pair is real here (unlike About, where the second slot had
   nothing to fill it and one mid-scale heading was used instead).
   ⚠️ A SEPARATE class from .ab-h2 / .tm-h2 on purpose — those stylesheets are
   not loaded here, and coupling pages that have no reason to move together is
   how orphans start.
   -------------------------------------------------------------------------- */
/* =====================================================================
   THE TYPE SCALE  (round 10)
   🔴 WRITTEN AFTER A MEASUREMENT, NOT A HUNCH. Brees: "the spacing is
   weird, formatting, so many different font weights..."
   `node .shots/type-audit.js` counted what was actually on the page:
       39 distinct type treatments
       26 distinct font sizes
       4  font weights (400 / 500 / 600 / 700)
       10 distinct vertical gaps
   and — the finding that explains the feeling — **18 of the 26 sizes sat
   within 3px of each other**, everything between 12px and 28px. Nothing in
   that range was distinguishable from anything else, so nothing won and the
   eye had no ladder to climb.

   📌 HOW IT HAPPENED: every round added a size to solve a LOCAL problem
   ("raise this statement", "recess that lede") and nobody ever looked at the
   total. Exactly the same failure mode as the register count in §5 and §8.
   Round 3 called out SEVEN treatments in ONE section as the fault; the page
   had reached 39 across nine.

   FIVE STEPS. Everything on this page uses one of them — no exceptions, no
   "just this once" in-between value. If a new element needs a size that is
   not here, the answer is almost always that it belongs at an existing step.
   ⚠️ `.shots/type-audit.js` re-counts this on demand. Run it after any pass
   that touches type; drift is invisible section by section and obvious in
   the totals.
   ===================================================================== */
:root {
  --t-display:   clamp(1.9rem,  4.2vw, 3rem);      /* ~48px  the loudest line in a section */
  --t-heading:   clamp(1.6rem,  3vw,   2.125rem);  /* ~34px  section <h2> and vow names */
  --t-statement: clamp(1.25rem, 2vw,   1.5rem);    /* ~24px  the one big sentence */
  --t-body:      clamp(1.08rem, 1.55vw, 1.2rem);   /* ~19.2px all prose */
  --t-ui:        clamp(.88rem,  1.1vw, .94rem);    /* ~15px  eyebrows, small labels */
  --t-micro:     12px;                             /*        numerals and ::before counters */

  /* --- SPACING (round 10) -------------------------------------------
     🔴 site.css ALREADY HAS A SPACING SCALE (--s1..--s8 = 8/16/24/32/48/
     64/96/128). This file had quietly stopped using it and started inventing
     one-off clamps instead — clamp(36px,5vw,64px), clamp(44px,6vw,80px),
     clamp(20px,2.6vw,32px) and so on — which is why the audit found TEN
     distinct vertical gaps and why Brees said "the spacing is weird."
     📌 Nobody decided to abandon the scale; each round just needed "a bit more
     room here" and reached for a fresh clamp. Same drift as the type sizes.
     Four fluid steps, all built on the existing scale. Use these, not a new
     clamp — a one-off value is how the last ten got here. */
  --sp-0: var(--s2);                    /* 16    inside a line */
  --sp-1: var(--s3);                    /* 24    inside a group */
  --sp-2: clamp(32px, 3.4vw, 48px);     /* 32-48 between elements */
  --sp-3: clamp(48px, 5.5vw, 80px);     /* 48-80 between blocks */
  --sp-4: clamp(64px, 8vw, 120px);      /* 64-120 major break */
}

.net-h2 {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-heading); line-height: 1.18;
  color: var(--navy); max-width: 22ch; margin: 0;
}
.net-h2::before {
  content: ""; display: block;
  width: 28px; height: 2px; background: var(--red);
  margin-bottom: 16px;
}
/* On navy the red rule measures 3.05:1 and muddies — site.css's rule. */
.v4-sec--dark .net-h2 { color: var(--cream); }
.v4-sec--dark .net-h2::before { background: rgba(251,247,239,.35); }

/* `.net-sub` DELETED round 7 — the doc's subtitle line under each heading.
   All six instances became `.net-eyebrow` and moved ABOVE the heading; the
   words are unchanged. See the eyebrow block below for why. Kept as a note
   rather than a live rule so the next person greps `net-sub` and finds the
   answer instead of a dead selector. */

/* Shared body type. Set once here rather than per section — the doc's voice is
   uniform and eight separate rules would be eight chances to drift. */
.net-intro__body p .v4-sec--dark .net-intro__body p { color: rgba(251,247,239,.82); }

/* --------------------------------------------------------------------------
   §2 — MORE THAN A NETWORK (navy)
   -------------------------------------------------------------------------- */
/* 🔴 §2 RESTRUCTURED 7/31 pm. It was h2 + a four-paragraph spine column beside
   a tall figure — the same shape as About §2 and Team §2, which is exactly what
   Brees said had to stop ("I want section two to NOT be the same as it is on
   the other interior pages"). It is now three bands: a copy+picture row, the
   ledger, then the thesis. `.v4-spine` came off the body with it. */
/* `.net-intro__grid` and `.net-intro__body` were DELETED in round 4 with the
   markup that used them — §2 is now a centred head plus a bento, with no
   two-column copy/figure row at all. Removed rather than left behind, along
   with their 860px collapse. */

/* --------------------------------------------------------------------------
   §2 — CENTRED STATEMENT + FEATURE BOXES (round 4)
   See the markup note. Brees asked for centred text with feature boxes under
   it; the ledger's two-column version (round 3) is superseded by this.
   ⚠️ The bento COMPOSITION is from 21st.dev's `feature-bento` (18898) — one
   large media card plus smaller information cards, deliberately asymmetric so
   it does not read as a card grid. **None of its styling was taken**: that
   component is gradient-filled with SaaS stat tiles, and gradients are banned
   house-wide. These are flat --navy panels on --navy-deep.
   -------------------------------------------------------------------------- */
.net-intro__head { max-width: 62ch; margin-inline: auto; text-align: center; }
/* The centred variant of the shared heading. The red/cream rule above it is
   generated content and left-aligned by default, so it needs its own centring
   — a heading that centres while its rule stays left looks like a bug. */
.net-h2--center { margin-inline: auto; text-align: center; max-width: 20ch; }
.net-h2--center::before { margin-inline: auto; }

/* =====================================================================
   THE SLASH EYEBROW (round 7) — from subduxion.com, which tags every
   section "/ OUR SERVICES", "/ SOLUTIONS", "/ ABOUT US". That taxonomy is
   most of why nine sections there read as one designed system instead of
   nine essays.

   🔴 THE TEXT IS SEAN'S, NOT OURS. Brees' ruling on the reference pass was
   "I don't want to make up words." Inventing category labels (THE STANDARD,
   THE STORY) was the obvious way to do this and is exactly what he ruled
   out; truncating each <h2> was the other option and reads redundantly
   ("/ MEMBERSHIP" over "Membership in The Excelsior Network").
   So the eyebrow is the kicker line Sean already wrote for each section —
   the old `.net-sub`, MOVED from below the heading to above it. Those lines
   ("Independent. Connected. Stronger Together." / "Growth Through
   Excellence.") always were kickers; under the h2 they read as an
   afterthought, over it they orient you before you read the heading.
   ⚠️ NOT `text-transform: uppercase` — several are full sentences and caps
   makes them shout. Letterspacing and scale carry the eyebrow register.

   ⚠️ §1 (hero) and §9 (the close) deliberately have NO eyebrow: every
   ARGUMENT section is labelled, the opening and the closing are not. That
   is the system, not an omission — do not "complete the set".
   ===================================================================== */
.net-eyebrow {
  display: flex; align-items: baseline; gap: 10px;
  font-family: var(--font-body); font-weight: 500;
  font-size: var(--t-ui);
  letter-spacing: .06em; line-height: 1.35;
  color: var(--navy-soft);           /* 5.94:1 on cream */
  margin: 0 0 var(--sp-1); max-width: 46ch;
}
.net-eyebrow::before {
  content: "/"; flex: 0 0 auto;
  color: var(--red);                 /* 5.09:1 on cream — the cream-side accent */
  font-weight: 700; font-size: 1.05em; letter-spacing: 0;
}
/* 🔴 CREAM ON NAVY, NOT GOLD — Brees' explicit ruling. Gold on this page is
   spent EXACTLY TWICE (hero motto, §4's rotating word) and a gold slash on
   every dark section would be a third, fourth and fifth. Red is a cream-side
   accent only (3.05:1 on navy-deep — fails as text), so dark sections take
   cream at .82 (10.4:1) rather than either accent. */
.v4-sec--dark .net-eyebrow { color: rgba(251, 247, 239, .82); }
.v4-sec--dark .net-eyebrow::before { color: rgba(251, 247, 239, .82); }

/* The eyebrow replaces the bare 28px rule as the section's opening mark —
   two marks stacked above one heading is one too many. */
.net-eyebrow + .net-h2::before { display: none; }
.net-eyebrow--center { justify-content: center; text-align: center; margin-inline: auto; }
.net-intro__lede {
  color: rgba(251,247,239,.82); font-size: var(--t-body);
  line-height: 1.75; margin: var(--sp-2) auto 0; max-width: 56ch;
}

/* ══ ROUND 12 (8/3) — THE KEEP AND THE GAIN ═══════════════════════════════
   DELETED, not orphaned: `.net-bento`, `.net-box`, `.net-box--media`,
   `--keep`, `--share`, `--thesis`, `.net-box__bg`, `.net-box__scrim`,
   `.net-box__lead`, `.net-box__list`.

   🔴 THE BENTO WAS WHY THE SECTION READ FLAT. Its panels were navy fills on a
   navy ground — the contrast between box and background was so slight they
   registered as smudges rather than structure, and the rounded corners added
   the AI-card look for nothing. Four identical boxes also flattened Sean's
   actual argument, which is not four things: it is TWO things in opposition.
   📌 A container that does not read is worse than no container. It costs the
   card look and buys no legibility.
   🟢 What replaces it is a hairline and a type scale. `--keep` and `--share`
   sit side by side with a rule between them, so "you keep everything, and you
   gain this" is stated by the layout instead of hidden inside two boxes that
   look identical.
   ⚠️ The old `.net-box--thesis` also carried a background photo and a 0.70
   scrim. Both are gone; the thesis now sits on plain navy, which removes the
   one text-over-image contrast risk in this section. `network-contrast.js`'s
   "§2 thesis box background" block was retired with it — it measured a scrim
   that no longer exists.
   -------------------------------------------------------------------------- */

/* Head: copy left, photograph filling what used to be dead space top-right. */
.net-intro__head {
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  gap: clamp(32px, 5vw, 80px); align-items: center;
  max-width: none; margin-inline: 0; text-align: left;
}
.net-intro__headtext { max-width: 46ch; }
.net-intro__fig { margin: 0; }
.net-intro__fig img {
  width: 100%; height: clamp(260px, 30vw, 400px);
  object-fit: cover; display: block;
}

/* THE PAIR. ⚠️ The divider is a pseudo-element on the grid, not a border on a
   child — a border would belong to one side and imply it owns the boundary.
   This rule sits BETWEEN them and belongs to neither, which is the point. */
.net-pair {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(32px, 5vw, 88px);
  margin-top: var(--sp-3); position: relative;
}
.net-pair::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 50%;
  width: 1px; background: rgba(251, 247, 239, .16);
}
.net-pair__lead {
  font-family: var(--font-body); font-weight: 500; font-size: var(--t-ui);
  letter-spacing: .02em; color: #AEB6CC;     /* 7.31:1 on navy-deep */
  margin: 0 0 var(--sp-1); max-width: 34ch;
}
/* The items carry the weight — they are the nouns a practice owner is actually
   scanning for. Asserted bigger than the lead-in in network-measure.js. */
.net-pair__list { list-style: none; margin: 0; padding: 0; }
.net-pair__list li {
  font-family: var(--font-head); font-weight: 500;
  font-size: clamp(1.5rem, 2.9vw, 2.15rem); line-height: 1.25;
  letter-spacing: -.015em; color: var(--cream);
  padding: 11px 0; border-bottom: 1px solid rgba(251, 247, 239, .16);
}
.net-pair__list li:first-child { border-top: 1px solid rgba(251, 247, 239, .16); }

/* 🟢 THE THESIS. This is the page's argument ("What connects us is not
   ownership…") and it sits on plain navy at 15.54:1.
   🔴 ONE PARAGRAPH, CENTRED — Brees, 8/3: "don't have it on separate lines,
   just have it all together" and "we'll have that centered." It used to be
   three spans forced to `display: block`, which is the same "bunch of single
   lines" fault he named on §8. Sean's three sentences are unchanged and in
   order; only the forced breaks are gone.
   ⚠️ `text-align: center` centres the words; `margin-inline: auto` places the
   box. Both are needed — this page has produced four bugs from confusing them. */
.net-intro__turn {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-heading); line-height: 1.28;
  color: var(--cream); max-width: 46ch;
  margin: var(--sp-3) auto 0; text-align: center;
}

/* --------------------------------------------------------------------------
   §3 — TWO PARAGRAPHS. See the markup note: Brees asked for prose, so the
   round-5 pivot band, the `.net-built` list, the three-column grid and the
   display close were all removed with their markup.
   🔴 DELETED WITH THEM: `.net-why__pivot`, `.net-why__turn`, `.net-why__foundation`,
   `.net-why__found-lede`, `.net-built`, `.net-built li`, `.net-why__after`,
   `.net-why__last`, and the `.net-why__col p` entry in the shared body rule.
   Nine rules for markup that no longer exists — checked by counting consumers,
   not by reading, because this file has now been bitten in BOTH directions in
   one day (a rule with no consumer, and a consumer whose rule was deleted).

   ⚠️ TWO COLUMNS, AND THE TURN HAPPENS ACROSS THE GUTTER. Paragraph one ends
   on "But independence does not mean isolation." and paragraph two answers it.
   Setting them side by side means the reader crosses the gutter exactly where
   the argument turns — which is the pivot idea at a whisper instead of a shout,
   and it uses the width rather than leaving the right half empty (the PWU fault
   this page has now hit three times).

   ⚠️ SIZE IS DOING THE WORK THE DEVICE USED TO DO. At 17px body these two
   paragraphs would be a wall. At ~19px with a 46ch measure they read as an
   editorial statement. This is the ONLY thing standing between this section and
   the "just a bunch of text" complaint — do not quietly drop it back to body
   size. Asserted in network-measure.js.
   -------------------------------------------------------------------------- */
/* ROUND 12 (8/3) — THE TRIPTYCH. `.net-why__band`, `.net-why__col`,
   `.net-why__fig` and `.net-why__fig img` are DELETED, not orphaned. Brees:
   "style it kind of like the hero on the about page: two images on the right
   and on the left and then the text in the center."

   🟢 THE GRID ITSELF IS site.css's `.triptych` — the same three-column shape
   About, PWU and Team open with. Nothing is redeclared here: only the stagger
   direction, the cream-ground overrides and the block padding are local. A
   fourth hand-rolled three-column layout was the alternative and it would have
   been a fourth thing to keep in sync.

   ⚠️ FULL-BLEED: `.net-why__triptych` sits OUTSIDE `.wrap` in the markup, so
   the side panels reach the viewport edges. `.v4-sec` pads block-only, so this
   needed no override — but that also means the CENTRE column is the only thing
   holding the measure. Do not remove its cap.

   🔴 GROUND CHANGE FROM THE SOURCE PATTERN. About's triptych rides on
   `.phero--dark` (navy) and re-colours the eyebrow, title and lede for it. This
   one is on CREAM, so NONE of those `.phero--dark` rules apply and the existing
   `.net-eyebrow` / `.net-h2` / `.net-why__para` cream colours stand unchanged.
   ⚠️ This is the fourth time this project has had to reason about a shared
   component landing on two grounds. If this pattern is ever moved again, check
   the colours before assuming they travel. */
/* 🔴 §3'S OWN BLOCK PADDING, 8/4 — Brees: "lessen the white space at the top of
   the container and bottom." `.v4-sec` pays clamp(72px, 8vw, 128px) at both
   ends, which measured 128px top and bottom at 1900px on a section that also
   carries a 48px triptych margin and a vertically-centred column.
   ⚠️ THE TOP GAP WAS NEVER MOSTLY PADDING. Measured 329px from the section's
   top edge to the eyebrow at 1900 against 128px of padding — the rest was
   `.triptych`'s `align-items: center` offsetting the short centre column
   against the taller staggered panels. Moving the payoff into that column makes
   it taller and removes most of that offset on its own; this rule only trims
   what is genuinely padding. Cutting padding alone would have left ~200px of
   centring offset and looked like the note had been ignored.
   ⚠️ Equal specificity with `.v4-sec` (0,1,0) — this lands only because
   network.css is loaded AFTER v4.css. Source order, not weight. */
.net-why { padding-block: clamp(48px, 5vw, 80px); }

.net-why__triptych { margin-top: var(--sp-2); }

/* The vertical stagger. Named for WHERE THE PANEL SITS, not for its order in
   the DOM — `--lead`/`--trail` were the first names and they described position
   in the markup, which is exactly the kind of name that goes stale the moment
   the offsets are swapped. `--high` and `--low` cannot lie.
   ⚠️ SWAPPED 8/3 ON BREES' CALL: "left should be higher and then the right
   should be shorter." The LEFT panel (independence) now rides high and the
   RIGHT (togetherness) drops. Renaming rather than crossing the classes over in
   the markup means the CSS still reads as what you see on screen. */
.net-why__panel--high { margin-bottom: clamp(var(--s4), 6vw, var(--s7)); }
.net-why__panel--low  { margin-top: clamp(var(--s4), 6vw, var(--s7)); }

/* The centre column. `.triptych__text`'s own hero-scale block padding is
   dialled back because `.v4-sec` is already padding this section — the hero has
   no such parent and needs to make its own room. */
.net-why__text { padding-block: 0; text-align: center; }

/* 🔴 CENTRING, THE THREE-ANSWER PROBLEM. `text-align` above centres the INLINE
   content of the eyebrow and the h2. It does NOT place a capped block — that is
   `margin-inline: auto`, below. Three separate bugs on 7/31 (PWU's close, the
   hero eyebrow dash, the homepage Explore button) were all this same confusion,
   and `centre-check.js` exists because of them. Both are needed; neither
   substitutes for the other. */
.net-why__text .net-h2 { margin-inline: auto; }

/* 🔴 THE EYEBROW NEEDS TWO OF THE THREE ANSWERS, AND centre-check CAUGHT IT
   MISSING BOTH — 82px off on the first cut of this section.
   `.net-eyebrow` is `display: flex` (a `/` pseudo-element beside the words)
   with `max-width: 46ch`. So:
     · `margin-inline: auto`  places the BOX in the column
     · `justify-content`      places the SLASH AND WORDS inside the box
   `text-align: center` on the parent does neither — a flex container does not
   forward it to its items. All three answers now appear in this one section,
   which is the clearest statement of the rule the page has: block, flex row and
   inline content each centre by a different property. */
.net-why__text .net-eyebrow { margin-inline: auto; justify-content: center; }

/* 🔴 THE VERTICAL RHYTHM HAD TO BE PUT BACK BY HAND. `.net-h2` and
   `.net-why__para` both carry `margin: 0` — the old `.net-why__col` wrapper
   supplied every gap in this section with `gap: 1.1em`, and the triptych
   rebuild deleted that wrapper without replacing what it was doing. The result
   shipped with the h2 touching the first paragraph and the two paragraphs
   running together as one block with NO separation at all — measured 0px and
   0px. Caught by measuring the gaps, not by reading the CSS.
   📌 WHEN YOU DELETE A FLEX/GRID PARENT, YOU DELETE ITS `gap`. Every child that
   relied on it silently loses its spacing, and children that set `margin: 0`
   give you no clue — nothing errors and nothing looks obviously broken; it just
   reads as a slightly dense section. This is the third spacing regression on
   this page to come from a container change rather than a spacing change.
   ⚠️ Specificity is 0,3,0 so it out-ranks `.net-why .net-why__para`'s
   `margin: 0` (0,2,0) regardless of source order — unlike §5's turn, where an
   equal-specificity override written above its rival silently did nothing. */
.net-why__text .net-h2 { margin-bottom: var(--sp-2); }
.net-why__text .net-why__para + .net-why__para { margin-top: 1.1em; }

/* ⚠️ THE PARAGRAPHS ARE THE ONE THING NOT CENTRE-ALIGNED, DELIBERATELY.
   The box is centred (`margin-inline: auto`); the words stay ragged-right.
   About's hero can centre its text because that is a title plus a one-line
   lede. These are ~190 words, and centred prose rags on both edges — which
   would undo exactly the reading quality that the size-and-measure rule below
   exists to protect. If someone "fixes" this to match the hero, §3 quietly
   becomes the wall of text rounds 6 and 10 were spent removing. */
.net-why__text .net-why__para { margin-inline: auto; text-align: left; }

/* ⚠️ SIZE IS STILL DOING THE WORK. At 17px body these paragraphs are a wall; at
   ~19px on a 46ch measure they read as an editorial statement. This remains the
   only thing between this section and "just a bunch of text" — do not quietly
   drop it back to body size. Asserted in network-measure.js (>=18px). */
.net-why .net-why__para {
  font-family: var(--font-head); font-weight: 400;
  font-size: var(--t-body); line-height: 1.62;
  color: var(--navy-soft); max-width: 46ch; margin: 0;
}
/* The pivot, marked in the sentence rather than by the layout. */
.net-why .net-why__turn { color: var(--navy); font-weight: 500; }

/* THE PAYOFF. "Because knowledge shared becomes progress multiplied." was the
   last eight words of paragraph two — the best sentence in the section, set at
   body size. 🟢 Same treatment as §8's "They are built by becoming better.", so
   the two cream sections now close the same way.
   🔴 SPECIFICITY: this must out-rank `.net-why .net-why__para` (0,2,0). The
   mock got this wrong — a `.b-col p` descendant rule beat the payoff class and
   silently rendered it at body size, which looked like a weak idea rather than
   a bug. Hence the two-class selector. */
/* 🔴 RELOCATED AND STEPPED DOWN, 8/4 — it now lives INSIDE `.net-why__text`,
   not in a trailing `.wrap` after the triptych. See the markup comment for why
   the old position made the gap a function of the staggered panel's height.
   ⚠️ DISPLAY (48px) -> HEADING (34px). This is a MOVE ALONG THE EXISTING
   LADDER, not a new size — round 10 cut this page from 26 sizes to 7 and the
   five steps are display 48 / heading 34 / statement 24 / body 19.2 / ui 15.
   It has to come down because the centre column is capped at 560px: at 48px the
   line ran 581px wide in the full wrap and would now wrap to four lines in a
   column two thirds that width. It still clears body (19.2px) by a full step,
   which is what keeps it reading as the section's close.
   ⚠️ NOT 24px. `--t-statement` sits close enough to the 19.2px prose that the
   close would stop winning — the exact "nothing is distinguishable so nothing
   wins" failure the type audit found across 18 of 26 sizes.
   ⚠️ `text-align: left` is REQUIRED, not cosmetic. Its new parent
   `.net-why__text` sets `text-align: center`, which the payoff would otherwise
   inherit — and centred display type above left-ragged prose reads as a mistake.
   It matches the paragraphs: box centred in the column, words ragged right.
   ⚠️ 30ch resolves against THIS element's 34px (~510px), not the prose's 19.2px.
   That is deliberate — it lands just inside the 560px column so the left edge
   lines up with the paragraphs above it. `ch` on a container is the trap this
   file documents at `.net-hero__text`; here the unit is on the type it measures,
   which is the case where it is the right tool. */
.net-why .net-why__payoff {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-heading); line-height: 1.15;
  color: var(--navy); max-width: 30ch;
  margin: var(--sp-2) auto 0;
  text-align: left;
}

/* --------------------------------------------------------------------------
   §4 — OUR GUIDING PHILOSOPHY (navy) — THE TYPOGRAPHIC SET PIECE
   -------------------------------------------------------------------------- */
/* 🔴 TWO-COLUMN, AND IT IS A FIX FOR A NAMED FAULT — NOT A PREFERENCE.
   The first cut was one left-aligned stack, which left the right ~45% of the
   viewport empty. That is precisely what Brees rejected twice on PWU (§1.5
   "not sold on that section" after four passes on type size, and §5 "it still
   feels bland"). Both diagnoses were identical and neither was about size: a
   purely typographic block with nothing to push against, in a narrow left
   column, wasting the width. The documented fix was a two-column split on the
   .8fr/1.2fr rhythm `.v4-op` already uses — so this reuses it rather than
   inventing a third answer to the same problem.
   ⚠️ The forwards go in the WIDE column. They are the section's subject; the
   definition that sets them up is the support. */
.net-phil__grid {
  display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: var(--sp-3); align-items: start;
}
.net-phil__def {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-statement); line-height: 1.3;
  color: var(--cream); max-width: 24ch; margin: var(--sp-2) 0 var(--sp-1);
}
.net-phil__def em { font-style: italic; }
.net-phil__body {
  color: rgba(251,247,239,.82); font-size: var(--t-body); line-height: 1.75;
  max-width: 58ch; margin: 0 0 var(--sp-1);
}

/* --------------------------------------------------------------------------
   §4 — THE FORWARDS ROTATOR (added 7/31)
   See the markup note. Default state = the <ul> exactly as it always was.
   The rotator only exists once JS adds `.is-rotating`, and JS only does that
   when the user has not asked for reduced motion.
   -------------------------------------------------------------------------- */
.net-fwd__line { display: none; }
/* ⚠️ `flex-end`, NOT `baseline`. Two reasons, both measured:
   1. `vertical-align` does nothing to a flex item, so the `vertical-align:
      bottom` on the slot is inert here — the flex container decides.
   2. An element with `overflow: hidden` reports its BOTTOM MARGIN EDGE as its
      baseline. So `align-items: baseline` sat the slot's bottom on the text
      baseline of "Forward in" and floated the rotating word 14px above it.
   The slot and the prefix now share a font-size and line-height (see below),
   so their boxes are the same height and aligning bottoms aligns baselines.
   ⚠️ `gap` is in em and this container carries the display font-size, so the
   space between "Forward in" and the word scales with the type. */
.net-fwd.is-rotating .net-fwd__line {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: 0 .28em; margin: var(--sp-0) 0 var(--sp-2);
}
/* 🔴 CLIPPED, NOT display:none. The <ul> must stay in the accessibility tree —
   it is the actual content and the only place the five sentences exist in
   full. display:none or visibility:hidden would delete them from screen
   readers and leave a decorative, aria-hidden line as the only copy on the
   page. That is precisely the failure §6's comment forbids. */
.net-fwd.is-rotating .net-forwards {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* 🔴 THE SLOT IS IN THIS SELECTOR LIST DELIBERATELY — DO NOT SPLIT IT OUT.
   Its `height: 1.12em` resolves against ITS OWN font-size. Left out of this
   rule it inherited the 17px body face, so the mask came out **19px tall
   around a 54px word**: the type was sheared through the middle and sat 19px
   below the baseline of "Forward in".
   ⚠️ This is the SAME BUG this file already documents at `.net-hero__text`,
   where `max-width: 24ch` resolved against the container's 16px instead of the
   57.6px title and produced a 242px box. Different property, identical cause.
   The standing rule from that fix — cap containers in px/%, cap type in ch —
   generalises: **any relative unit is measured against the element it is
   written on, never the type it is meant to describe.** If a box is sized in
   `em`/`ch` to fit text, the box must carry that text's font-size. */
.net-fwd__pre,
.net-fwd__slot,
.net-fwd__w {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-display); line-height: 1.12;
  color: var(--cream);
}
/* 🔴 THE PAGE'S SECOND GOLD, AND IT IS DELIBERATE — RULE AMENDED 7/31 pm.
   The standing rule was "GOLD APPEARS EXACTLY ONCE" (the hairline above the
   hero motto), written after v1 died as "a bank or lawyer site" on navy + gold
   everywhere. Brees asked for the rotating word in gold to get colour into a
   section that is otherwise pure type, was shown the rule, and chose a single
   deliberate exception.
   **The rule is now: GOLD APPEARS EXACTLY TWICE — the hero motto rule and this
   word. Both are load-bearing. Do not add a third.** What killed v1 was gold
   as decoration across every navy ground; this is gold on the one element that
   moves, which is emphasis.
   ⚠️ Colour-system-consistent: red belongs to cream grounds, gold to navy, and
   §4 is navy. Measured 8.78:1 on --navy-deep, far past the 4.5 body floor —
   asserted in network-measure.js so a palette change cannot quietly break it. */
.net-fwd__w { color: var(--gold); }
/* The slot is one line tall and masks everything outside it, so the outgoing
   word leaves upward and the incoming word arrives from below — the same
   "ever upward" mechanic as §1's hero and §9's closing line, at word scale. */
.net-fwd__slot {
  position: relative; display: inline-block;
  overflow: hidden; vertical-align: bottom;
  height: 1.12em;
  /* Width is set inline by JS to the widest word, so the line never reflows
     mid-rotation — see network.js. */
}
.net-fwd__w {
  position: absolute; left: 0; top: 0; white-space: nowrap;
  transform: translateY(110%); opacity: 0;
  transition: transform .55s var(--ease), opacity .35s var(--ease);
}
.net-fwd__w.is-current { transform: translateY(0); opacity: 1; }
.net-fwd__w.is-leaving  { transform: translateY(-110%); opacity: 0; }
/* Belt and braces: even if JS ever adds .is-rotating against the user's
   preference, the motion stops and the full list comes back. */
@media (prefers-reduced-motion: reduce) {
  .net-fwd.is-rotating .net-fwd__line { display: none; }
  .net-fwd.is-rotating .net-forwards {
    position: static; width: auto; height: auto; margin: var(--sp-0) 0 var(--sp-2);
    overflow: visible; clip-path: none; white-space: normal;
  }
}

/* THE FIVE FORWARDS. Display scale, each on its own line, revealing in
   sequence — the one place on the site where "Excelsior" is defined, so the
   type does the work instead of an image. A real <ul>: five items, heard as
   five. */
.net-forwards {
  list-style: none; margin: var(--sp-0) 0 var(--sp-2); padding: 0;
  display: grid; gap: var(--s1);
}
.net-forwards li {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-display); line-height: 1.12;
  color: var(--cream);
}
/* The guiding question — bold in the doc. Set up rather than emboldened:
   bold on a dark ground at this size muddies (the same call pwu.css makes). */
.net-phil__q {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-statement); line-height: 1.35;
  color: var(--cream); max-width: 34ch; margin: 0;
  padding-top: var(--s4); border-top: 1px solid rgba(251,247,239,.22);
}

/* --------------------------------------------------------------------------
   §5 — MEMBERSHIP (cream) — THE SPLIT
   🔴 REBUILT 7/31. It was the page's only section with NO media and a single
   68ch column of five stacked paragraphs — 911px of one register. Brees, 7/30:
   "a lot of text… read all of the text and not have to scroll so much",
   "more visuals", and §6's constellation is the model because "the element
   really speaks to the content".
   So the question was what this copy's element IS. The copy's argument is
   SELECTION — membership is *earned*, and "We don't seek to partner with every
   hospital." So the section splits: the lead pairs with a picture in one
   horizontal band, and the antithesis is set as two columns where only one
   side is marked. The layout makes the point the sentences make.
   ⚠️ 21st.dev SEARCHED FIRST per the standing rule, NO FIT — the catalogue
   returns a form <Select>, a <Checkbox>, an editorial hero and an editorial
   testimonial for this brief. None carries five paragraphs of argument, and
   none is about choosing. Purpose-built, same call and same reason as §6.
   -------------------------------------------------------------------------- */
/* The lead band. Copy and picture share ONE vertical band, which is the whole
   trick for "more images without more scrolling" — the figure costs no height
   it does not already spend on the paragraphs beside it.
   🔴 THE TRICK ONLY WORKS IF THE COPY IS THE TALLER SIDE, AND THAT HAD TO BE
   MEASURED, TWICE. The first cut put an `aspect-ratio: 4/5` portrait here
   against 256px of copy: the figure came out 598px, DROVE the row, and added
   342px to the one section built to prove the opposite. §3's version of the
   same mistake cost 85px more (see `.net-figure--band`).
   The fix is `align-items: stretch` + a figure with no intrinsic height — the
   copy column sets the row, the picture crops to it. `object-fit: cover` on a
   height:100% img is what makes the image a PASSENGER rather than the driver.
   ⚠️ The coda paragraph was pulled INTO this column on purpose. It is what
   makes the copy side tall enough to give the picture a workable box; left
   below the split, the band collapsed to the two short paragraphs above it. */
/* ROUND 9b — ONE BAND FOR THE WHOLE LOWER HALF.
   Brees, on a screenshot of this area: "I do not like this section."
   🔴 WHAT WAS WRONG WAS FRAGMENTATION AGAIN — the same fault taken out of §8
   one round earlier, rebuilt here without anyone noticing: a big statement, a
   small paragraph, a photograph, a dead gap, then a full-width closing
   paragraph. Four registers, with empty space under the copy AND under the
   picture because neither column filled the row.
   📌 THE FIX IS THE SHAPE §3 JUST GOT APPROVED WITH: all the prose in ONE
   column — statement, coda, close — with the photograph held beside it. The
   column is now tall enough to balance the picture, so the dead space has
   nothing to sit in, and the section ends on Sean's closing line instead of
   trailing into a gap.
   ⚠️ THE PHOTOGRAPH STAYS — Brees ruled the problem was the LAYOUT, not the
   image, when asked directly. That also finally settles the tortoiseshell,
   which had been flagged twice as the frame most likely to fail the
   "cared-for pets" rule. It is approved by omission no longer; it is approved.
   📌 Register count for this half is now TWO (prose column + picture). Check
   that number after every pass — it is how this fault keeps coming back. */
/* ═══ ROUND 12 (8/3) — THE THRESHOLD ═══════════════════════════════════════
   `.net-member__lead`, `.net-member__col` and the `.net-member__lead`-scoped
   sticky-figure rules are DELETED, not orphaned. Brees chose R1 from six mocks.
   ⚠️ `.net-figure--fill`'s base rule survived this edit because §8 still used
   it — §8 has since become the overlap and the rule is now gone entirely. Only the
   two rules that were scoped to the deleted `.net-member__lead` came out. That
   distinction matters: deleting the base would have silently broken another
   section, which is how `.net-member__body` took the turn's rule down with it.

   🔴 THIS IS AN INTERIM SHAPE. Brees: "I like R1 out of all of those but still
   not happy with it." Two named gaps, and neither is fixed here:
     · NO IDEA IN IT — a better container, not a device. §6 is the model.
     · TOO STATIC — it needs to move.
   Do not read the tidiness as completion.

   THE SHAPE: a full-bleed two-column crossing. The photograph runs floor-to-
   ceiling and off the left viewport edge; the standards stand on the far side.
   ⚠️ THE SECTION CARRIES NO BLOCK PADDING — `.net-member--threshold` zeroes
   `.v4-sec`'s, and the TEXT COLUMN carries it instead. That is what lets the
   picture reach the section's top and bottom edges. If you restore padding on
   the section, the bleed dies and you get a cream band above and below the
   photo — the same fault QA'd on About §4 and PWU §4 on 7/31.
   ────────────────────────────────────────────────────────────────────────── */
.net-member--threshold { padding-block: 0; }

.net-thresh {
  display: grid;
  grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr);
  align-items: stretch;
}

/* ⚠️ min-height is load-bearing. The figure has no intrinsic height here (the
   img is absolutely positioned so it can fill whatever the text column makes),
   so without it a short copy column would collapse the picture to nothing. */
.net-thresh__fig {
  margin: 0; position: relative; overflow: hidden;
  min-height: clamp(420px, 52vw, 760px);
}
.net-thresh__fig img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}

/* The copy column carries the section's block padding (see above) and is capped
   so the measure stays readable no matter how wide the viewport gets — the side
   the picture does not take is not an invitation to run 120ch lines. */
.net-thresh__body {
  padding-block: clamp(72px, 8vw, 128px);
  padding-inline: clamp(36px, 4.5vw, 80px) var(--gutter);
  display: grid; align-content: center;
  max-width: 860px;
}
/* The rhythm between the standards and the prose that follows them.

   🔴 THREE CLASSES, AND THE THIRD IS LOAD-BEARING — measured, not guessed.
   The first cut of this block was `.net-thresh__body .net-member__turn`
   (specificity 0,2,0). `.net-member .net-member__turn` further down this file
   is ALSO 0,2,0 and sets `margin-block: var(--sp-0)`, so the later rule won and
   my margin was silently dead: the gap from `/04 continuous improvement` to
   "Together, we are building…" measured 16px against 24-72px everywhere else.
   It read as a slightly tight section rather than as a broken rule — which is
   the failure mode this file has now recorded for the ninth time.
   📌 Equal specificity is not a tie you can ignore; source order decides it, and
   an override written ABOVE the rule it overrides never lands. Caught by
   measuring the gaps, not by looking — 16 vs 24px is invisible at a glance. */
.net-member--threshold .net-thresh__body .net-seq { margin-top: var(--sp-2); }
.net-member--threshold .net-thresh__body .net-member__turn { margin-block: var(--sp-3) 0; }
.net-member--threshold .net-thresh__body .net-member__coda { margin-block: var(--sp-2) 0; }
.net-member--threshold .net-thresh__body .net-member__split { margin-block: var(--sp-2) 0; }
/* `.net-member__body` DELETED round 5 with its markup. 🔴 IT TOOK THE TURN'S
   RULE WITH IT: `.net-member .net-member__body .net-member__turn` stopped
   matching anything and the statement silently fell to 17px body size in a
   serif face — the exact failure this file documents seven times over, and
   indistinguishable from a design choice on screen.
   Caught by network-measure.js, not by looking. The selector below is now
   TWO classes because the element rule it was out-specifying (`.net-member__body p`)
   no longer exists — remove the competition where you can. */

/* No aspect-ratio: the row decides. min-height is the floor for the case where
   the copy is short (narrow viewports, big type) — without it the picture can
   be squeezed to a letterbox slot. */
/* `.net-figure--fill` DELETED 8/3 — §8 was its last consumer and §8 is now the
   overlap. §5 stopped using it when it became the threshold. Kept as a note so
   the next person greps the name and finds the answer, not a dead selector. */

/* `.net-mult` — THE MULTIPLICATION — BUILT AND REMOVED THE SAME DAY (7/31).
   A field of diamonds propagating from one red origin, meant to draw §3's
   "knowledge shared becomes progress multiplied."
   🔴 BREES KILLED IT ON SIGHT AND HE WAS RIGHT: "I'm not sure I understand the
   diamond. What are the diamonds supposed to represent? I just don't want a
   design here just because it looks cool."
   📌 THE LESSON, WHICH IS THE POINT OF KEEPING THIS NOTE: the device was built
   to satisfy a rule NOBODY ASKED FOR — "every section needs a device". §3 had
   been deviceless since round 6 BY HIS OWN DECISION, and that was fine. An
   abstract mark that needs a caption to be understood is decoration, and
   decoration invented to fill a checklist is worse than an empty column.
   ⚠️ Do not rebuild this. If §3 ever gets a device again it has to be
   legible without explanation. */

/* THE FOUR CRITERIA AS A PINNED SEQUENCE (round 7).
   Replaces the round-5 boxes (`.net-crit-grid` / `.net-crit-box`, both deleted
   with their markup). Those boxes made §5 the page's second box section after
   §8 — the monotony trade the notes flagged at the time. This spends the
   section's height on scroll rather than on reading. */
.net-crit__lead {
  font-family: var(--font-head); font-weight: 400;
  font-size: var(--t-body); line-height: 1.45;
  color: var(--navy-soft); margin: var(--sp-2) 0 var(--sp-1); max-width: 44ch;
}

/* THE FOUR CRITERIA (round 9) — a plain line-up. No pin, no runway, no JS.

   📌 THE FULL HISTORY, BECAUSE THIS SECTION HAS BEEN REBUILT FOUR TIMES:
     round 3  four criteria as emphasised spans inside a sentence
     round 5  four cream-panel boxes            → the AI-default card
     round 7  a PINNED /01-/04 scroll sequence  → lifted from subduxion.com
     round 9  this
   🔴 THE PIN IS GONE AT BREES' EXPLICIT CHOICE, SHOWN A SIDE-BY-SIDE OF BOTH.
   He liked the held words ("I like the scrolling and the excellence, integrity,
   that kind of thing") but the section cost 2,760px to deliver four of them —
   "it's a lot of scrolling for just a little bit of stuff." Two rounds were
   spent tuning the runway (280vh → 200vh → 140vh), the dead space under the
   list and a real bug where the steps finished in the first third of the pin.
   📌 THE LESSON: none of that tuning changed the ratio, because the ratio was
   the problem. **When a device needs repeated tuning to feel justified, the
   honest answer is usually that it is not justified.** Four words do not need
   a scroll runway; they need to be set well and got out of the way.
   ⚠️ DO NOT REINSTATE THE PIN. The alternative (criteria pinned while the rest
   of the section scrolled past them, which fixed the empty runway) was built,
   shown, and rejected in favour of this. Both are in the session notes. */
.net-seq { margin-top: var(--sp-2); }
.net-seq__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.net-seq__item {
  display: flex; align-items: baseline; gap: var(--sp-1);
  color: var(--navy);
}
/* 🔴 THE ONLY THING ON THE SITE THAT OVERFLOWED BELOW 320px (found 8/14 at
   280px, after Brees asked for the sub-320 range to be covered).
   This row is `/0N` + a display-scale word. Its min-content is the 38px marker
   + gap + the longest word ("improvement" at --t-display) = 242.4px, against a
   240px content box at 280px. `.net-thresh__body` is a grid, and a grid item's
   default min-width:auto IS min-content — so the column could not shrink and
   pushed the whole DOCUMENT to 282px. Same failure as the footer email address
   documented in site.css, in a different element.
   ⚠️ TWO WRONG FIXES CAME FIRST, AND THE SECOND ONE LOOKED FINE IN THE NUMBERS.
   1. Unscoped `flex-wrap: wrap` is not self-regulating — it changes the flex
      algorithm's PREFERENCE, so 320 and 390 (never broken) had the whole word
      drop below its marker: the /04 row went 68px -> 112px, the list 176 -> 220,
      and 1024 moved too. Scoping it to this media query fixed the scope creep.
   2. Scoped `flex-wrap: wrap` then PASSED EVERY CHECK — 0 overflow at all 16
      widths, 300+ byte-identical — and still looked wrong: /01 /02 /03 kept
      their words inline while only /04 dropped to its own line, so three rows
      paired and one did not. **A green gate says nothing about whether the
      section reads as a section.** Only the screenshot showed it.
   3. Dropping the word a type step (--t-display -> --t-heading) did NOTHING,
      because --t-display already clamps to 30.4px at these widths — the word
      was never rendering at 48px here. I assumed the size instead of reading
      it, and the rule changed no computed value at all.
   THE MEASURED ARITHMETIC, which is what finally settled it:
      38px marker (min-width) + 24px gap (--sp-1) + 180.4px min-content of
      "improvement" at 30.4px = 242.4px, against a 240px content box.
   Short by 2.4px, and the GAP holds the most slack. Below 290px it steps down
   the existing spacing scale, --s3 (24px) -> --s2 (16px): 38 + 16 + 180.4 =
   234.4px, fitting with ~6px to spare. Type, marker and pairing all untouched,
   so all four rows still read identically.
   The arithmetic behind 290: --gutter clamps to 20px below ~400px, so the
   content box is viewport - 40 and drops under 242.4px at a viewport of 282.4.
   Everything <=282 overflows; 290 covers it with margin and leaves 300 alone
   (300 measured clean; a first attempt at max-width:300 changed it for nothing).
   📌 Three fixes, three lessons: "it only applies when needed" is a claim to
   MEASURE · "every check passed" says nothing about how it LOOKS · and a token's
   computed value is not the value written in the declaration. */
@media (max-width: 290px) {
  .net-seq__item { gap: var(--s2); }
}
.net-seq__num {
  font-family: var(--font-body); font-weight: 700; font-size: var(--t-micro);
  letter-spacing: .14em; color: var(--red); flex: 0 0 auto; min-width: 38px;
}
.net-seq__word {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-display); line-height: 1.12;
  letter-spacing: -0.02em; color: inherit;
}

/* 🔴 THE BOLD CAME OFF 7/31 pm. Brees: "all these different sizes of text,
   bold text. My eyes don't know where to go."
   Measured, this section was rendering SEVEN distinct type treatments —
   38 / 30 / 28 / 23 / 22 / 18 / 17-bold — with FOUR of them clustered between
   22px and 30px. Nothing could win, so nothing did. Emphasising four words
   inside a body paragraph added a seventh voice to a section that already had
   six, in service of a point the sentence already makes.
   The criteria now sit in full navy against navy-soft body: still lifted out
   of the line, no longer shouting over the statement below them.
   ⚠️ STILL TRUE AND STILL LOAD-BEARING IN ROUND 7 — the pinned sequence adds
   exactly ONE display size to this section and takes the four boxes away, so
   the treatment count went down, not up. If a future pass adds per-step body
   copy it re-creates the seven-voices problem this note exists to prevent. */
/* `.net-crit` DELETED round 5 (emphasis spans) · `.net-crit-grid` and
   `.net-crit-box` DELETED round 7 (the boxes became `.net-seq`). Only
   `.net-crit__lead` survives — it is Sean's lead-in sentence. */

.net-member .net-member__turn {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-statement); line-height: 1.32;
  color: var(--navy); max-width: 30ch;
  margin-block: var(--sp-0);
}

/* THE SPLIT. Not-side narrower and recessive, do-side wider and dominant —
   the ratio is part of the argument, not a grid preference.
   🟢 These two sit OUTSIDE `.net-member__body`, so no element rule competes and
   two classes are genuinely enough here. That is the same claim this file's
   header once made falsely about five other statements, so it is ASSERTED, not
   trusted: both are in .shots/network-measure.js. */
/* ⚠️ `.net-member__split` WAS A TWO-COLUMN GRID and that rule survived the
   round-8 rewrite for a while, silently turning the new paragraph's two <span>s
   into grid items — so it still rendered as two columns and LOOKED plausible.
   A duplicate rule for the same selector further down the file is invisible in
   a screenshot; grep the selector, do not trust the render. */
/* 🔴 BOTH SIDES ARE NOW THE SAME SIZE — CHANGED 7/31 pm.
   They were 23px and 30px, which put two more entries into a section already
   carrying four sizes between 22 and 30. Size was doing the same job the rule
   colour and the text colour were already doing, three times over.
   **The asymmetry survives intact and is now carried entirely by colour:**
   neutral hairline + navy-soft over what EVAA does not do, red rule + full
   navy over what it does. That was always the device — the size was noise on
   top of it. Ladder for this section is now 38 / 30 / 22 / 17, with exactly
   one thing at 30. */
/* 🔴 NOW BOXES, NOT BARE COLUMNS (round 5) — but the asymmetry is unchanged
   and still lives in weight, colour and the rule, never in size. Both sides
   share one font-size; that was the round-4 fix for "my eyes don't know where
   to go" and it is asserted in network-measure.js on weight/colour/rule rather
   than on px. Do not reintroduce a size difference here. */
/* 🔴 THE TWO PANELS ARE GONE (round 8). Both were `--cream-2` fill +
   `border-radius` + a `border-top` hairline — the AI-default card.
   Brees: "we don't seek the partner, we seek the partner. Those need to be
   fine-tuned much more. This is default AI work. I don't know how many times
   I've said it… we don't want boxes like this that have that border. That
   border is like an AI palette, and it's ugly."
   📌 THE HISTORY IS THE LESSON. Round 3 had these as bare columns with hairline
   rules; Brees called that "like somebody did it in Word." Round 5 answered by
   putting the columns IN PANELS. Both times the fix reached for the container
   when the problem was that the TYPE was not doing enough work. It now is:
     · "we don't"  — navy-soft, regular, smaller, shorter measure
     · "we seek"   — navy, 500, a full step larger
   Same antithesis, no chrome. ⚠️ network-measure.js asserts this split on
   weight and colour; those assertions were re-pointed, not deleted. */
.net-member__split {
  font-family: var(--font-head); font-weight: 400;
  /* ⚠️ 22px, NOT the 19.2px prose scale. This is the section's closing
     statement and network-measure.js holds BOTH halves at >=21px — a floor
     written when "we seek" was a panel and kept deliberately when the panel
     went, because losing the box must not also lose the statement's presence.
     Meeting the assertion was the right move here; lowering it to match a
     smaller size would have quietly demoted the line. */
  font-size: var(--t-statement); line-height: 1.5;
  max-width: 46ch; margin: var(--sp-1) 0 0;
}
/* ⚠️ SAME SIZE, DELIBERATELY — inherited from the paragraph, so the two halves
   cannot drift apart. Asymmetry is weight + colour ONLY (round 3's ruling,
   asserted in network-measure.js). */
.net-member .net-member__not { color: var(--navy-soft); font-weight: 400; }
.net-member .net-member__do  { color: var(--navy);      font-weight: 500; }
/* The closing sentence. It sits in the lead column beside the picture (see the
   band note) rather than fifth in a stack. Outside `.net-member__body`, so it
   carries its own type — a step above body, a step below the split. */
.net-member .net-member__coda {
  color: var(--navy-soft); font-size: var(--t-body);
  line-height: 1.7; max-width: 62ch; margin: var(--sp-0) 0 0;
}

/* --------------------------------------------------------------------------
   §6 — THE BENEFITS OF BELONGING — THE CONSTELLATION
   🔴 THE PAGE'S CENTREPIECE, REBUILT 7/30. It was homepage §6's service rows
   (.v4-ops__*) — a list of what a network gives you, rendered as a service
   list, on a page named The Excelsior Network. Brees: this page needs to be
   "more unique as the homepage is to the inner pages."
   ⚠️ 21st.dev was searched first per the standing rule and had NO FIT — its
   network components are cursor-driven canvas backgrounds that cannot carry
   copy (one paints an hsla rainbow gradient, against the no-gradients rule),
   and its orbit components would just re-run homepage §7's radial burst.
   So: purpose-built, per this project's own §3 lesson — make the section BE
   the effect rather than bolt a set piece into the middle of one.

   🔴 THE DIAGRAM IS DECORATION AND THE LIST IS THE CONTENT. All seven benefits
   are always rendered and always readable; the SVG is aria-hidden and only
   highlights the one being read. Do NOT "improve" this into a diagram that
   swaps paragraphs in and out — that hides six-sevenths of the copy from
   assistive tech, from reduced-motion users and from anyone with JS off.
   -------------------------------------------------------------------------- */
.net-web__head { max-width: 46ch; }
.net-web__lede {
  color: var(--navy-soft); font-size: var(--t-body); line-height: 1.75;
  max-width: 56ch; margin: var(--sp-1) 0 0;
}
.net-web__stage {
  display: grid; grid-template-columns: minmax(0, .78fr) minmax(0, 1.22fr);
  gap: var(--sp-3); align-items: start; margin-top: var(--sp-4);
}

/* 🔴 STICKY, AND IT NEEDS NO overflow ANYWHERE ABOVE IT. Same trap as
   pinned-bg.css and Team's portraits: overflow:hidden/clip on an ancestor
   turns position:sticky into a silent no-op. Asserted by network-measure.js. */
.net-web__diagram { position: sticky; top: 120px; }
.net-web__svg { width: 100%; height: auto; display: block; overflow: visible; }

/* Edges. Hairlines by default; an edge touching the active node brightens.
   ⚠️ --rule is the site's own hairline token, so the web sits in the same
   register as .v4-op's row rules rather than inventing a new grey. */
.net-web__edges line {
  stroke: var(--rule); stroke-width: 1;
  transition: stroke .5s var(--ease), stroke-width .5s var(--ease);
}
.net-web__edges line.is-live { stroke: var(--navy-soft); stroke-width: 1.5; }

/* Nodes. Unreached = hollow navy-soft ring; reached = filled red.
   🔴 BOTH STATES CARRY MEANING ON THEIR OWN AND NEITHER IS TEXT. These are
   decorative graphics (1.4.11 territory, 3:1), and the benefit's name is never
   conveyed by the node — it is always the <h3> beside it. So the highlight can
   never be the only way to know which benefit you are on. */
.net-web__nodes circle {
  fill: var(--cream-2); stroke: var(--navy-soft); stroke-width: 1.5;
  transition: fill .45s var(--ease), stroke .45s var(--ease), r .45s var(--ease);
}
.net-web__nodes circle.is-active {
  fill: var(--red); stroke: var(--red); r: 13;
}
.net-web__nodes circle.is-past { fill: var(--navy-soft); stroke: var(--navy-soft); }

/* The list. Generous rhythm so each benefit occupies the viewport long enough
   for its node to read as "the one you are on". */
.net-web__list { list-style: none; margin: 0; padding: 0; display: grid; }
.net-benefit {
  padding-block: clamp(28px, 4vw, 52px);
  border-top: 1px solid var(--rule);
}
.net-benefit:last-child { border-bottom: 1px solid var(--rule); }
.net-benefit__name {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-statement); line-height: 1.25;
  color: var(--navy); margin: 0;
  display: flex; align-items: baseline; gap: 12px;
}
/* The ordinal ties a row to its node without adding a second colour. */
.net-benefit__name::before {
  content: counter(benefit, decimal-leading-zero);
  counter-increment: benefit;
  font-family: var(--font-body); font-weight: 700;
  font-size: var(--t-micro); letter-spacing: .12em; color: var(--navy-soft);
  flex: none;
}
.net-web__list { counter-reset: benefit; }
.net-benefit__desc {
  color: var(--navy-soft); font-size: var(--t-body); line-height: 1.75;
  max-width: 60ch; margin: var(--sp-0) 0 0;
}

/* The doc's only sub-list, inside benefit 07. */
.net-resources {
  list-style: none; margin: var(--sp-0) 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px var(--sp-1);
}
.net-resources li {
  font-size: var(--t-ui); line-height: 1.6; color: var(--navy-soft);
  padding-left: 16px; position: relative;
}
/* A dot, not a coloured left border (house rule). */
.net-resources li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--red);
}

/* --------------------------------------------------------------------------
   §7 — THE EXCELSIOR PLEDGE (navy) — THE CHARTER
   A real <ol>: these are ordered commitments and the numerals carry meaning,
   so unlike About §3's decorative ordinals they are NOT aria-hidden.
   -------------------------------------------------------------------------- */
.net-pledge__lede {
  font-family: var(--font-head); font-weight: 400;
  font-size: var(--t-statement); line-height: 1.4;
  color: var(--cream); margin: var(--sp-2) 0 var(--sp-0); max-width: 40ch;
}
/* 🔴 ONE COLUMN, NOT TWO — REVERSED 7/31 WITH THE CHARTER REBUILD.
   Two columns were right for seven heading+sentence pairs: it halved a very
   tall block. It is wrong for seven single words, which in two columns read as
   a word grid — a menu of options rather than a set of promises. Down one
   column under seven equal rules they read as what they are: a document you
   sign. The height is roughly unchanged; the amount of text on screen is not. */
.net-pledge__list {
  list-style: none; margin: var(--sp-2) 0 0; padding: 0;
  counter-reset: vow;
}
.net-vow {
  counter-increment: vow;
  border-top: 1px solid rgba(251,247,239,.22);
}
.net-vow:last-child { border-bottom: 1px solid rgba(251,247,239,.22); }

/* The row itself is the control. Whole-width target, not a small twisty — the
   default marker is ~10px and this is the primary interaction of the section. */
.net-vow__sum {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: var(--sp-1);
  padding-block: clamp(18px, 2.2vw, 26px);
}
.net-vow__sum::-webkit-details-marker { display: none; }  /* Safari's triangle */

.net-vow__name {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-heading); line-height: 1.15;
  color: var(--cream); margin: 0;
  display: flex; align-items: baseline; gap: var(--sp-1);
  flex: 1 1 auto;
  transition: color .3s var(--ease);
}
/* The numeral. Cream at .55 — decorative weight, but it is a real ordinal from
   the <ol>, so it is generated content on the item rather than a hidden span. */
.net-vow__name::before {
  content: counter(vow, decimal-leading-zero);
  font-family: var(--font-body); font-weight: 700;
  font-size: var(--t-micro); letter-spacing: .12em;
  color: rgba(251,247,239,.55); flex: none;
}

/* THE MARK — a plus that becomes a minus. Two rules, one of which collapses on
   open. `aria-hidden` is unnecessary: it is generated content with no text.
   ⚠️ 3:1 is the floor here (WCAG 1.4.11, non-text) — cream at .55 on navy-deep
   measures well past it, asserted in network-measure.js. */
.net-vow__mark {
  position: relative; flex: none;
  width: 22px; height: 22px;
  transition: transform .4s var(--ease);
}
.net-vow__mark::before,
.net-vow__mark::after {
  content: ""; position: absolute; inset: 50% 0 auto 0;
  height: 1.5px; background: rgba(251,247,239,.55);
  transform: translateY(-50%);
  transition: transform .4s var(--ease), background .3s var(--ease);
}
.net-vow__mark::after { transform: translateY(-50%) rotate(90deg); }
.net-vow__d[open] .net-vow__mark::after { transform: translateY(-50%) rotate(0deg); }
.net-vow__d[open] .net-vow__mark::before,
.net-vow__d[open] .net-vow__mark::after { background: var(--cream); }

/* Hover/focus lifts the whole row, not just the word. */
.net-vow__sum:hover .net-vow__name,
.net-vow__d[open] .net-vow__name { color: #fff; }
.net-vow__sum:hover .net-vow__mark::before,
.net-vow__sum:hover .net-vow__mark::after { background: var(--cream); }

.net-vow__desc {
  color: rgba(251,247,239,.82); font-size: var(--t-body); line-height: 1.7;
  margin: 0 0 var(--sp-1); max-width: 52ch;
  padding-left: calc(clamp(14px, 2vw, 24px) + 22px);
}
/* ⚠️ OVERFLOW ONLY WHILE THE TWEEN RUNS — NOT PERMANENTLY.
   The animator sets an inline height during open/close and clears it on
   finish; the clip is needed for those ~380ms and no longer. A permanent
   `overflow:hidden` here made deep-a11y report the six closed vows as
   "CONTENT CLIPPED under reduce", because it pattern-matches an overflow
   ancestor and has no concept of a disclosure widget. That was a false
   positive — a closed <details> is announced as collapsed and opens from the
   keyboard — but the rule really was doing nothing for 99% of its life, so the
   honest fix is to scope it rather than to teach the checker to ignore it.
   Nothing here may set a height, or it will fight the inline one. */
.net-vow__d.is-animating { overflow: hidden; }

/* --------------------------------------------------------------------------
   §8 — BUILDING SOMETHING THAT LASTS (cream)
   -------------------------------------------------------------------------- */
/* ⚠️ align-items: START, not center. Round 8 shortened this column from three
   paragraphs to one, and `center` then floated that paragraph into the middle
   of the taller figure — leaving a big hole under the heading that looked like
   a spacing bug and was really a leftover alignment from a different amount of
   copy. §3 and §5's bands both align to the top; this is now the third band on
   the page and they must agree.
   📌 When a column's CONTENT changes length, re-check its ALIGNMENT — centring
   is invisible until the two sides stop matching. */
/* ══ ROUND 12 (8/3) — §8 AS PARAGRAPHS, IN AN OVERLAP ═════════════════════
   DELETED, not orphaned: `.net-lasts__grid`, `.net-lasts__body`,
   `.net-lasts__support`, `.net-lasts__payoff`, `.net-lasts__larger`,
   `.net-lasts__better` and `.net-figure--fill` (whose LAST consumer this was —
   §5 stopped using it when it became the threshold, so its base rule goes too).

   🔴 THE COPY INSTRUCTION IS THE ONE THAT MATTERS, AND IT IS NOT A LAYOUT NOTE.
   Brees: "reformat (not reword) the text so it's more paragraph like than a
   bunch of single lines." §8 was two paragraphs plus TWO ORPHAN DISPLAY LINES —
   the 24px "…not built by simply becoming larger." and the 48px "They are built
   by becoming better." Those are sentences of a paragraph that got promoted to
   furniture. They are now the third paragraph and the close lands by WEIGHT AND
   COLOUR (`.net-lasts__turn`), the mechanism §3's turn uses.
   📌 A sentence can land without becoming a display-scale object. Reaching for
   a size jump is what produced "a bunch of single lines" in the first place.
   ⚠️ ZERO WORDS CHANGED — verified by diff, not by eye. 6 treatments → 4.

   🟢 THEN THE COMPOSITION, SEPARATELY: "text looks good but these designs are
   boring." The photograph goes full-bleed and the cream block LIFTS UP INTO IT.
   Nothing else on this page layers; depth is the one visual idea it had never
   used, and it is what makes plain paragraphs stop being boring WITHOUT
   fragmenting them. Those two fixes are independent — do not undo the first to
   satisfy a future complaint about the second.
   -------------------------------------------------------------------------- */

/* 🔴 NO TOP PADDING. `.v4-sec` pads block-both, and a full-bleed figure under
   that padding renders a cream band above the photograph — exactly the defect
   QA'd on About §4 and PWU §4 on 7/31. The lift block supplies the top spacing
   instead. Bottom padding is kept. */
/* 🔴 BOTTOM PADDING CUT 128px -> 48px, 8/4. Brees: "SO much white space."
   Measured before touching it: 180px of cream sat below the last line of type at
   1900px — 52px of the lift's OWN padding and then another 128px of `.v4-sec`
   on top of it. The block already pays for its bottom breathing room; the
   section was paying a second time for the same gap.
   ⚠️ The lift's own 52px stays. That is what keeps the copy off the block's edge
   ("text needs breathing room") and was never the problem. */
.net-lasts--overlap { padding-top: 0; padding-bottom: clamp(32px, 3.6vw, 48px); }

/* Full width for free — a direct child of the section, OUTSIDE `.wrap`.
   ⚠️ NOT `100vw` + negative margin: that counts the scrollbar and overflowed
   the page by 27px in the mock. This project has now been bitten by that twice. */
.net-lasts__bleed { margin: 0; }
/* 🔴 TALLER, 8/4 — AND NOT FOR THE REASON IT LOOKS LIKE. Growing the photograph
   does NOTHING to the block's overhang on its own: the lift is pulled up from
   the photo's BOTTOM edge, so the overhang is `liftHeight - pull` whatever the
   photo's height is. The first attempt at this fix grew the photo by 280px and
   moved the overhang by exactly zero.
   📌 The photo is taller because it is what makes a DEEPER PULL SAFE. The old
   note below records that at -330px against a 600px photo the dog's face was
   cut off; against an 880px photo there is room above the block to keep the
   whole subject. Height buys headroom, not overlap.
   ⚠️ The 340px FLOOR IS UNCHANGED on purpose, so nothing below ~607px viewport
   moves. §8 forks to its stacked layout at 860px and that composition was not
   part of this pass. */
.net-lasts__bleed img {
  display: block; width: 100%;
  height: clamp(340px, 74vw, 1200px); object-fit: cover;
}

/* THE LIFT. ⚠️ The fill is DELIBERATE and is flagged in the markup: it is
   `--cream`, identical to the section's own ground, so it is invisible except
   where it occludes the photograph. No border, no radius. It reads as the page
   reasserting itself over a bleed, not as a panel around copy — and every
   paragraph inside it is transparent, which is what network-measure.js tests.
   ⚠️ The negative margin is the whole device. If it is ever removed, the
   section becomes a photo with text under it and the idea is gone. */
.net-lasts__lift {
  position: relative; z-index: 2; background: var(--cream);
  /* 🔴 THE PULL HAS A WINDOW, AND BOTH EDGES OF IT ARE REAL FAILURES. Retuned
     8/4 against the taller photograph; the old -280px reasoning is superseded
     and is kept below only as the record of what each end looks like.
       · PULL TOO LITTLE and the block finishes BELOW the photograph, cream on
         cream, with nothing behind its lower third. That was the live defect —
         the block stood ~270px taller than the picture, so the overlap died
         half way down and the bottom of the section read as a void. This is
         what Brees saw as "SO much white space"; the padding was only half of it.
       · PULL TOO MUCH and the block finishes entirely INSIDE the picture — and
         then it is a filled cream rectangle floating on a photograph, which is
         the AI-default card this whole page was de-boxed to remove. The first
         retune did exactly this and every measurement passed, because the check
         only guarded the hanging direction.
     🟢 THE TARGET IS THE MERGE: the block's bottom edge must cross the photo's
     bottom edge by a SMALL amount (20-60px), so the cream runs into the
     section's own cream ground. That is the entire justification for the fill
     being --cream — invisible except where it occludes the photograph. A block
     that clears the picture on all four sides is a panel, not a bleed.
     Window: liftHeight is 582 at 1440 and 567 at 1900, so the pull must land in
     [522,562] and [507,547]. 37vw clamped to 540 satisfies both; measured
     crossings are 28px and 27px.
     ⚠️ TUNED AND RE-RENDERED, NOT CALCULATED — liftHeight moves with the
     measure, so any change to the columns, the copy or the type scale means
     re-running `.shots/lasts-opts-check.js`, which asserts BOTH ends. */
  margin-top: calc(-1 * clamp(400px, 37vw, 540px));
  /* 🔴 MOVED RIGHT, 8/4. Brees: "maybe we move the box to the right instead of
     center." Measured first: the block was sitting LEFT of centre — 342px of
     photograph revealed on the left against 574px on the right — so the bleed
     read as two slivers rather than one picture and the dog was never a subject.
     The margins are now the mirror of what they were: the large margin moves to
     the LEFT to reveal the photograph as one continuous area, and the negative
     bleed moves to the RIGHT so the block still gains its padding outward.
     🟢 THE COPY DOES NOT LEAVE THE GRID — it re-anchors to the other edge. Its
     LEFT edge no longer matches §2/§3, but its RIGHT edge lands exactly on
     theirs (1506px at 1900, 1276px at 1440), because the negative right margin
     equals the padding. Asserted in `.shots/lasts-opts-check.js`.
     ⚠️ DO NOT PUSH THIS FURTHER RIGHT. A variant that did was built and
     rejected: the block's width and its distance from the left edge are the
     same number, so travelling further starves the two-column measure — it hit
     29ch at 1900px, where the breaks go ragged. This is the practical limit. If
     more photograph is ever wanted, the lever is the photo's height or the
     pull, never more horizontal travel. */
  margin-inline: clamp(88px, 15vw, 300px) calc(-1 * clamp(28px, 3.4vw, 52px));
  /* ⚠️ This margin-top only applies BELOW 861px now — see the grid overlay
     immediately after this rule, which supersedes it on desktop. It is kept
     because the stacked layout at <=860px still needs a pull to overlap at all. */
  /* 🔴 PADDING ON ALL FOUR SIDES, AND THE NEGATIVE LEFT MARGIN IS WHAT MAKES
     IT POSSIBLE. The first cut was `padding: 49px 49px 0 0` — top and right
     only — so the copy sat FLUSH against the block's left edge (measured 0px)
     while the right had 49px. Against the photograph that reads as text jammed
     into a corner. Brees: "text needs breathing room."
     ⚠️ Left padding alone would have shifted the copy off the page grid: §8's
     text starts at x=164, the same as §2's and §3's, and a section whose body
     copy does not line up with its neighbours looks broken in a different way.
     So the BLOCK bleeds left by exactly the padding it gains, and the TEXT
     does not move. Verified: text left stays 164, block left moves to 115.
     📌 When a filled block needs interior padding but its contents must stay on
     the grid, the block moves — not the type. */
  padding: clamp(28px, 3.4vw, 52px);
}

/* ==========================================================================
   🔴 THE WHITE BAR AT THE BOTTOM — REMOVED STRUCTURALLY, 8/4.
   Brees marked the full-width cream strip between the photograph's bottom edge
   and §9's navy and said remove it. At 1900px it measured ~75px: the section's
   remaining 48px of `padding-bottom`, plus the 27px the block was overhanging
   the photo by.

   🔴 WHY THIS IS A RESTRUCTURE AND NOT ANOTHER TUNED NUMBER. Killing the strip
   means the block's bottom edge, the photograph's bottom edge and the section's
   bottom edge all have to be the SAME line. With the negative-margin pull that
   is unachievable across the range, because the pull is a viewport-relative
   clamp and the block's height is content-relative — they do not track. The
   overhang measured 83 / 81 / 39 / 28 / 27px at 880 / 1024 / 1280 / 1440 /
   1900, so any single clamp that zeroes it at one width leaves a bar at the
   others. This file has already warned twice that this value "needs
   re-rendering at several widths, not arithmetic" — that is the smell of a
   number doing a structure's job.

   🟢 THE FIX: put the photograph and the copy block in the SAME grid cell and
   bottom-align the block. The row's height is the photograph (always the taller
   of the two across the whole desktop range — verified 493v409, 573v460,
   717v513, 806v561, 880v567), so the block's bottom lands exactly on the
   photograph's bottom at EVERY width, with no value to tune and nothing to
   drift. `padding-bottom: 0` then takes the section's own edge to the same line.

   🟢 AND IT KEEPS THE ANTI-CARD PROPERTY, BY A DIFFERENT ROUTE. The earlier
   version needed the block to cross into the section's cream ground so it read
   as continuous with the page rather than a panel floating on a picture. There
   is no cream ground below it any more — instead the block terminates flush
   against a hard structural edge, with §9's navy beginning on that exact line.
   A block anchored to a section boundary is not floating. ⚠️ What WOULD bring
   the card back is the block ending ABOVE the photograph's bottom, leaving a
   strip of photo beneath it — then it is inset on all four sides. The overlay
   makes that impossible by construction rather than by a guarded number.

   ⚠️ SCOPED TO >=861px ON PURPOSE. §8 forks to its stacked layout at 860px,
   where the figure and the copy are meant to sit in SEQUENCE, not on top of
   each other. Overlaying them there would collapse the stack.
   ⚠️ If the copy ever grows enough for the block to exceed the photograph, the
   row grows to the block and a cream gap returns under the photo. Asserted in
   `.shots/lasts-live-check.js`.
   ========================================================================== */
@media (min-width: 861px) {
  .net-lasts--overlap { display: grid; grid-template-columns: 1fr; padding-bottom: 0; }
  .net-lasts--overlap > .net-lasts__bleed,
  .net-lasts--overlap > .wrap { grid-area: 1 / 1; }
  .net-lasts--overlap > .wrap { align-self: end; }
  /* The pull is now the grid's job. Leaving the negative margin here would
     lift the block off the bottom edge again by exactly its old value. */
  .net-lasts__lift { margin-top: 0; }
}

/* The headline carries the drama the body copy is no longer allowed to.
   ⚠️ THE DISPLAY STEP (48px), NOT A NEW ONE. Round 10 cut this page from 26
   sizes to 7; a sixth step was mocked (D3, ~99px) and deliberately not taken. */
.net-lasts__head {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-display); line-height: 1.04; letter-spacing: -.025em;
  color: var(--navy); max-width: 15ch; margin: 0 0 var(--sp-2);
}

/* 🔴 TWO COLUMNS, RESTORED 8/3. I had switched this to one column on my own
   judgement — the `columns: 2` break was landing mid-phrase — and Brees called
   it: "it was suppose to be two columns of text." He approved D2 with two
   columns; changing it was an unsolicited change, not a fix.
   📌 The right response to an ugly break is to tune the break, not to discard
   the layout that was signed off.

   ⚠️ CSS `columns`, not a two-column grid. A grid needs the paragraphs
   pre-assigned to sides, and with three paragraphs the only splits available
   are 4|8 and 9|3 — reading order (down column one, then column two) rules out
   anything balanced. `columns` balances the flow itself and keeps the order.
   ⚠️ orphans/widows at 3 — higher than the usual 2 — because a two- or
   three-line straggler at a column edge is what makes this technique look
   careless. Raising it also pushes the break away from the very start of a
   paragraph, which is where the worst break landed the first time. */
.net-lasts__prose { columns: 2; column-gap: clamp(32px, 4vw, 64px); orphans: 3; widows: 3; }

/* Prose at the section's reading size — the same 19.2px on a measure that §3
   runs on. This is what stops continuous prose reading as a wall without
   chopping it back into fragments. Asserted in network-measure.js (>=18px). */
.net-lasts .net-lasts__para {
  font-family: var(--font-head); font-weight: 400;
  font-size: var(--t-body); line-height: 1.62;
  color: var(--navy-soft); margin: 0; max-width: 46ch;
}
/* ⚠️ margin-TOP, and `:first-child` reset, so a paragraph pushed to the top of
   the second column does not carry a stray indent-gap above it. */
.net-lasts .net-lasts__para + .net-lasts__para { margin-top: 1.1em; }
.net-lasts__prose .net-lasts__para { max-width: none; }
/* ⚠️ Do NOT add `break-inside: avoid` here. It forces the break back to
   paragraph boundaries and returns the 4|8 imbalance this exists to fix. */

/* The two emphases, both inline, both by weight+colour and never by size.
   `__triad` is Sean's "Trust is. Respect is. Excellence is."; `__turn` is the
   closing "They are built by becoming better." */
.net-lasts__triad { color: var(--navy); font-weight: 500; }
.net-lasts__turn  { color: var(--navy); font-weight: 500; }


/* --------------------------------------------------------------------------
   §9 — JOIN THE MOVEMENT (image + flat scrim)
   -------------------------------------------------------------------------- */
.net-close {
  position: relative; overflow: hidden; text-align: center;
  /* 🔴 A REAL background-color is load-bearing. If the image 404s the scrim
     would otherwise sit over the page's CREAM base and cream text would land on
     a light ground — the defect deep-a11y surfaced on PWU §6 (it reported
     1.00:1 because it resolves the SECTION's background-color and cannot see
     the image). Never silence the checker instead of giving it a real ground. */
  background: var(--navy-deep);
}
.net-close__bg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0;
}
/* ⚠️ .85 — LIGHTENED FROM .88 ON 8/4 AT BREES' ASK ("lighten just a little"),
   AND .85 IS ALMOST THE WHOLE BUDGET. The §9 photograph is backlit and its
   brightest pixels are pure white, so the scrim is the only thing holding the
   copy legible. Swept against `.shots/network-contrast.js`:
       .88  border 3.28:1   creed 5.25:1   pass
       .85  border 3.11:1   creed 4.88:1   pass   <- shipped
       .84  border 3.06:1   creed 4.78:1   pass, but 0.06 from the floor
       .80  border 2.84:1   creed 4.31:1   ✖ FAILS both
   The binding constraint is `.btn--ghost-dark`'s 1px border against a 3:1
   non-text floor (WCAG 1.4.11), not the body copy. **Do not lighten further
   without changing that border.**
   🔴 AND THE CHECK THAT "DERIVED" THIS USED TO BE UNABLE TO SEE IT. The scrim
   was hardcoded inside network-contrast.js, so editing this value produced
   byte-identical output — .88 and .80 both reported "alpha 0.88 … PASS". The
   old comment here said "re-run it if the opacity changes", which could never
   have worked. The checker now parses this declaration out of this file, and
   the sweep above is the proof it responds. */
.net-close__scrim { position: absolute; inset: 0; z-index: 1; background: rgba(18, 29, 59, .85); }
.net-close .wrap { position: relative; z-index: 2; }

.net-close__title {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-display); line-height: 1.18;
  color: var(--cream); max-width: 18ch; margin: 0 auto;
}
.net-close__sub {
  font-family: var(--font-head); font-weight: 400;
  font-size: var(--t-statement); line-height: 1.4;
  color: rgba(251,247,239,.86); max-width: 46ch; margin: var(--sp-1) auto 0;
}
/* `.net-close__lede` lived here until 7/31 and was DELETED WITH ITS MARKUP —
   the preamble it styled is now `.net-creed__lede` inside the running creed.
   Left behind it would have been a dead rule; this file has already paid for
   the mirror-image bug (a live `.net-motto` consumer whose rule was deleted),
   so both directions get checked now. */

/* THE CREED — one running statement. See the markup note.
   The whole block is set at statement scale rather than body scale: it is now
   the only prose in the section, so it can afford the size, and size is what
   stops a wrapped paragraph reading as "more text". */
.net-creed {
  max-width: 40ch; margin: var(--sp-2) auto 0;
  font-family: var(--font-head); font-weight: 400;
  font-size: var(--t-statement); line-height: 1.5;
  text-wrap: balance;
}
/* The preamble steps back. It is scaffolding for the five convictions, not one
   of them — at equal weight it competed with the lines that matter. */
.net-creed__lede { color: rgba(251,247,239,.62); }
.net-beliefs {
  display: inline; list-style: none; margin: 0; padding: 0;
}
.net-beliefs li {
  display: inline; color: var(--cream);
}
/* A space between sentences, generated rather than typed, so the markup holds
   the doc's words and nothing else. */
.net-beliefs li + li::before { content: " "; }
.net-close__standing {
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-statement); line-height: 1.35;
  color: var(--cream); margin: var(--sp-3) auto var(--sp-1); max-width: 30ch;
}

/* THE CLOSING RISE — see the markup note. This is the page's largest line now;
   it was its smallest.
   ⚠️ THE MASK IS LOAD-BEARING and so is the padding/negative-margin pair:
   `overflow:hidden` clips at the text box, which would shave the descenders of
   "Upward" and "Together" without it. Safe here — nothing inside §9 is sticky.
   ⚠️ SPECIFICITY IS DELIBERATE AND ORDER-INDEPENDENT. `.reveal.is-in` in
   site.css is (0,2,0); the travel below is (0,3,0) so it wins the resting
   state, and the `.is-in` rule under it is (0,4,0) so it wins the arrival.
   Get this backwards — the trap this file documents seven times — and the line
   simply never arrives. Asserted in .shots/network-measure.js. */
.net-close__ever {
  overflow: hidden;
  padding-bottom: .14em; margin: 0 auto -.14em;
}
.net-close .net-ever__line {
  display: block;
  font-family: var(--font-head); font-weight: 500;
  font-size: var(--t-display); line-height: 1.15;
  color: var(--cream);
}
.net-close .net-ever__line.reveal { transform: translateY(100%); }
.net-close .net-ever__line.reveal.is-in { transform: none; }
/* Slower than the shared .5s: this is a curtain line, not a fade-in. */
.net-close .net-ever__line.reveal { transition: opacity .6s var(--ease), transform .95s var(--ease); }
.net-close .btn-row { justify-content: center; margin-top: var(--sp-2); }

/* --------------------------------------------------------------------------
   RESPONSIVE
   🔴 THIS BLOCK MUST STAY BELOW THE BASE RULES. Media queries add NO
   specificity — source order decides between equal-weight rules. On PWU the
   collapse was written ABOVE the two-column rule and lost at every width,
   rendering a 35px lead inside a 160px column at 390px. Do not move this up.
   -------------------------------------------------------------------------- */
/* ⚠️ The 1100px and 640px `.net-beliefs` column steps were REMOVED on 7/31
   with the creed rebuild. They set `grid-template-columns` on an element that
   is now `display:inline` — inert, but the kind of dead rule that reads as
   live intent to whoever edits this next. Running prose needs no breakpoints;
   it wraps. */
@media (max-width: 900px) {
  /* ⚠️ `.net-pledge__list { grid-template-columns: 1fr }` was REMOVED here on
     7/31 — the charter is a single column at every width now, so the rule was
     collapsing a grid that no longer exists. */
  .net-phil__grid { grid-template-columns: 1fr; gap: var(--sp-2); }
}
@media (max-width: 960px) {
  /* The constellation collapses first: below this the diagram column is too
     narrow for the web to read as a web, and a squashed diagram beside the
     copy is worse than no diagram. The list is the content and stands alone. */
  .net-web__stage { grid-template-columns: 1fr; gap: var(--sp-2); }
  .net-web__diagram { position: static; max-width: 380px; margin-inline: auto; }
}
@media (max-width: 860px) {
  /* §8's overlap: the lift stops hanging off the right and the two prose
     columns become one. The negative pull is reduced but KEPT — losing it
     entirely turns the section into a photo with text underneath. */
  /* ⚠️ REWRITTEN WITH THE PADDING FIX. This used to zero the right margin and
     the right padding, which was correct when the block had padding on only two
     sides. It now has padding on all four, and the desktop rule pulls the block
     LEFT by that amount — so on a phone the offset has to be released or the
     block hangs off the screen edge. `margin-inline: 0` does that in one go;
     the padding stays symmetric so the copy keeps its breathing room. */
  .net-lasts__lift {
    margin-top: clamp(-96px, -11vw, -56px);
    margin-inline: 0;
    padding: var(--sp-2) var(--sp-1);
  }
  .net-lasts__prose { columns: auto; }
  /* §3's two paragraphs stack. The measure cap comes off so a single
     column does not sit in a 46ch ribbon on a 700px screen. */
  /* §3's triptych: site.css already drops `.triptych` to two columns with the
     text spanning both, and zeroes its own `--video` / `--img` stagger there.
     These two lines are the same release for the position-named classes — the
     stagger is a wide-viewport idea and becomes a lopsided gap once the panels
     sit side by side under the copy.
     ⚠️ If site.css's 900px breakpoint ever moves, these move with it. */
  .net-why .net-why__para { max-width: none; }
  .net-why__panel--high { margin-bottom: 0; }
  .net-why__panel--low  { margin-top: 0; }
  /* §8's measures stack two-then-one rather than staying three across —
     "Excellence is." at display scale needs more than a third of 860px. */
  /* The support pair and the larger/better contrast both collapse. The payoff
     keeps its order — the rejected idea still reads before the answer. */
  .net-lasts .net-lasts__para { max-width: none; }
  .net-resources { grid-template-columns: 1fr; }

  /* §5. The lead band collapses and the split stacks. ⚠️ Stacked, the ONLY
     thing carrying the antithesis is the type contrast — size, weight and
     colour — because the rules and panels that used to help are gone (round 8).
     So do not equalise these two on small screens; the gap between them is
     the argument. */
  /* §2's bento collapses to one column; every box spans it. */
  /* §2 collapses: copy above the picture, and the keep/gain pair stacks with
     the divider moving from a vertical rule to a horizontal one. */
  .net-intro__head { grid-template-columns: 1fr; gap: var(--sp-2); }
  .net-pair { grid-template-columns: 1fr; gap: var(--sp-2); }
  .net-pair::before { display: none; }
  .net-intro__turn { max-width: none; }

  /* §5's threshold collapses to picture-then-copy. The crossing is a
     side-by-side idea and there is no width to cross on a phone, so it
     becomes a normal stacked section rather than a squeezed one.
     ⚠️ The figure needs an explicit height here — it is absolutely positioned
     inside its wrapper, so a one-column grid gives it nothing to fill. */
  .net-thresh { grid-template-columns: 1fr; }
  .net-thresh__fig { min-height: clamp(260px, 62vw, 420px); }
  .net-thresh__body {
    padding-inline: var(--gutter);
    padding-block: clamp(56px, 9vw, 88px);
    max-width: none;
  }
  /* §5's split is one paragraph now (round 8) — nothing to collapse. */
}
