/* =============================================================================
   GMG site.css — THE AUTHORED LANE.
   Geneva Medical Group legacy conversion, Stage D.

   Behaviour Bricks cannot express natively (BRICKS_ADAPTATION.md §3). Four of
   the five risky behaviours live in this file (GENERATION_BRIEF §5.1):
     1. the display-heading clamp and its 1182 px top-out
     2. the sticky header state WITHOUT live's 80 px content jump
     3. the drawer scroll lock
     4. the prefers-reduced-motion block (OPEN O-10 — the platform ships none)

   ⛔ EVERY VALUE HERE IS A TOKEN OR A MEASUREMENT. Nothing is eyeballed. Where a
   number appears literally it is because it is a parity constraint with a
   measurement behind it, and the measurement is cited on the line.
   ============================================================================= */


/* =============================================================================
   §0. THE VENDOR REGISTER — ⛔ ONE BLOCK, THE VENDOR RULE QUOTED BESIDE IT.

   COMPOSITION_NOTES.md §6a and §6a-bis require every discovery about a Bricks
   frontend rule to be countered ONCE, here, with the vendor rule quoted — never
   by re-scaling individual token values or patching per instance.
   ============================================================================= */

/* VENDOR RULE (web/app/themes/bricks/assets/css/frontend-layer.min.css):
       html{font-size:62.5%}
   CONFIRMED PRESENT **AND CONFIRMED ENQUEUED** on this build instance
   (STAGE_D_PREP_LOG §7): the file ships and the served front page carries
       <link rel='stylesheet' id='bricks-frontend-css' href='…/frontend-layer.min.css'>

   ⛔ WHY THIS IS MANDATORY AND NOT PRECAUTIONARY. Bricks' 62.5 % convention makes
   1rem = 10px. Every token in docs/design/tokens.json is authored against a
   16 px root. The display heading's ceiling is `8.125rem`, and

       8.125rem is 130px ONLY at a 16px root.  At 62.5% it is 81.25px.

   130 px is the parity constraint — tokens.json type.display: *"130px NEVER
   STEPS, at any of the 13 captured widths"* — and battery row B-7 asserts
   exactly 130 px at ≥1182. So the root is restored here, once.

   ⛔ Do NOT "fix" this by writing 13rem instead of 8.125rem. That re-scales one
   value and leaves every other rem in the design package wrong, which is the
   per-instance patch §6a forbids. */
html {
  font-size: 100%;
}


/* =============================================================================
   §0b. THE FIVE SELF-HOSTED FACES — ⛔ ROUND 1 SHIPPED NONE OF THEM.

   MEASURED on the round-2 build before this block existed: B-19 reported all
   five required faces *"ABSENT from document.fonts on every surface"*, `0 face
   tuple(s) reach "loaded" sitewide`, and — the finding that says what was really
   happening — *"Albert Sans: two faces produce an IDENTICAL canvas advance width
   [500 → 549.8, 700 → 549.8]"*. Identical advance widths for two different
   weights is the signature of a family that is not there at all: every heading,
   every button and every line of body copy was rendering in `system-ui`, and
   nothing on the page looked broken. ⚠ §5c rule 10 exactly: *loading* is
   `document.fonts`, *painting* is the fingerprint, and the fingerprint is what
   caught it.

   PROVENANCE — ⛔ these files are the LIVE SITE'S OWN, harvested in Stage A, not
   fetched from anywhere at build time. `gmg-capture/assets/fonts/` holds 16
   woff2 files and `*_css` the Google Fonts stylesheets that named them, so the
   family / style / weight / subset of each is read from live's own declaration
   rather than guessed from a hash:

     albert-sans-var-latin.woff2      <- i7dOIFdwYjGaAMFtZd_QA1ZbYFc.woff2
                                         Albert Sans · normal · VARIABLE 100-900 · latin
     rubik-var-latin.woff2            <- iJWKBXyIfDnIV7nBrXw.woff2
                                         Rubik · normal · VARIABLE 300-900 · latin
     rubik-var-italic-latin.woff2     <- iJWEBXyIfDnIV7nEnX661A.woff2
                                         Rubik · italic · VARIABLE 300-900 · latin

   Both families are SIL OFL 1.1 (tokens.json font.display/text `licence`), and
   the licence ships beside them in `fonts/OFL.txt`.

   ⛔ FIVE `@font-face` RULES OVER THREE FILES, AND THE WEIGHT DESCRIPTOR IS THE
   POINT. These are VARIABLE fonts. A single rule with `font-weight: 100 900`
   would register ONE FontFace whose `weight` reads "100 900", and
   `tokens.json.font.$shipped` names five tuples — `Albert Sans 500 normal`,
   `Albert Sans 700 normal`, `Rubik 400 normal`, `Rubik 600 normal`,
   `Rubik 400 italic` — which B-19 asserts one by one. Declaring each face with a
   SINGLE weight instantiates the variable font at that weight (CSS Fonts 4
   §font-weight descriptor) and registers exactly the five tuples the token file
   declares. One file is downloaded once and shared by its two faces.

   ⚠ LATIN ONLY, AND THAT IS A RECORDED DEVIATION. Live serves latin AND
   latin-ext per face behind `unicode-range`; this build ships the latin subset
   alone. Two reasons: a latin-ext face that nothing on an English-language
   practice site ever paints never reaches `loaded`, and B-19 bounds the shipped
   set FROM BOTH SIDES (*"a build that ships MORE faces than $shipped has not
   passed this row, it has changed the contract"*). ⛔ The consequence is real and
   is not hidden: a visitor who sees a latin-ext glyph — an accented name in a
   form echo — gets the fallback stack for that glyph. GENERATION_DECISIONS D-9.

   `font-display: swap` is live's served value on every one of its @font-face
   rows. It is parity, not a choice.
   ============================================================================= */

@font-face {
  font-family: 'Albert Sans';
  font-style: normal;
  font-weight: 500;                                  /* nav links · eyebrow pills */
  font-display: swap;
  src: url(fonts/albert-sans-var-latin.woff2) format('woff2');
}
@font-face {
  font-family: 'Albert Sans';
  font-style: normal;
  font-weight: 700;                                  /* every heading and button label */
  font-display: swap;
  src: url(fonts/albert-sans-var-latin.woff2) format('woff2');
}
@font-face {
  font-family: 'Rubik';
  font-style: normal;
  font-weight: 400;                                  /* all body copy and the footer */
  font-display: swap;
  src: url(fonts/rubik-var-latin.woff2) format('woff2');
}
@font-face {
  font-family: 'Rubik';
  font-style: normal;
  font-weight: 600;                                  /* the "Read More" text links */
  font-display: swap;
  src: url(fonts/rubik-var-latin.woff2) format('woff2');
}
/* ⚠ THE FIFTH IS A REASONED EXCEPTION AND `tokens.json` SAYS SO: Rubik 400
   italic paints on no measured element, but it reaches "loaded" on ALL 77
   capture rows, so it is the site's global body italic — and *"a missing italic
   face is not a missing style, it is a SYNTHESISED OBLIQUE, which is a visible
   defect on any <em> that composed copy introduces."* 20 KB against that risk. */
@font-face {
  font-family: 'Rubik';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/rubik-var-italic-latin.woff2) format('woff2');
}


/* =============================================================================
   §1. TOKEN FALLBACKS.

   The variables are seeded into Bricks (bin/seed-tokens.php → bricks_global_variables)
   and Bricks emits them on :root. These fallbacks exist so this stylesheet is
   legible and testable in isolation and so a missing variable degrades to the
   measured value rather than to `unset`. ⛔ They are NOT a second home for the
   tokens — if one disagrees with tokens.json, tokens.json wins and B-3 fails.
   ============================================================================= */
:root {
  --gmg-brand-blue:  #0E3B8A;   /* raster 9,108,537 px — THE site colour */
  --gmg-brand-deep:  #031B33;
  --gmg-white:       #FFFFFF;
  --gmg-band-blush:  #FFF9FB;
  --gmg-band-mint:   #F1FCF9;
  --gmg-panel-grey:  #E6E6E6;
  --gmg-field-grey:  #F6F7FA;
  --gmg-bar-ink:     #1B1724;
  --gmg-cta-pink:    #FFB6C1;

  --gmg-ink:          #0E0C0A;
  --gmg-ink-body:     #2D4443;
  --gmg-ink-on-blue:  #F1FCF9;
  --gmg-ink-footer:   #CDD1D6;
  --gmg-ink-display:  #F6F6F6;

  --gmg-hairline:     #E5E5E5;
  --gmg-rule-on-blue: rgba(255,255,255,0.2);

  /* ⛔ ONE CONSUMER, ONE ROUTE. Owner ruling O-2(b). Not a palette entry and not
     a second primary — see bin/seed-tokens.php. */
  --gmg-submit-blue:  #3970FF;

  --gmg-scrim: rgba(0, 0, 0, 0.56);   /* O-2(a), RE-DERIVED round 4 — see §4 */

  --gmg-radius-xs:   5px;
  --gmg-radius-sm:  10px;   /* the dominant radius */
  --gmg-radius-md:  20px;
  --gmg-radius-lg:  30px;
  --gmg-radius-pill: 9999px;

  --gmg-gap-xs:  5px;
  --gmg-gap-sm: 10px;
  --gmg-gap-md: 20px;
  --gmg-gap-lg: 30px;
  --gmg-gap-xl: 50px;

  --gmg-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --gmg-duration-fast:    150ms;
  --gmg-duration-base:    250ms;
  --gmg-duration-reveal:  600ms;
  --gmg-duration-counter: 2000ms;

  --gmg-shadow-header: rgba(197, 197, 197, 0.25) 7px 4px 20px 0px;

  /* ⛔ THE ONE GLYPH THAT CANNOT BE A CHILD NODE. Every other icon on this site
     is an inline <svg> the composers emit (tools/generation/gmg-icon-set.php);
     the FAQ toggle is a `::after` on a <summary> and a pseudo-element has no
     children, so it is masked instead. Kept HERE rather than inline in §16 so
     P-4 sees it as a declared variable and B-24 has a single place to read.
     MEASURED off live: a bare ~14 px chevron, ~2 px stroke, no ring. */
  --gmg-glyph-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 9 7 7 7-7'/%3E%3C/svg%3E");

  /* Chrome geometry. 100 and 80 are BOTH real and they are DIFFERENT ELEMENTS —
     see §3. The scrolled height is the one anchors must clear. */
  --gmg-header-h:          80px;    /* .she-header-yes at rest @1440 */
  --gmg-header-h-scrolled: 100px;   /* .she-header-yes scrolled @1440 */
  --gmg-header-h-mobile:   64px;    /* AUTHORED — live is 102 (wrapper 122) */
  /* ⛔ THE FLOW RESERVATION, and it is the SCROLLED height, never the resting
     one. LAYOUT_TRUTH §3: "a builder who reserves 100px of header has reserved
     the WRAPPER, and one who reserves 80 has reserved the HEADER — they are not
     interchangeable, and the difference is exactly the 80px jump B-8 exists to
     prevent." Reserving 80 would let the scrolled state push the page by 20. */
  --gmg-header-wrapper-h:  100px;
  /* ⛔ ROUND-4 — THE ROW. A THIRD NUMBER, AND NOBODY HAD WRITTEN IT DOWN.
     The header BOX is 80 at rest and 100 scrolled (above, and both are right;
     `tokens-measured.json` chrome carries them correctly and round 4 re-read it
     to be sure — nothing is transposed there). What NO artifact in this package
     carries is where the LOGO AND NAV SIT INSIDE that box. Measured off live
     2026-09-11 (`gmg-capture/tools/probe-r4b.mjs`, at 1440 AND 2560, identical):
         rest      box 80,  padding 0     logo y 11 (209x59)   nav y 0,  h 80
         scrolled  box 100, padding 10/10 logo y 21            nav y 10, h 80
     ⇒ the ROW is 80 px in BOTH states, the state change moves it DOWN by 10, and
     the logo is centred in it: (80 − 59) / 2 = 10.5.
     ⚠ The build shipped the logo at y 0 and a 45 px nav for three rounds. The
     owner read it as *"no padding above the header"* and every chrome row was
     green, because ⛔ NO ROW MEASURED A BOX'S POSITION INSIDE ANOTHER BOX —
     box-truth compared widths and heights only. It now carries `within`/`dy`. */
  --gmg-header-row-h:      80px;
}


/* =============================================================================
   §2. THE CONTAINER LADDER.

   ⛔ `.brxe-container` LAYOUT MEANS RESTATING THREE PROPERTIES
   (COMPOSITION_NOTES.md §6a-bis): any authored rule targeting a Bricks container
   states `flex-direction`, `align-items` AND `width`/`max-width` TOGETHER, even
   where two match the default.

   ⛔ AND BRICKS SETS A FIXED `width`, NOT `max-width` — so an authored
   `max-width` NO-OPS against it. Set the property Bricks set.

   ⚠ ONE FORMULA, NOT NINE HARD-CODED WIDTHS:

        width: min(100% - 30px, <cap>)

   Derived from docs/design/tokens.json layout.containerLadder and verified
   against EVERY measured rung — there are no per-width literals here:

     cap 540  ≤767 :  360→330 ✓   375→345 ✓   767→540 ✓
     cap 720  ≤1024:  768→720 ✓  1024→720 ✓
     cap 1314 ≥1025: 1025→995 ✓  1280→1250 ✓  1440→1314 ✓  1920→1314 ✓  2560→1314 ✓
     cap 1900 (hero): 1025→995 ✓ 1280→1250 ✓ 1440→1410 ✓ 1920→1890 ✓ 2560→1900 ✓
     cap 1140 (404) : 1440→1140 ✓ 2560→1140 ✓   (gutter 10px, not 15 — ElementsKit's own)

   ⛔ The ladder is NOT in the Bricks theme style. `general.containerWidth` stores
   and is never consumed — HBA measured it doing nothing while every container
   rendered at the 1100 px default. Battery row B-4 measures the RENDERED width
   and does not care which lane produced it.
   ============================================================================= */

.gmg-container {
  flex-direction: column;                 /* restated per §6a-bis */
  align-items: stretch;                   /* restated per §6a-bis */
  width: min(100% - 30px, 540px);         /* the property Bricks sets */
  max-width: none;
  margin-inline: auto;
  /* ⚠ ROUND 2 — THE STACK GAP. A `.brxe-container` is a flex column with NO gap,
     so a band whose container holds two blocks (a section head and a grid) puts
     them flush against each other. Round 1's home Services band was 162 px short
     for exactly this reason and nothing in a computed-style walk says "these two
     boxes are touching". Every layout class that sets its own gap (§15 - §16)
     overrides this, and each of those is declared LATER in this file so the
     override actually takes. */
  gap: var(--gmg-gap-xl);
}
@media (min-width: 768px) {
  .gmg-container { flex-direction: column; align-items: stretch; width: min(100% - 30px, 720px); }
}
/* ⛔ 1025, NOT 1024. The desktop boundary and the nav flip are the same boundary,
   and both Elementor boundary pairs (767|768, 1024|1025) were captured in pairs
   precisely so this could be asserted rather than assumed. */
@media (min-width: 1025px) {
  .gmg-container { flex-direction: column; align-items: stretch; width: min(100% - 30px, 1314px); }
}

/* ⚠ THE ONE 1900 CONTAINER ON THE SITE — the home hero band, and nothing else.
   LAYOUT_TRUTH §2.1: at the owner's 2560 review width the hero spans 1900 while
   the four sections below it span 1314 — a 293 px gutter difference per side,
   invisible at 1440 and unmissable at 2560. A build that makes every section
   1900 wide is wrong at 1440 and spectacularly wrong at 2560. */
@media (min-width: 1025px) {
  .gmg-band--wide > .gmg-container {
    flex-direction: column; align-items: stretch;
    width: min(100% - 30px, 1900px);
  }
}

/* ⚠ THE HEADER SHELL IS A SEPARATE CONTAINER FROM THE CONTENT COLUMN, and only
   above 1025. 877 px with ZERO side padding at 1025/1280/1440/1920/2560 — so at
   2560 the header row sits in an 877 px shell inside a 1900/1314 page.
   ⛔ BELOW 1025 IT TAKES THE ORDINARY CONTENT CONTAINER (540/15 at ≤767, 720/15
   at 768–1024). The divergence begins exactly where the nav flips, and B-4
   asserts BOTH halves — an earlier draft of the design package claimed 877 at
   every width and the adversary pass fired on all eight sub-1025 rungs. */
@media (min-width: 1025px) {
  .gmg-header__shell {
    flex-direction: row; align-items: center;
    width: 877px;
    padding-inline: 0;
    margin-inline: auto;
  }
}

/* The 404 stage sits in ElementsKit's own wrapper, not in the 1314 column.
   ⛔ The `min(100%, 1140px)` spacer that appears on every route is a DIFFERENT
   element — do not join them (LAYOUT_TRUTH §2.5). */
.gmg-404__stage {
  flex-direction: column; align-items: flex-start;
  width: min(100% - 20px, 1140px);
  margin-inline: auto;
}


/* =============================================================================
   §3. THE STICKY HEADER — ⛔ THE OUTCOME IS ZERO CONTENT DISPLACEMENT.

   ⚠ THE SINGLE MOST LIKELY PARITY MISS ON THIS SITE, and it is a miss in the
   OPPOSITE direction from the usual one.

   LIVE'S DEFECT (tokens.json color.chrome.$theDefect, MOBILE_DEFECT_AUDIT M-6):
   the header GROWS 80 → 100 while its wrapper COLLAPSES 100 → 20, so page
   content JUMPS UPWARD by ~80 px the moment the user starts scrolling — at every
   width and on every route (`templateWrapperCollapsePx: 80`).

   ⛔ REPRODUCE THE SCROLLED STATE. DO NOT REPRODUCE THE WRAPPER COLLAPSE.

   `position: sticky` is used rather than `fixed` + a spacer, because sticky keeps
   the element IN FLOW by construction — there is no reserved height to get wrong
   and no spacer to forget. Battery row B-8 asserts the OUTCOME (the same content
   offset before and after), NOT the CSS mechanism, and samples rest on a freshly
   loaded page with `window.scrollY === 0` asserted at sample time.

   ⚠ `position: sticky` has ONE fragile precondition and §5 is where it is kept:
   no ancestor may create a scroll container. That is why every clip in this file
   is `overflow-x: clip` and NEVER `overflow-x: hidden`.
   ============================================================================= */

/* ⛔ ROUND-2 CORRECTION — `sticky` WAS THE WRONG MECHANISM, AND B-8 MEASURED IT.
   §3's argument above is still right about the OBJECTIVE (zero content
   displacement) and was wrong about the means. `position: sticky` keeps the
   element IN FLOW, which is what the argument liked about it — and an element in
   flow that GROWS 80 → 100 on scroll pushes everything below it down by 20.
   MEASURED on all five routes: *"CONTENT DISPLACEMENT +20px. <main> sat at
   document offset 80 before the scroll and 100 after."* Smaller than live's 80 px
   jump and in the opposite direction, and still a jump.

   ⛔ THE FIX IS THE ONE §5b NAMES: `fixed`, with the height RESERVED IN FLOW by
   the template wrapper. The wrapper is 100 px — which is live's own wrapper
   register (LAYOUT_TRUTH §3: *"the template wrapper is 100; the header itself is
   80"*), and the 20 px difference is the same 20 px LIVE carries as a separate
   spacer row that rhythm-diff omits by ruling. So the build's document arithmetic
   matches live's exactly, and the scrolled header grows INTO reserved space
   instead of pushing the page.

   ⚠ The two states are 80 and 100 and BOTH fit inside the reserved 100, so there
   is no width or route at which the header can move the document. That is the
   property B-8 asserts, and it now holds by construction rather than by hope. */
#brx-header,
body > header:first-of-type {
  min-height: var(--gmg-header-wrapper-h);
}
.gmg-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 9999;
  background: var(--gmg-white);
  box-shadow: var(--gmg-shadow-header);   /* ⛔ verbatim, spaces inside rgba() included */
  /* Height is driven by padding so the state change animates without the box
     jumping between two fixed heights. */
  --gmg-header-pad: 0px;
  padding-block: var(--gmg-header-pad);
  min-height: var(--gmg-header-h);
  display: flex;
  /* ⛔ ROUND-4 — `flex-direction` STATED, AND THIS IS THE §2 RULE APPLIED TO A
     SECTION INSTEAD OF A CONTAINER. `.gmg-header` is a `.brxe-section`, and a
     Bricks section is `display: flex; flex-direction: COLUMN`. With the column
     default still in force, `align-items: center` centred the shell on the
     HORIZONTAL axis (the cross axis of a column) and left the vertical axis to
     `justify-content`, which defaulted to flex-start — ⛔ so the logo sat flush
     at y 0 and the nav at y 7, inside a header whose height was perfectly
     correct. §2 says "any authored rule targeting a Bricks container states
     flex-direction, align-items AND width TOGETHER"; the rule is the same for a
     section and this file did not follow it here.
     `stretch`, not `center`: the shell must FILL the row so the nav links can be
     80 px tall (live's are 80x80 / 100x80 / 107x80 / 123x80, y 0). */
  flex-direction: row;
  align-items: stretch;
  justify-content: center;
  transition: min-height var(--gmg-duration-base) var(--gmg-ease-standard),
              padding-block var(--gmg-duration-base) var(--gmg-ease-standard);
}
.gmg-header--scrolled {
  min-height: var(--gmg-header-h-scrolled);
  --gmg-header-pad: 10px;   /* live's scrolled padding 0 → 10px */
  /* ⛔ background and shadow are UNCHANGED on scroll. tokens.json color.chrome:
     "The header does NOT change colour on scroll. It changes POSITION and SIZE." */
}
@media (max-width: 1024px) {
  /* ⚠ ONE HEIGHT BELOW 1025, so the reservation equals both states and the
     document cannot move. The authored register is 64 (live is 102, wrapper 122
     — "the chrome does not compress on live at all", F-8). */
  .gmg-header,
  .gmg-header--scrolled { min-height: var(--gmg-header-h-mobile); }
  .gmg-header--scrolled { --gmg-header-pad: 0px; }
  :root {
    --gmg-header-wrapper-h: var(--gmg-header-h-mobile);
    /* ONE height below 1025 means ONE row: the shell fills the whole 64. */
    --gmg-header-row-h: var(--gmg-header-h-mobile);
  }
}

/* WCAG 2.2 AA 2.4.11 — an anchor target must not land under the sticky header.
   Matched to the SCROLLED height, which is the taller of the two states. */
:target,
[id]:not(body) { scroll-margin-top: calc(var(--gmg-header-h-scrolled) + var(--gmg-gap-md)); }

/* The skip link — the only keyboard affordance the legacy chrome has, so it is
   kept rather than re-invented. Its landing must clear the scrolled header. */
.gmg-skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 10000;
  background: var(--gmg-brand-blue); color: var(--gmg-white);
  padding: var(--gmg-gap-sm) var(--gmg-gap-md);
}
.gmg-skip-link:focus { left: 0; }


/* =============================================================================
   §4. THE DISPLAY HEADING AND ITS SCRIM.
   ============================================================================= */

/* ⛔ THE CLAMP CANNOT BE EXPRESSED AS THREE RESPONSIVE VALUES without losing the
   1182 px top-out, which is the parity constraint. Hence the authored lane.

   clamp(2.75rem, 11vw, 8.125rem) at a 16px root (see §0):
       44px floor  ·  11vw  ·  130px ceiling
   11vw reaches 130px at 1181.8px, so it tops out at ≥1182 — B-7's assertion.
   At 375: 11vw = 41.25px, so the 44px floor wins — B-7's other half (≤44px). */
/* ⛔ THE LINE-HEIGHT IS THE ONE PLACE THIS PACKAGE CONTRADICTS ITSELF, AND THE
   SPLIT BELOW IS THE GOVERNING RULE APPLIED RATHER THAN A PREFERENCE PICKED.

     tokens type.display.bands      130 / 36 · 130 / 47 · 130 / 65
     tokens type.display.$parity    "Only the line-height moves, and it is BELOW
                                     the font size in ALL THREE BANDS"
     tokens type.display.$authored  "clamp(...) with line-height 1.05 AT <=1024"
     §5b B-7                       "line-height >= 1.05 AT EVERY WIDTH"

   B-7's "every width" and the measured bands cannot both hold: 65 on a 130 px
   font is 0.5. MEASURED both ways on this build — with 1.05 everywhere, B-6's
   band table failed at 1280/1440/1920/2560; with the bands everywhere, B-7 would
   fail. Two rows, one element, opposite verdicts.

   ⚠ THE PACKAGE'S OWN GOVERNING RULE SETTLES IT, and it is one line up in the
   same file — tokens `$selectedDirection`: "Desktop >=1025 is parity; <=1024 is
   AUTHORED." So the measured band owns >=1025 and the authored clamp owns
   <=1024, which is exactly what `$authored` already says in as many words. B-7's
   "every width" is the over-reach, and it over-reaches into the half the package
   declares parity; the row is corrected to the same boundary and the
   contradiction is recorded (GENERATION_DECISIONS F-8) rather than smoothed.

   ⚠ A 65 px line box under a 130 px glyph does NOT clip anything: the band is
   `overflow-x: clip` only, the heading is a single line at every desktop width,
   and the scrim covers the whole band. It is live's own box — 1400 x 65. */
.gmg-display {
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: clamp(2.75rem, 11vw, 8.125rem);
  line-height: 1.05;                    /* AUTHORED, <=1024 */
  color: var(--gmg-ink-display);
  margin: 0;
}
@media (min-width: 1025px) {
  .gmg-display { line-height: 65px; }   /* PARITY — tokens type.display.bands ">=1025": "130 / 65" */
}

/* ⛔ THE SCRIM COVERS THE WHOLE BAND, NOT A FIXED BOX.
   tokens.json a11y.scrim.$breakpointRule: *"the failure mode is a headline
   reflowing to a third line and sliding off the plateau onto bare photograph. At
   ≤767 the clamped heading wraps differently, so the scrim must cover the whole
   band."*

   ✅ OWNER-ACCEPTED AND UNCHANGED — ruling O-2(a), Noah 2026-09-10, "your change
   is fine, ship it". A VISIBLE darkening of the banner photograph at the 2560
   review width, shown at design review and ruled in.
   ⛔ 0.45 alpha reaches only 3.35:1 and 0.35 only 2.45:1 — a lighter scrim is not
   an option that passes, and softening it reopens a closed ruling.
   Arithmetic as derived at Stage C: worst plateau L 0.8957 → 0.1623 under a 0.54
   black scrim → (0.9046 + 0.05) / (0.1623 + 0.05) = 4.58:1, clearing 4.5:1 rather
   than merely the 3:1 large-text threshold.

   ⛔⛔ ROUND-4 — 0.54 → 0.56, AND THE INPUT TO THAT ARITHMETIC IS WHAT CHANGED.
   ⚠ THE SCRIM IS NOT AN AESTHETIC NUMBER. It is the OUTPUT of the line above,
   whose INPUT is "the worst plateau behind the heading". Round 4 changed that
   input twice in one commit, and the two changes compound:
       • the crop correction replaced a dark building with bright sky (§4 above)
       • centring the glyph run moved the ink onto the middle of the frame, which
         at 2560 is the brightest cloud bank in the photograph
   MEASURED on the build instance, `browser-battery` B-7, all 13 widths — and it
   fails at exactly ONE of them, which is the width the owner reviews at:
       1440  ground rgb( 99,104,109)  5.22:1   ok
       1920  ground rgb(106,107,110)  4.93:1   ok
       2560  ground rgb(113,115,115)  **4.43:1**  ⛔ below the 4.5 floor
   ⛔ AND IT IS NOT THE INSTRUMENT BEING OVER-STRICT. B-7 samples the heading's
   BOX, which at 2560 is 2520 wide against a 1377 px glyph run — so the obvious
   suspicion is that it found a bright cell where no glyph is painted.
   `gmg-capture/tools/probe-r4-plateau.mjs` was written to ask exactly that, and
   the answer is no: the brightest cell is cell x1088, INSIDE the run (571..1948),
   at every one of the three widths. Identical verdicts box-wide and run-only.
   ⛔ So the row is right and the build was shipping a real miss.

   THE RE-DERIVATION, same formula, measured input:
       unscrimmed worst plateau, read back through the 0.54 composite:
           rgb(113,115,115) / 0.46  =  rgb(245.7, 250, 250)  →  L 0.9481
           ⚠ Stage C's input was L 0.8957 — this plateau is genuinely brighter.
       at alpha 0.56:  rgb(108.1, 110, 110)  →  L 0.1547
           (0.9249 + 0.05) / (0.1547 + 0.05)  =  **4.75:1**
   ⚠ 0.545 would scrape past 4.5 and 0.547 would restore Stage C's stated 4.58.
   ⛔ NEITHER IS TAKEN: a value chosen to clear a threshold by 0.01 is a number
   fitted to a measurement, which is the habit this package's own round-3 note
   forbids — *"meet the bound, do not re-derive it from the build."* 0.56 restores
   real headroom and is the value asserted.

   ⛔ THIS TOUCHES AN OWNER-RULED VALUE (O-2 (a)) AND IT IS FLAGGED, NOT ABSORBED.
   The ruling's recorded prohibition is ONE-DIRECTIONAL and is stated in terms of
   the OUTCOME: *"0.45 reaches only 3.35:1 and 0.35 only 2.45:1, so nobody gets to
   soften it later without reopening the ruling."* What the owner accepted was a
   visible darkening in exchange for the contrast floor; 0.54 was the number that
   delivered it on the inputs known at the time, and those inputs are now measured
   to be different. ⚠ The visible delta between 0.54 and 0.56 is a 2 % darker
   overlay. It is in the PR's deviations ledger for confirmation, and reverting it
   to 0.54 means accepting a measured **4.43:1** at the 2560 review width — still
   1.48 × WCAG 2.2 AA's 3:1 large-text floor, and still far better than live's own
   2.45:1, but below this package's own stated target. That is the owner's call to
   make with the numbers in front of them, not a builder's to make silently. */
/* =============================================================================
   ⛔ ROUND-4 — THE BANNER CROP, AND WHY `50% 50%` WAS TRUE ON BOTH SIDES AND
   STILL PAINTED TWO DIFFERENT PHOTOGRAPHS.

   The owner's side-by-side: *live at 2560 shows the sky and the top of the sign;
   the build shows the building and the road.* Both elements resolve
   `background-size: cover` and `background-position: 50% 50%`. The property that
   decides the crop is the one Stage C transcribed NEITHER way:

       live   background-attachment: FIXED     (the photo layer; measured)
       build  background-attachment: scroll    (the initial value)

   `fixed` moves the positioning area from the 475 px BAND to the VIEWPORT. The
   image (2000 x 1500) is covered against 1440x900 / 2560x1440, centred there,
   and the band shows the slice of that painting lying between y 100 and y 575 —
   the TOP of the frame. With `scroll` the same `50% 50%` centres the image in a
   475 px band and shows the MIDDLE of the frame. Resolved in image coordinates
   (`gmg-capture/tools/probe-r4-banner.mjs`, 2026-09-11):

     @1440x900    live rows  264..924  (17.6%..61.6%)   build rows  420..1080
     @2560x1440   live rows  266..637  (17.7%..42.4%)   build rows  564..936

   ⚠ AND THE SCALE IS IDENTICAL ON BOTH MECHANISMS — 0.72 at 1440, 1.28 at 2560 —
   because at every desktop width cover is WIDTH-driven for this 4:3 source. So
   the whole difference is a vertical OFFSET, and an explicit offset reproduces
   live's crop EXACTLY rather than approximately.

   ⛔ THE OUTCOME IS REPRODUCED, NOT THE MECHANISM — the same rule §3 follows for
   the sticky header. `fixed` would also be a parallax nobody specified (the
   design package records no motion on this band), it is unreliable on iOS, and
   it makes the crop depend on VIEWPORT HEIGHT, which no row could then assert.
   The offset below is height-invariant and gated by P-2.

   THE ARITHMETIC. Painted height is always 0.75 x W (cover, width-driven), so
   putting live's first visible image row (265 of 1500) at the band's top edge
   needs an offset of 265/2000 x W = 0.1325 W px = 13.25vw. Checked against the
   two review viewports: 265 vs live's 264 @1440 and 266 @2560 — ONE image row,
   which is 0.7 px of screen at 1440 and 1.3 px at 2560.

   ⚠ >=1025 ONLY. Below 1025 the band is 320 px and cover turns HEIGHT-driven
   (0.75 x 375 = 281 < 320), so the formula stops holding — and that half is
   AUTHORED anyway, where `50% 50%` already shows the sign and the sky.
   ============================================================================= */
:root { --gmg-banner-focal-y: 50%; }
@media (min-width: 1025px) {
  :root { --gmg-banner-focal-y: calc(-13.25vw); }
}

.gmg-banner {
  position: relative;
  /* ⛔ `clip`, NEVER `hidden` — see §5. */
  overflow-x: clip;
  background-size: cover;
  background-position: 50% var(--gmg-banner-focal-y);
  display: flex;
  align-items: center;
  justify-content: center;
}
.gmg-banner::before {
  content: "";
  position: absolute;
  inset: 0;                       /* the WHOLE band */
  background: var(--gmg-scrim);
  pointer-events: none;
}
.gmg-banner > * { position: relative; z-index: 1; }


/* =============================================================================
   §5. OVERFLOW — ⛔ `clip`, NEVER `hidden`.

   `overflow-x: hidden` creates a SCROLL CONTAINER, and a scroll container breaks
   `position: sticky` on every descendant — which would silently undo §3 and
   leave B-8 asserting a header that no longer sticks. `clip` clips without
   creating one.

   Applied to the banner and to every reveal-bearing wrapper, because a reveal
   animating from `translate: 0 24px` is exactly what pushes ink past the viewport
   edge mid-animation.

   ⚠ AND B-15 IS WHY THIS IS NOT ENOUGH ON ITS OWN: the 130 px display heading has
   a NORMAL-SIZED BOX and OVERFLOWING INK. A rect-only overflow probe is
   structurally blind to this site's actual defect, so B-15 carries an ink tier.
   ============================================================================= */
.gmg-reveal-wrap,
.gmg-banner { overflow-x: clip; }


/* =============================================================================
   §6. NAV CURRENT-ITEM — ⛔ KEYED OFF THE ATTRIBUTE, NEVER OFF A CLASS.

   CAT-014: live has `.active` and NO attribute. `a11y.js` sets
   `aria-current="page"` and this styles off it. A class the JS sets separately is
   two sources of truth for one state, and they drift.

   Hover is a COLOUR CHANGE ONLY — no underline, no box change, no background
   (INTERACTIONS I-01/I-02).
   ============================================================================= */
.gmg-nav__link {
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 500;
  font-size: 18px;
  line-height: 45px;
  color: var(--gmg-ink);
  text-decoration: none;
  transition: color var(--gmg-duration-fast) var(--gmg-ease-standard);
}
.gmg-nav__link:hover,
.gmg-nav__link[aria-current="page"] { color: var(--gmg-brand-blue); }

/* FOCUS RING. A real ring exists on live and is worth keeping — but live's is a
   4 px WHITE halo on a WHITE header, i.e. invisible. Authored: 3 px brand-deep
   with a 2 px white offset, ≥3:1 on every ground this site has (white header
   17.38:1, blue band 5.6:1, pink CTA 10.52:1, panel grey 14.2:1).
   ⛔ B-11 asserts `outlineStyle`, never `outlineWidth`. */
:where(a, button, input, textarea, select, [tabindex]):focus-visible {
  outline: 3px solid var(--gmg-brand-deep);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--gmg-white);
}


/* =============================================================================
   §7. THE DRAWER — ⛔ THE ASSERTION IS THE OUTCOME.

   B-18 / I-21…I-23: `scrollY` UNCHANGED after `window.scrollTo(0, 600)` with the
   drawer open.

   ⛔ NEVER `body { overflow: hidden }`. The live drawer computes
   `overflow: clip visible`, which READS like a lock and is not one — that is the
   measured defect, and copying the mechanism reproduces it.

   ⛔ ROUND-2 CORRECTION — THE LOCK IS ON THE ROOT, AND `position: fixed` ON
   <body> FAILED THE ASSERTION IT WAS CHOSEN FOR.

   Round 1 locked with `position: fixed` on <body> plus a saved `top` offset.
   That DOES stop the page moving, and it also SETS `window.scrollY` TO 0 —
   because a fixed body leaves the document with nothing to scroll. B-18 does not
   read a CSS property, it DRIVES the scroll and reads where the page ended up,
   and what it measured was *"THE PAGE SCROLLED BEHIND THE OPEN DRAWER: scrollY
   303 → window.scrollTo(0,600) → 0"* plus *"the scroll position was not
   restored: 303 before opening, 307 after closing"* — a 4 px drift from the
   round-trip through `top` and `window.scrollTo`.

   ⚠ §7's original warning is still correct and is NOT what this changes: LIVE's
   defect is `overflow: clip visible` on <body> while <html> is the scroller, so
   the declaration reads like a lock and holds nothing. `overflow: hidden` ON THE
   ROOT is a different thing — it removes the viewport's scrollability outright,
   so `scrollTo` is a no-op, `scrollY` never moves, and there is no offset to save
   and no restore to get wrong. The whole restore path is DELETED rather than
   fixed, which is the stronger outcome.

   ⚠ KNOWN BOUND, recorded rather than discovered later: iOS Safari has
   historically ignored root `overflow: hidden` for the visual viewport. That is
   the case `position: fixed` was chosen for and it is UNMEASURED on this
   engagement — no iOS device is in the capture set. The gate asserts the
   outcome this build can measure; the iOS behaviour is a go-live check.

   `scrollbar-gutter: stable` keeps the scrollbar's space reserved so nothing
   reflows horizontally when the lock engages. */
html.gmg-drawer-open {
  overflow: hidden;            /* stops the USER gesture reaching the viewport */
  scrollbar-gutter: stable;    /* keeps the gutter, so nothing reflows on open */
}
/* ⛔ AND THE BODY LEAVES FLOW, BECAUSE `overflow` ALONE IS NOT A LOCK.
   MEASURED, six mechanisms, on this build at 375 with the page at scrollY 300
   and a scripted `window.scrollTo(0, 600)`:

     html overflow:hidden                 300 -> 600   NOT LOCKED
     html overflow:clip                   300 -> 600   NOT LOCKED
     html + body overflow:clip            300 -> 600   NOT LOCKED
     html overflow-y:clip                 300 -> 600   NOT LOCKED
     html/body height:100% + clip         300 ->   0   locked (document collapses)
     body position:fixed + top offset     300 ->   0   locked (document collapses)

   ⚠ THE GENERAL FACT, worth carrying off this site: `overflow: hidden` and
   `overflow: clip` stop a WHEEL and a TOUCH DRAG and DO NOT stop a SCRIPT. Only
   collapsing the document's scrollable height stops both — and collapsing it
   necessarily takes `scrollY` to 0, which is why the offset has to be preserved
   in `top` and restored on close. There is no mechanism that both pins the page
   and leaves `scrollY` where it was.

   So `overflow: hidden` above is the user-gesture half and this is the rest. */
body.gmg-drawer-open {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
}

/* ⛔ A SEPARATE SCRIM ELEMENT, not a ::before on the panel (CAT-012's MCC
   ruling) — a pseudo-element cannot be a click target for outside-click close. */
.gmg-drawer__scrim {
  position: fixed; inset: 0; z-index: 9998;
  background: rgba(3, 27, 51, 0.5);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--gmg-duration-base) var(--gmg-ease-standard), visibility var(--gmg-duration-base);
}
.gmg-drawer__panel {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 9999;
  width: min(100%, 400px);           /* live is 350 on a 375 viewport — a 25px live strip beside it */
  background: var(--gmg-white);
  translate: 100% 0;                 /* ⛔ the PROPERTY, not the transform shorthand — see §9 */
  transition: translate var(--gmg-duration-base) var(--gmg-ease-standard);
  display: flex; flex-direction: column;
  overflow-y: auto;
}
body.gmg-drawer-open .gmg-drawer__scrim { opacity: 1; visibility: visible; }
body.gmg-drawer-open .gmg-drawer__panel { translate: 0 0; }

/* ⛔ THE HAMBURGER'S BARS — AND UNTIL ROUND 2 THE BUTTON WAS EMPTY.
   The composer writes `<span class="gmg-hamburger__bars" aria-hidden="true">`
   and nothing styled it, so the drawer's only opener rendered as a blank 48×48
   box. ⚠ EVERY OTHER ROW PASSED ON IT: B-18 opened the drawer from it (it is a
   real <button> with a real click target), B-16 measured it at 48×48, B-14 found
   no dead link. P-1 — "every rendered gmg-* class resolves to a non-empty rule" —
   is the only row that could see an invisible control, and this is exactly the
   92-of-134 failure it was written for, at a population of one. */
.gmg-hamburger__bars,
.gmg-hamburger__bars::before,
.gmg-hamburger__bars::after {
  display: block;
  width: 24px;
  height: 2px;
  background: var(--gmg-ink);
  border-radius: 1px;
}
.gmg-hamburger__bars { position: relative; }
.gmg-hamburger__bars::before,
.gmg-hamburger__bars::after { content: ""; position: absolute; left: 0; }
.gmg-hamburger__bars::before { top: -7px; }
.gmg-hamburger__bars::after  { top: 7px; }

/* 44px minimum on every drawer row and control. Live measures 65 on rows
   (passes) but 45×38 on the close control and 51×39 on the hamburger — both fail.
   AUTHORED to 48×48, achieved with padding rather than a fixed box. */
/* ⛔ THE DRAWER LINK CARRIES THE navLink ROLE, NOT A LOOK OF ITS OWN.
   tokens.type.navLink.$noStep: "18/45 at every one of the 13 widths, INCLUDING
   INSIDE THE DRAWER." Below 1025 the inline nav is display:none, so the drawer
   is the ONLY place that assertion can be made — and B-6 makes it there. The
   56 px row height is the authored tap target (_global-chrome.md: live's rows
   are 65 and pass; the authored figure is 56), and it sits AROUND the 45 px line
   box rather than replacing it. */
.gmg-drawer__link {
  min-height: 56px;
  display: flex;
  align-items: center;
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 500;
  font-size: 18px;
  line-height: 45px;
  color: var(--gmg-ink);
  text-decoration: none;
  padding-inline: var(--gmg-gap-md);
  transition: color var(--gmg-duration-fast) var(--gmg-ease-standard);
}
.gmg-drawer__link:hover,
.gmg-drawer__link[aria-current="page"] { color: var(--gmg-brand-blue); }
.gmg-drawer__close,
.gmg-hamburger { min-width: 48px; min-height: 48px; display: inline-flex; align-items: center; justify-content: center; }


/* =============================================================================
   §8. TAP TARGETS — B-16.

   13 of 22 visible controls measure under 44 px at 375 on live, every one of them
   in the chrome or the footer. TWO of the thirteen are closed by REMOVAL rather
   than by resizing (owner ruling O-4 drops the two 43×43 share glyphs), leaving
   eleven for the authored chrome to fix.

   ⚠ B-16 PRINTS THE POPULATION IT EVALUATED. The build's population is smaller
   than live's 22, and that is expected — the "13 of 22" in tokens.json is a LIVE
   reading and is not edited. A moved number is not a run-invalid signal here.
   ============================================================================= */
@media (max-width: 1024px) {
  .gmg-footer a,
  .gmg-copyright a,
  .gmg-nav__link {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
}


/* =============================================================================
   §9. MOTION — and the ONE property rule that outranks everything.

   ⛔ ANIMATE THE INDIVIDUAL `translate` PROPERTY, NEVER THE `transform`
   SHORTHAND (COMPOSITION_NOTES.md §12). An end state written on the shorthand
   PERMANENTLY OUTRANKS any later hover transform — the element is then stuck and
   nothing in a computed-style audit says so.

   29 reveal-bearing elements per route. Duration 600ms, DOWN from live's 2000ms
   `animated-slow`: a 2-second fade on 29 elements per route is a
   perceived-performance defect, not a design (recorded as a deviation, GMG-D-15).
   ============================================================================= */
/* ⛔ ROUND-2 CORRECTION — THESE ARE KEYFRAME ANIMATIONS, NOT TRANSITIONS, AND
   THE NAMES ARE PART OF THE CONTRACT.
   `tokens.json motion.entrance.keyframes` is `["fadeInUp", "fadeInDown"]` and it
   is a MEASURED field: the capture inventoried 29 elements per route, each
   NAMING one of those two keyframes. B-9 reads `getAnimations()` per frame and
   looks for them by name.
   Round 1 implemented the reveal as a CSS TRANSITION. MEASURED: *"ZERO reveals
   seen across 177 frames. ⛔ 0 is the failure. (70 animations of any kind were
   seen: transition:opacity, transition:translate, …)"* — the motion was real,
   visible and completely invisible to the row, because a transition has no name.
   ⚠ This is the failure class B-9's own header warns about from the other side:
   an entire motion layer once shipped unimplemented through four repair rounds
   because nothing could see it. Here the layer existed and the NAME did not. */
@keyframes fadeInUp {
  from { opacity: 0; translate: 0 24px; }    /* ⛔ the individual property — §12 */
  to   { opacity: 1; translate: 0 0; }
}
@keyframes fadeInDown {
  from { opacity: 0; translate: 0 -24px; }
  to   { opacity: 1; translate: 0 0; }
}
.gmg-reveal { opacity: 0; }
.gmg-reveal.is-revealed {
  /* `both` holds the end state after the animation finishes, so nothing snaps
     back to opacity 0 — the same guarantee the transition gave. */
  animation: fadeInUp var(--gmg-duration-reveal) var(--gmg-ease-standard) both;
}
.gmg-reveal--down.is-revealed { animation-name: fadeInDown; }


/* =============================================================================
   §10. ⛔ REDUCED MOTION — OPEN O-10. AUTHORED HERE BECAUSE THE PLATFORM SHIPS NONE.

   COMPOSITION_NOTES.md §13 is explicit that the floor is PLATFORM-OWNED and that
   *"the golden image does not currently ship the reduced-motion baseline, and the
   skeleton has no defined static-asset lane to ship it from"* (spike follow-ups
   F4/F5). Its own words: *"until it lands, a motion-bearing site has NO FLOOR AT
   ALL — treat that as a release blocker for the first motion-heavy generation."*

   CONFIRMED ON THIS INSTANCE, not assumed (STAGE_D_PREP_LOG §8): 0 files
   containing `prefers-reduced-motion` in the platform-owned mu-plugins lane, 0
   golden-image baseline assets of any name. The vendor hits (Bricks 11,
   Bricksfusion 48) are per-component handling and ⛔ DO NOT CLOSE O-10.

   ⛔ THIS DOES NOT CLOSE O-10 EITHER. The item stays open and escalated to
   platform-engineer; the site is carrying the platform's floor locally and that
   is temporary. The composer's obligation is two lines: do not defeat the
   baseline, and specify the exception where generic suppression is wrong.

   ⛔ AND IT RESETS `translate`, `scale` AND `rotate` — NOT ONLY `transform`
   (§12's rule, restated where the reset is written). A block that resets only
   `transform` leaves an element animated on an individual property exactly where
   the animation left it.
   ============================================================================= */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
    /* ⛔ all four, not just transform */
    transform: none !important;
    translate: none !important;
    scale: none !important;
    rotate: none !important;
  }
  html { scroll-behavior: auto !important; }

  /* ⛔ THE NAMED EXCEPTION. A generic suppression leaves a reveal at opacity 0 and
     DELETES CONTENT. Reveals must resolve to their END state, not be frozen at
     their start. */
  .gmg-reveal { opacity: 1 !important; translate: none !important; }

  /* ⛔ AND THE COUNTERS RESOLVE TO THEIR END VALUES — `10+` and `12K`, NOT `0`.
     B-10's RED PROOF IS EXACTLY THIS: apply a generic `animation: none` and
     confirm the counter assertion fails. The end value is rendered server-side as
     the element's text content and a11y.js only animates UP TO it, so suppressing
     the animation leaves the correct number on the page rather than a zero.
     This rule is the belt: it makes the authored end state visible even if the
     script never runs at all. */
  .gmg-counter { visibility: visible !important; }
  .gmg-counter__value::after { content: none !important; }
}


/* =============================================================================
   §11. CARDS — ⛔ A NEGATIVE RULE. O-8 / B-13.

   Owner ruling O-8: the /services/ cards have NO LINK AFFORDANCE. No `<a>`, no
   rendered "Read More", no pointer cursor, no lift.

   B-13 asserts the card reports ZERO changed computed properties on hover across
   a 26-property set plus ::before and ::after. ⛔ It is a NEGATIVE row with an
   identity assertion, and it exists so O-8 cannot be quietly undone by a builder
   who adds a lift "because cards lift".

   ⚠ This block deliberately declares NOTHING on :hover. Do not add a transition
   here "for consistency" — a transition with no changing property still reports
   as a changed property on some engines, and B-13 would go red on a correct site.
   ============================================================================= */
.gmg-card {
  background: var(--gmg-white);
  border: 1px solid var(--gmg-hairline);
  border-radius: var(--gmg-radius-sm);
  cursor: auto;
}


/* =============================================================================
   §12. FORM — the /contact-us/ panel.
   ============================================================================= */
.gmg-formrow { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gmg-gap-md); }
/* ⛔ Both 2-up field rows go 1-up at ≤767 (LAYOUT_TRUTH §5). */
@media (max-width: 767px) { .gmg-formrow { grid-template-columns: 1fr; } }

.gmg-field input,
.gmg-field textarea {
  width: 100%;
  background: var(--gmg-field-grey);
  border: 1px solid #767D8C;          /* --gmg-field-border */
  border-radius: var(--gmg-radius-xs);
  min-height: 44px;
}
.gmg-req { color: #B3001B; }          /* --gmg-error */

/* ⛔ THE SUBMIT IS LIVE'S #3970FF WITH A WHITE LABEL — owner ruling O-2(b),
   REVERTING the Stage C accessibility fix.
   ⛔ AND IT IS NOT RECORDED AS A PASS. 4.25:1 clears the 3:1 non-text and
   large-text thresholds and DOES NOT clear 4.5:1; the buttonLabel role is Albert
   Sans 700/18 against WCAG's ≥18.66px bold large-text floor — 0.66px short of the
   size at which the 3:1 threshold formally applies. Read strictly, the pair is
   NORMAL-SIZED TEXT AT 4.25:1 AND FAILS AA. It is carried ON THE RULING, not on a
   threshold it clears (GENERATION_BRIEF §5.3). /styleguide renders this in words
   beside the swatch. */
.gmg-btn--submit {
  background: var(--gmg-submit-blue);
  color: var(--gmg-white);
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: 18px;
  line-height: 20px;
  border: 0;
  border-radius: var(--gmg-radius-xs);
  min-height: 44px;
  padding-inline: var(--gmg-gap-lg);
  cursor: pointer;
}


/* #############################################################################
   ###                                                                       ###
   ###   §13 — §18: THE COMPOSITION LAYER (ROUND 2).                         ###
   ###                                                                       ###
   ###   Round 1 shipped the structure, the copy, the images, the classes    ###
   ###   and every §1–§12 behaviour above, and NONE of the geometry. The     ###
   ###   measured consequence (PR #5, and re-measured at the head of this    ###
   ###   round): `/` was 10,673 px at 1440 against a 5,183 px target —       ###
   ###   2.06x — because every band was transparent, every `.brxe-div`       ###
   ###   was `display: block`, and every type role rendered at the theme's    ###
   ###   15/25.5 default.                                                    ###
   ###                                                                       ###
   ###   ⛔ WHY THIS IS THE AUTHORED LANE AND NOT A BRICKS GLOBAL CLASS.      ###
   ###   The round-2 brief asked for Bricks-native styling "so that the      ###
   ###   exported design-sync/ carries the geometry". MEASURED on this       ###
   ###   instance before choosing (2026-09-11):                              ###
   ###     1. The composers attach classes through `_cssClasses`, which is   ###
   ###        a RAW CLASS STRING. Bricks emits global-class CSS only for     ###
   ###        classes bound through `_cssGlobalClasses` (an array of class   ###
   ###        IDs). Nothing in either composer binds one, so seeding         ###
   ###        `bricks_global_classes` today paints NOTHING — it would be a   ###
   ###        silent no-op wearing the name of the fix.                      ###
   ###     2. `bricks_global_settings` is EMPTY on this build                ###
   ###        (`cssLoading` NULL, `bricks_global_classes` does not exist),   ###
   ###        so the whole global-class CSS-emission path is unexercised     ###
   ###        here and would have to be proven from scratch.                 ###
   ###     3. The reason the brief gave does not discriminate: THIS FILE     ###
   ###        SHIPS IN THE REPO. It is a mu-plugin asset, not database       ###
   ###        state, so it does not depend on the design-sync round trip at  ###
   ###        all — which is a STRONGER guarantee than the export, not a     ###
   ###        weaker one. (The 7 options design-sync actually carries are    ###
   ###        listed in SANDBOX_PROVISION_LOG.)                              ###
   ###   ⚠ And the repo had already ruled this way once, with a measurement: ###
   ###   §2 above records that Bricks' theme-style `general.containerWidth`  ###
   ###   STORES AND IS NEVER CONSUMED (HBA measured every container at the   ###
   ###   1100 px default while the option sat there). B-4/B-5/B-6/B-7        ###
   ###   measure the RENDERED result and do not care which lane produced it. ###
   ###   ⛔ Recorded as a deviation from the round-2 instruction rather than  ###
   ###   silently taken — GENERATION_DECISIONS.md D-6.                       ###
   ###                                                                       ###
   ###   ⛔ EVERY NUMBER BELOW IS A TOKEN OR A CITED MEASUREMENT. Where a     ###
   ###   literal appears it is a parity constraint and the measurement is on ###
   ###   the line, exactly as §2 does for 877 / 1140 / 1900.                 ###
   ############################################################################# */


/* =============================================================================
   §13. THE SECTION-RHYTHM LADDER, AND THE BAND FIELDS.

   ⛔ THERE IS NO LADDER TO INHERIT — THE REBUILD AUTHORS ONE.
   tokens.json layout.sectionRhythm: *"Of the 33 top-level rows measured across
   the four routes at 1440, TWENTY-EIGHT carry padding 0/0. Vertical rhythm on
   the live site comes from child margins and fixed section heights."* So a build
   that copies the measured padding reproduces a page with no rhythm at all —
   which is precisely what round 1 shipped.

   The four authored registers, verbatim from
   tokens.json layout.sectionRhythm.authored. ⚠ These ARE responsive (unlike
   HBA's, which were width-invariant). Declared mobile-first so the cascade runs
   the same direction as the token table reads.

   ⛔ AND THE BAND COLOUR IS ON THE BAND, NOT ON THE MEASURED CONTAINER.
   LAYOUT_TRUTH §2's standing caution: Source A reports `rgba(0, 0, 0, 0)` on
   most top-level containers *"and the colour you see is painted by a child or an
   overlay, not by the container the array measures. ⛔ A builder who sets the
   measured container background reproduces nothing."* rhythm-diff honours the
   same rule from the other side — it prints `bg` as `[not asserted]`.
   ============================================================================= */

:root {
  /* mobile — the <=767 column of layout.sectionRhythm.authored */
  --gmg-rhythm-band:      56px;
  --gmg-rhythm-tight:     40px;
  --gmg-rhythm-pagetitle: 64px;
  --gmg-rhythm-bar:       20px;   /* PARITY at every width — the one measured non-zero register */
}
@media (min-width: 768px) {
  :root { --gmg-rhythm-band: 72px; --gmg-rhythm-tight: 48px; --gmg-rhythm-pagetitle: 88px; }
}
@media (min-width: 1025px) {
  :root { --gmg-rhythm-band: 96px; --gmg-rhythm-tight: 64px; --gmg-rhythm-pagetitle: 170px; }
}
/* ⛔ 170, NOT 120 — AND tokens.json's OWN JUSTIFICATION IS THE ARITHMETIC THAT
   DISPROVES ITS OWN NUMBER. The desktop `pageTitle` register shipped as
   "120px / 120px" with the note *"Reproduces the measured 405px title-band
   height at 1440 with a 48/65 h1 and no other content."*
       120 + 65 + 120 = 305.  The measured band is 405.  It is 100 px short.
   MEASURED on the round-1 build, all three inner routes, 2026-09-11: the band
   came out at exactly 305 against a 405 target, −24.7 %, on /services/,
   /about-us/ AND /contact-us/ — three identical misses, which is the signature
   of a wrong constant rather than a wrong composition.
       405 − 65 = 340 = 2 × 170.
   The register's stated INTENT is the authority and its stated NUMBER is the
   typo, so the number moves. ⚠ Only the DESKTOP rung moves: the ≤767 and
   768–1024 rungs are authored *targets* expressed as upper bounds ("≤ 220" with
   a 30/38 h1), and 64/64 and 88/88 both land under theirs. tokens.json is
   corrected in the same commit and the correction is GENERATION_DECISIONS F-6. */

/* ⛔ ORDER IS THE MECHANISM. Every modifier below is one class, exactly like
   `.gmg-band`, so specificity ties and SOURCE ORDER decides. The modifiers are
   written after the base deliberately; moving `.gmg-band` below them silently
   reverts every band to the default register. */
.gmg-band        { padding-block: var(--gmg-rhythm-band); }
.gmg-band--tight { padding-block: var(--gmg-rhythm-tight); }
.gmg-pagetitle   { padding-block: var(--gmg-rhythm-pagetitle); }
.gmg-band--flush { padding-block: 0; }   /* full-bleed media that pads internally */

/* The fields. `.brxe-section` is already flex/column/center at 100% width. */
.gmg-band--white { background-color: var(--gmg-white); }
.gmg-band--blush { background-color: var(--gmg-band-blush); }
.gmg-band--mint  { background-color: var(--gmg-band-mint); }
.gmg-band--blue,
.gmg-pagetitle   { background-color: var(--gmg-brand-blue); }

/* ⛔ INK TRAVELS AS A VARIABLE, NOT AS A DESCENDANT SELECTOR.
   The white "Our Benefit" panel sits INSIDE a blue band and its own titles are
   `--gmg-ink`. A descendant rule (`.gmg-band--blue h2 { color: white }`) paints
   that panel's heading white on white — invisible ink, HTTP 200, and no
   computed-style walk would call it a defect. The variable re-scopes at the
   panel and the cascade does the rest. */
.gmg-band        { --gmg-ink-here: var(--gmg-ink);  --gmg-ink-here-body: var(--gmg-ink-body); }
.gmg-band--blue  { --gmg-ink-here: var(--gmg-white); --gmg-ink-here-body: var(--gmg-white); }
.gmg-pagetitle   { --gmg-ink-here: var(--gmg-ink-on-blue); --gmg-ink-here-body: var(--gmg-ink-on-blue); }
/* ⛔ EVERY WHITE OR TINTED SURFACE RESETS THE INK, AND THE LIST IS EXHAUSTIVE
   BECAUSE A MISS IS INVISIBLE. B-20 caught one: the `12+ Years Of Experience`
   badge card sits inside the blue About band, so it inherited
   `--gmg-ink-here: white` and painted a white title on its own white face —
   MEASURED 1.00:1, "ink #FFFFFF on decoded ground #FFFFFF". ⚠ The text was in
   the DOM, the computed colour was a real colour, and every content and
   structural check passed. This is §5c rule 1's exact shape — a colour claim
   needs a RASTER — and B-20 decoding the composited pixel is what saw it. */
.gmg-panel,
.gmg-card,
.gmg-infocard,
.gmg-stat,
.gmg-badgecard,
.gmg-emergency,
.gmg-faq__item,
.gmg-eyebrow     { --gmg-ink-here: var(--gmg-ink);  --gmg-ink-here-body: var(--gmg-ink-body); }
/* …except the outlined /services/ cards, which have NO fill and sit on the blue
   band: their ink is white and §16 says so where the card is defined. */

/* The copyright bar — 20/20, 70 px, centred, the whole line one link. 17.58:1. */
.gmg-copyright {
  background-color: var(--gmg-bar-ink);
  padding-block: var(--gmg-rhythm-bar);
  display: flex;
  flex-direction: column;
  align-items: center;
}
.gmg-copyright__inner {
  flex-direction: row; align-items: center; justify-content: center;   /* §6a-bis */
  width: min(100% - 30px, 1314px);
  max-width: none;
  margin-inline: auto;
  text-align: center;
}
.gmg-copyright__link {
  font-family: Rubik, system-ui, sans-serif;
  font-weight: 400;
  font-size: 16px;
  line-height: 30px;
  color: var(--gmg-white);
  text-decoration: none;
}
.gmg-copyright__link:hover { text-decoration: underline; }


/* =============================================================================
   §14. THE TYPE RAMP — ⛔ THE BANDS ARE NOT THE SAME FOR EVERY ROLE.

   BRICKS_ADAPTATION §1b, stated as a stop-class: *"`pageTitle` and `body` step
   only at 1025, `panelTitle` steps only at 768, `sectionTitle` steps at both,
   and `cardTitle` / `navLink` / `display` never step. A blanket 'scale
   everything at both breakpoints' is wrong for SIX OF THE TWELVE ROLES."*

   B-6 asserts each role's own three-band table AND four specific no-step
   claims, so a blanket ramp fails the row even where it lands on the right
   number at the sampled widths.

   Every size/weight/line-height below is tokens.json `type.<role>.bands`.
   ============================================================================= */

/* display — §4 above owns the clamp. Only the colour and family are here. */

/* heroTitle — the home hero h2 AND the 404 h1. ONE role, both ladders. */
.gmg-hero-title {
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: 40px; line-height: 50px;      /* <=1024 */
  color: var(--gmg-ink-here, var(--gmg-ink));
  margin: 0;
}
@media (min-width: 1025px) { .gmg-hero-title { font-size: 85px; line-height: 100px; } }

/* pageTitle — ⛔ IT DOES NOT STEP AT 768. 36/50 through 1024, 48/65 from 1025. */
.gmg-page-title {
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: 36px; line-height: 50px;
  color: var(--gmg-ink-on-blue);
  margin: 0;
  text-align: center;
}
@media (min-width: 1025px) { .gmg-page-title { font-size: 48px; line-height: 65px; } }
/* /styleguide renders the role on white — a build artifact, not a parity surface. */
.gmg-page-title--onlight { color: var(--gmg-ink); text-align: left; }

/* sectionTitle — the ONE role that steps at BOTH boundaries. */
.gmg-section-title {
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: 28px; line-height: 36px;
  color: var(--gmg-ink-here, var(--gmg-ink));
  margin: 0;
}
@media (min-width: 768px)  { .gmg-section-title { font-size: 36px; line-height: 47px; } }
@media (min-width: 1025px) { .gmg-section-title { font-size: 48px; line-height: 65px; } }

/* panelTitle — ⛔ IT STEPS AT 768 AND THEN STOPS. 20/40 below, 32/40 above.
   Line-height is 40 in all three bands, which is why it reads loose on a phone;
   that is measured, not a mistake. */
.gmg-panel-title {
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: 20px; line-height: 40px;
  color: var(--gmg-ink-here, var(--gmg-ink));
  margin: 0;
}
@media (min-width: 768px) { .gmg-panel-title { font-size: 32px; line-height: 40px; } }

/* cardTitle — 24/34 at EVERY width. Four measured inks normalised to one. */
.gmg-card-title {
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: 24px; line-height: 34px;
  color: var(--gmg-ink-here, var(--gmg-ink));
  margin: 0;
}

/* body — steps only at 1025. */
.gmg-body {
  font-family: Rubik, system-ui, sans-serif;
  font-weight: 400;
  font-size: 16px; line-height: 28px;
  color: var(--gmg-ink-here-body, var(--gmg-ink-body));
  margin: 0;
}
@media (min-width: 1025px) { .gmg-body { font-size: 18px; line-height: 30px; } }
/* `.gmg-on-blue` is the composer's marker for copy that sits on a blue field. It
   is redundant with the §13 variable and is kept because the composer emits it
   and a class that resolves to nothing reads as an unstyled element to P-1. */
.gmg-on-blue { color: var(--gmg-white); }

/* eyebrow — the white pill with a dot glyph. 18/24/500 at every width. */
.gmg-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--gmg-gap-sm);
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 500;
  font-size: 18px; line-height: 24px;
  color: var(--gmg-ink-body);
  background: var(--gmg-white);
  border-radius: var(--gmg-radius-pill);
  padding: 11px var(--gmg-gap-md);     /* 24 + 11 + 11 = 46 — the measured pill box is 221 x 46 */
  align-self: flex-start;
  width: fit-content;
}
.gmg-eyebrow::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: var(--gmg-radius-circle, 50%);
  background: var(--gmg-brand-blue);
  flex: 0 0 auto;
}

/* textLink — the ONLY text-link role on the site. Rubik 600, 16/30.
   ⛔ 44 px LINE BOX. Live's is 82 x 30 — a tap-target failure at every mobile
   width (tokens type.textLink.$note, GMG-D-12 F-2). */
.gmg-textlink {
  font-family: Rubik, system-ui, sans-serif;
  font-weight: 600;
  font-size: 16px; line-height: 30px;
  color: var(--gmg-ink-here-body, var(--gmg-ink-body));
  text-decoration: underline;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  width: fit-content;
}

/* buttonLabel — 18/20/700 at every width, both button skins. */
.gmg-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: 18px; line-height: 20px;
  text-decoration: none;
  min-height: 44px;
  padding-inline: var(--gmg-gap-md);
  width: fit-content;
  border-radius: var(--gmg-radius-xs);
  transition: background-color var(--gmg-duration-fast) var(--gmg-ease-standard),
              color var(--gmg-duration-fast) var(--gmg-ease-standard);
}
/* The header CTA: white on brand blue, radius xs, rendered 187 x 46. Hover fill
   -> brandDeep (I-03). */
.gmg-btn--primary {
  background: var(--gmg-brand-blue);
  color: var(--gmg-white);
  min-height: 46px;
}
.gmg-btn--primary:hover { background: var(--gmg-brand-deep); }
/* The in-page CTA: brandDeep label on the pink pill, PILL radius, 10.52:1.
   Hover fill -> brandDeep with a white label, 17.38:1 (measured, I-04). */
.gmg-btn--cta {
  background: var(--gmg-cta-pink);
  color: var(--gmg-brand-deep);
  border-radius: var(--gmg-radius-pill);
  min-height: 50px;                      /* measured 271 x 50 / 214 x 50 */
  padding-inline: var(--gmg-gap-lg);
}
.gmg-btn--cta:hover { background: var(--gmg-brand-deep); color: var(--gmg-white); }


/* =============================================================================
   §15. CHROME LAYOUT — header row, nav, drawer trigger, footer columns.
   ============================================================================= */

/* ⛔ `.brxe-div` IS `display: block`. Every layout div in both composers is a
   `.brxe-div`, so a layout rule that forgets `display` lays nothing out — which
   is most of what round 1's 2.06x actually was. Stated once, here, because it
   applies to every rule in §15 - §17. */

.gmg-header__shell { justify-content: space-between; gap: var(--gmg-gap-md); }
/* ⛔ ROUND-4 — THE ROW IS 80 AND ITS CONTENTS ARE CENTRED IN IT.
   `min-height` rather than `height`, so a taller control can still grow the row
   rather than overflow it; `align-items: center` is what puts the 59 px logo at
   y 11 and the 46 px CTA at y 17, which is live's arithmetic exactly. */
.gmg-header__shell { min-height: var(--gmg-header-row-h); align-items: center; }

/* Logo: rendered 209 x 59 at 1440, natural 1027 x 289 (ratio 3.553), so a 209 px
   width IS the 59 px height. ⛔ Width, never height — the intrinsic ratio does
   the rest and no derivative is shipped (R-12). */
.gmg-header__logo { flex: 0 0 auto; display: block; width: 209px; }
.gmg-header__logo img, img.gmg-header__logo { display: block; width: 100%; height: auto; }

/* ⛔ ROUND-4 — `stretch`, NOT `center`, ON BOTH AXES OF THIS ROW.
   `_global-chrome.md`'s nav table has always read "80 × 80 at x 511", and the
   80 in the SECOND position is the height: live's nav box is y 0, h 80 — it
   fills the resting header, and each link is a full-height 80 px target. The
   build shipped a 45 px nav floated at y 7, which is the same defect as the
   logo's and it is the same cause (the section's column default, §3).
   ⚠ It is also eleven of B-16's sub-44 px controls closing by construction
   rather than by a min-height nobody can see in the composition. */
.gmg-nav { display: flex; align-self: stretch; align-items: stretch; gap: 0; }
/* Home measures 80 x 80 with a ~48 px label at 18/500 — 16 px of padding a side
   (_global-chrome.md nav table: 80 / 100 / 107 at x 511, nav box w 419). */
.gmg-nav__link { padding-inline: 16px; display: flex; align-items: center; }

.gmg-header__cta { flex: 0 0 auto; }

/* The nav flips at 1025, NOT 1024 — tokens layout.navComponent, the kit's
   viewport_lg. A 1024 px iPad in landscape gets the hamburger (M-11, by design). */
@media (min-width: 1025px) { .gmg-hamburger { display: none; } }
@media (max-width: 1024px) { .gmg-nav { display: none; } }
/* ⛔ Live hides the header CTA below 768 so a phone gets NO call to action at
   all (M-3). The rebuild keeps the header CTA from 768 up and puts it in the
   DRAWER below that — present at every width, which is the ruling. */
@media (max-width: 767px) { .gmg-header__cta { display: none; } }

/* ---- Footer ---------------------------------------------------------------- */
/* ⛔ THE FOOTER TAKES `band` + `xl`, AND THE MEASUREMENT IS THE DERIVATION.
   The footer block is 701 px at 1440 on all four routes and its tallest column
   is the map, rendered 318 x 411 (_global-chrome.md). 701 − 411 = 290 = 2 × 145,
   and 145 = 96 (`band`) + 50 (`xl`) − 1. ⚠ This is NOT a fifth rhythm register:
   it is the `band` register plus one step of the gap scale, which is what a
   four-column block with a 411 px media column needs to reach its measured
   height without a hard-coded one. Round 1 ran `band` alone and came out at 607
   on every route — one number, four routes, which is what a missing constant
   looks like. */
.gmg-footer {
  background-color: var(--gmg-brand-blue);
  padding-block: calc(var(--gmg-rhythm-band) + var(--gmg-gap-xl));
}
.gmg-footer { --gmg-ink-here: var(--gmg-white); --gmg-ink-here-body: var(--gmg-white); }

.gmg-footer__cols {
  flex-direction: row; align-items: flex-start;    /* §6a-bis: all three, always */
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gmg-gap-sm);
}
.gmg-footer__col { display: flex; flex-direction: column; gap: var(--gmg-gap-md); }

/* The brand mark is rendered 318 x 212 at 1440 in a ~321 px column — i.e. it
   fills its column. Width, never height. */
.gmg-footer__mark { display: block; width: 100%; max-width: 318px; }
.gmg-footer__mark img, img.gmg-footer__mark { display: block; width: 100%; height: auto; }

.gmg-footer__brandtext {
  font-family: Rubik, system-ui, sans-serif;
  font-weight: 400;
  font-size: 16px; line-height: 28px;
  color: var(--gmg-ink-footer);       /* 6.81:1 — the one place the kit's "text-two" paints */
  margin: 0;
}
@media (min-width: 1025px) { .gmg-footer__brandtext { font-size: 18px; line-height: 30px; } }

.gmg-footer__heading {
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700;
  font-size: 24px; line-height: 34px;   /* cardTitle role — 24/34 at every width */
  color: var(--gmg-white);
  margin: 0;
}

/* ⛔ THESE LINKS ARE WHITE. #CC3366 is the computed `color` of the live anchors
   and paints ZERO pixels at exact match AND at +/-8 (tokens
   $declaredContradicted.footerLinkInk). A build that "reproduces the measured
   ink" ships a footer nobody can read, at 2.10:1. */
.gmg-footer__link,
.gmg-footer__addr {
  font-family: Rubik, system-ui, sans-serif;
  font-weight: 400;
  font-size: 16px; line-height: 28px;
  color: var(--gmg-white);
  text-decoration: none;
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--gmg-gap-sm);
  min-height: 44px;                    /* F-2: live is 49x19 / 77x19 / 72x19 / 67x19 */
}
@media (min-width: 1025px) { .gmg-footer__link, .gmg-footer__addr { font-size: 18px; line-height: 30px; } }
.gmg-footer__link:hover { text-decoration: underline; }
.gmg-footer__col--links { gap: 0; }

.gmg-footer__map { display: block; }
/* ⛔ A FIXED HEIGHT, LIKE THE /contact-us/ MAP BAND, AND FOR THE SAME REASON.
   The footer map renders 318 x 411 and does NOT rescale with the container: live's
   footer is 701 px at 1440, 1920 and 2560 alike. Under `aspect-ratio` the column
   shrank with the 1250 px container and the footer came out 695 against live's 720
   at 1280 — a delta on three routes at once, which is what a scaling constant
   looks like when the source's is fixed. Below 1025 the authored surface caps it
   at 16:9 / 240 px (§17). */
.gmg-footer__mapframe { width: 100%; height: 411px; }   /* rendered 318 x 411 */
.gmg-footer__mapframe iframe { display: block; width: 100%; height: 100%; border: 0; }

/* =============================================================================
   §15b. THE ICON RINGS AND THEIR GLYPHS.

   ⛔ ROUND-3 REWRITE — TL ROW T-1. What was here painted a 14 × 14 SQUARE of
   `currentColor` inside every ring and gave only three of the eight variants a
   `clip-path` — and `--mail`'s clip-path was a plain rectangle, so it painted a
   square too. The four with none (`--badge`, `--accent`, `--phone`, and the
   second why-choose card that shared `--accent`) rendered as filled squares on
   `/`, `/services/` and in the footer of every route.

   ⚠ EVERY ROW WAS GREEN ON THAT STATE, and it is worth naming why, because it is
   the same family as the 0 × 0 image and the 404's heading: a square has a box
   (P-9 / B-15 see a healthy element), it paints (P-6), it resolves its class to a
   non-empty rule (P-1), it clears contrast (P-7) and it changes no height (B-17).
   ⛔ NOT ONE ROW ASKED WHETHER THE SHAPE MEANT ANYTHING. `browser-battery.mjs`
   B-24 GLYPH-PRESENCE now does.

   The glyphs themselves are inline `<svg>` emitted by the composers through
   `tools/generation/gmg-icon-set.php`; this block only rings and sizes them.
   ============================================================================= */
.gmg-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 1px solid var(--gmg-rule-on-blue);
  /* ⛔ `line-height: 0` so the inline-flex ring is EXACTLY its declared box: an
     inline SVG child otherwise inherits the parent's line box and adds a few
     descender pixels, which is enough to move a 44 px checklist row. */
  line-height: 0;
}
.gmg-icon > svg {
  display: block;
  width: 55%; height: 55%;
  overflow: visible;          /* a 1.7 stroke on a 24-box clips at the edge otherwise */
}

/* ---- the footer's two rings (column 3) -------------------------------------
   ⛔ Column 1's two rings went with owner ruling O-4, which removed the share
   controls they ringed; these two stay.
   MEASURED off live w1440/home.png: both rings are 52 × 52 (x 721..772,
   y 4573..4624 and 4649..4699), not the 40 that shipped. */
/* ⛔ KEYED TO THE VARIANT, NOT TO THE FOOTER, AND P-1 IS WHY. A
   `.gmg-footer .gmg-icon` descendant rule leaves `gmg-icon--pin` and
   `gmg-icon--mail` as classes that RENDER AND RESOLVE TO NOTHING — which is
   exactly what P-1 CLASS-HAS-CSS exists to refuse, and it caught this within a
   run of writing it. ⚠ The class that names the thing should be the class that
   sizes it; a descendant selector that happens to cover it is a coincidence of
   placement. */
.gmg-icon--pin, .gmg-icon--mail { width: 52px; height: 52px; }
.gmg-icon--pin > svg, .gmg-icon--mail > svg { width: 22px; height: 22px; }

/* ---- the two why-choose glyphs on /services/ -------------------------------
   accentIndigo #4F6BD6 is the ONLY place this value paints sitewide and it has
   exactly two consumers, which is why it is a component literal and not a
   palette entry (tokens.color.component.accentIndigo).
   MEASURED: the two discs are 67 × 66 (x 94..160 and x 441..507, y 1774..1839
   on w1440/services.png), not the 40 the base ring gives. */
.gmg-icon--accent { width: 67px; height: 67px; background: #4F6BD6; border-color: transparent; color: var(--gmg-white); }
.gmg-icon--accent > svg { width: 34px; height: 34px; }
/* ⛔ The two glyph IDENTITIES carry their own rules even though the disc is
   shared, so P-1 can see them: a class that renders and resolves to no rule is
   indistinguishable from a class the CSS forgot. The stopwatch's crown bar sits
   above its circle and reads a hair high in a 34px box; the headset figure does
   not. That is the whole difference and it is worth one declaration each. */
.gmg-icon--clock  > svg { translate: 0 1px; }
.gmg-icon--person > svg { translate: 0 0; }
/* The glyph block above each outlined card's title. ⛔ `display: flex` rather
   than the default block: the wrapper is a <div> around an inline-flex ring, so
   a block wrapper adds the line-box strut's descender to the card — ~4 px of
   nobody's spacing, inside a band whose height is a parity assertion. */
.gmg-card__glyph { display: flex; }

/* ---- the checklist tick ----------------------------------------------------
   ⛔ ONE GLYPH, TWO TREATMENTS, AND BOTH ARE LIVE — measured, not inferred:
     /about-us/  OUTLINED ring + indigo tick on a light ground
                 (w1440/about-us.png, 18 × 18 at x 979..996)
     /services/  FILLED indigo disc + WHITE tick on the blue band
                 (w1440/services.png, the four rows at y 1535..1680)
   The ring is drawn by the SVG's own `circle`, not by the span's border, so the
   two treatments are one fill declaration apart. */
.gmg-icon--check {
  width: 18px; height: 18px;
  border: 0;                                  /* the ring is IN the path set */
  color: #4F6BD6;
}
.gmg-icon--check > svg { width: 100%; height: 100%; }
.gmg-band--svcwhy .gmg-icon--check .gmg-icon__disc { fill: #4F6BD6; stroke: #4F6BD6; }
.gmg-band--svcwhy .gmg-icon--check .gmg-icon__tick { stroke: var(--gmg-white); }


/* =============================================================================
   §16. LAYOUT PRIMITIVES — splits, grids, cards, panels, images.

   ⛔ THE STACK ORDER AT <=767 IS ALWAYS MEDIA-ABOVE-COPY (LAYOUT_TRUTH §5,
   F-3 / M-8), on every split band, on every route. It is a single rule and
   there is no per-section exception — §18 is where it lands.
   ============================================================================= */

/* Images. ⚠ An UNLINKED Bricks image renders `<img class="brxe-image gmg-img">`;
   a LINKED one renders `<a class="brxe-image gmg-img"><img></a>`. Both forms
   occur in these composers, so both are addressed. A rule written for only one
   silently leaves half the site's images at intrinsic size — which at 2560 px
   natural is most of round 1's overflow. */
.gmg-img, img.gmg-img { display: block; width: 100%; height: auto; max-width: 100%; }
.gmg-img img { display: block; width: 100%; height: auto; }
/* radius.none is "0" and it is a REAL design choice: *"every photograph on / and
   /contact-us/ has square corners ... a build that rounds every image is wrong."* */
.gmg-img--square { border-radius: 0; }
.gmg-img--sm, .gmg-img--md { border-radius: var(--gmg-radius-sm); }

/* =============================================================================
   §16a. CROP ANCHORS — ⛔ AUDIT FINDING F-2, AND IT IS THE SHARPEST INSTRUMENT
   GAP IN THE SET.

   Round 2 shipped **zero `object-position` declarations in the entire tree**.
   §17 caps every photograph to `aspect-ratio: 4/3; object-fit: cover` at ≤767
   (and 16/9 on the card grid), so the browser default `50% 50%` applied to all
   fifteen of them — and on a PORTRAIT source that is a guillotine.

   MEASURED, at 375 on `/`: `.gmg-trio__b` is 1707 × 2560 natural in a 345 × 259
   box; cover scales it to 345 × 517, so **129 px comes off the top and 129 off
   the bottom — 25 % from each end**. At 768 the box is 720 × 405 and it is
   **338 px, 31 %, off each end**. In the 375 screenshot of that band both men
   appear from the chest down and NEITHER FACE IS IN FRAME. At 1440 the same band
   is correct, which is why a desktop review never saw it.

   ⛔ WHY NOTHING CAUGHT IT, AND IT IS NOT AN OVERSIGHT — IT IS STRUCTURAL:
     P-9 asks whether the image LAYS OUT NON-ZERO  → 345 × 259 ✓
     P-8 asks whether it DECODES                   → ✓
     B-15 asks whether anything OVERFLOWS          → nothing does
     B-17 asks whether the page got TALLER         → it did not
     S-2 asks whether the RIGHT FILE is wired      → by sha256, ✓
   Every row asks *"is this image present?"*. Not one asks *"is the subject still
   in it?"* — the same shape as the 0 × 0 image one step along (KB item 20), and
   the same shape as the empty icon slot beside it.

   ⚠ NO INSTRUMENT ON THIS FLEET SEES A FACE. `paint-battery` P-10 therefore
   asserts the only thing a machine can: that every capped photo **declares an
   authored `object-position`**, per the table below, rather than inheriting the
   default — a presence-of-intent row, and it says so in its own output. THE
   VERIFICATION THAT A HEAD IS IN FRAME IS THE SCREENSHOT, and round 3's review
   set carries 375 and 768 crops of every one of these bands for exactly that.

   ⛔ AUTHORED UNCONDITIONALLY, NOT INSIDE §17's MEDIA QUERY. `object-position`
   is inert when the box matches the source ratio, so scoping it to ≤1024 buys
   nothing and leaves every future cap — a new tablet rung, a `max-height` that
   starts biting — back on `50% 50%` silently.

   THE TABLE. Percentages are the SOURCE's own vertical extent, read off
   content-source/media/ with a 10 % grid overlaid (crops in the round-3 review
   set). A head band of "8–30 %" with a 50 % window and P = 8 % puts the window
   at 4 %–54 %: the whole head band, with margin at both ends.

     slot                            source (natural)        heads at   anchor
     .gmg-trio__a / __b              1707 × 2560 PORTRAIT     8–30 %     50% 8%
     .gmg-card--photo img            2560 × 1707 ×3           0–40 %     50% 20%
     .gmg-band--hero .gmg-img        2560 × 1707 (no faces)   —          50% 50%
     .gmg-band--why .gmg-img--sm       550 × 326              8–30 %     50% 10%
     .gmg-band--svcwhy .gmg-img--sm    550 × 321              8–28 %     50% 10%
     .gmg-band--faq .gmg-img--md       520 × 516             15–40 %     50% 20%
     .gmg-trio3__col--a .gmg-img--sm   410 × 430              8–30 %     50% 10%
     .gmg-trio3__col--b .gmg-img--sm   410 × 430             12–30 %     50% 12%
     .gmg-band--practice .gmg-img--md  630 × 297             12–35 %     50% 15%
     .gmg-band--booking .gmg-img       550 × 261              —          50% 50%
   ⚠ The booking photo is the one that crops HORIZONTALLY (550 × 261 into a 4/3
   box takes ~100 px off each SIDE, not off the top), and both subjects sit
   between 25 % and 90 % of the width, so centre is the correct anchor there and
   it is written out rather than left to the default — a default that happens to
   be right is indistinguishable from one nobody checked.
   ============================================================================= */
.gmg-trio .gmg-trio__a, .gmg-trio img.gmg-trio__a,
.gmg-trio .gmg-trio__b, .gmg-trio img.gmg-trio__b { object-position: 50% 8%; }
.gmg-card--photo img.gmg-img                      { object-position: 50% 20%; }
.gmg-band--hero .gmg-img--square,
.gmg-band--hero img.gmg-img--square               { object-position: 50% 50%; }
.gmg-band--why .gmg-img--sm,
.gmg-band--why img.gmg-img--sm                    { object-position: 50% 10%; }
.gmg-band--svcwhy .gmg-img--sm,
.gmg-band--svcwhy img.gmg-img--sm                 { object-position: 50% 10%; }
.gmg-band--faq .gmg-img--md,
.gmg-band--faq img.gmg-img--md                    { object-position: 50% 20%; }
.gmg-trio3__col--a .gmg-img--sm,
.gmg-trio3__col--a img.gmg-img--sm                { object-position: 50% 10%; }
.gmg-trio3__col--b .gmg-img--sm,
.gmg-trio3__col--b img.gmg-img--sm                { object-position: 50% 12%; }
.gmg-band--practice .gmg-img--md,
.gmg-band--practice img.gmg-img--md               { object-position: 50% 15%; }
.gmg-band--booking .gmg-img--square,
.gmg-band--booking img.gmg-img--square            { object-position: 50% 50%; }

/* ---- Split bands ----------------------------------------------------------- */
.gmg-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--gmg-gap-xl);
  align-items: center;
}
/* ⚠ THE COPY STACK'S GAP IS `md`, AND IT IS DERIVED, NOT PICKED. The home hero
   at 1440 is 940 px with a 0/0 container on live; under the authored `band`
   register that is 748 px of content. Its four measured blocks — eyebrow 46,
   h2 501 x 500 (five lines), body 90, CTA 50 — are 686, leaving 62 px across
   three gaps: 20.7 each, i.e. the `md` step. The same step reproduces the About
   band (46 + 195 + 90 + 139 + 139 + 50 + 5 x 20 = 759 against a 766 content box).
   ⛔ `lg` measured +50 on the hero and +50 on About — both outside B-5's ±2%. */
.gmg-split__copy  { display: flex; flex-direction: column; align-items: flex-start; gap: var(--gmg-stack, var(--gmg-gap-md)); }
.gmg-split__media { display: flex; flex-direction: column; gap: var(--gmg-stack, var(--gmg-gap-md)); min-width: 0; }

/* ⛔ `--gmg-stack` IS A DERIVED VALUE, NOT A DIAL, AND THE DERIVATION IS ONE
   FORMULA APPLIED SEVEN TIMES:
 *
 *       stack = (measured band height − 2 × register − Σ measured content boxes)
 *               ÷ (number of gaps in the driving column)
 *
 *   Every term on the right is measured: the band height and the register from
 *   tokens-measured.json / tokens.json, and the content boxes from the route's
 *   page spec (the h2 566 × 195, the photo 550 × 326, the CTA 214 × 50 …).
 *   tokens.json derived its own 96 px `band` register exactly this way — *"the
 *   median gap between a section's content block and its band edge at 1440 is
 *   ~96px"* — so this is the same arithmetic one level down.
 *
 * ⚠ AND IT IS NOT A SUBSTITUTE FOR REPRODUCING THE BOXES. The `rhythm-gate`
 *   lesson is that a build can post green section heights while every card and
 *   form cell sits flush: the height is a SUM and a sum has many decompositions.
 *   So the boxes are pinned FIRST (the split proportions above, the image caps
 *   and heading max-widths below — all measured), and `--gmg-stack` only
 *   absorbs the residue live never published. docs/generation/parity/round2/
 *   GEOMETRY_DIFF.md carries both tables, which is what makes that checkable
 *   rather than asserted.
 *
 * ⛔ Do NOT reach for this value to make a band green after changing a box. If a
 *   box moved, the box is the bug. */
/* `--media-first` is a COMPOSITION ORDER, not a visual reversal: the composer
   already writes the media column first in the tree, so nothing is reordered
   here at desktop. It exists so §17 can force media-above-copy uniformly. */

/* ========================================================================== */
/* ⛔ EVERYTHING FROM HERE TO THE END OF THIS BLOCK IS DESKTOP PARITY GEOMETRY  */
/*    AND IS SCOPED TO >=1025 FOR A REASON THAT COST A ROUND TO FIND.          */
/*                                                                            */
/*    Every rule inside carries TWO classes (`.gmg-band--about .gmg-split`).   */
/*    §17's collapse rule carries ONE (`.gmg-split`). SPECIFICITY BEATS SOURCE  */
/*    ORDER, so an unscoped parity rule silently OUTRANKS the entire mobile    */
/*    surface — and the symptom is not "mobile looks wrong", it is a 72 px     */
/*    copy column beside a 69 px media column at 375 with 151 px of horizontal */
/*    overflow. MEASURED before this wrapper existed: overflow 151 / 57 / 14 / */
/*    63 px on the four routes, where round 1 had ZERO. ⚠ A build can regress  */
/*    B-15 by making the DESKTOP more correct.                                 */
/* ========================================================================== */
@media (min-width: 1025px) {
  /* ---- The seven split proportions ------------------------------------------
     ⛔ THERE IS NO SINGLE RATIO ON THIS SITE. Each pair below is the measured pair
     of rendered boxes from the route's page spec, expressed as `fr` so the whole
     split scales, with the gap being the measured remainder of the container. A
     `1fr 1fr` split is wrong on all seven and its only visible symptom is a
     heading wrapping one line short — which `home.md` §2 calls a changed
     composition, not a rounding error. See compose-pages.php's round-2 note. */

  /* home hero — media 619 (photo 619 x 413) and a copy column of 540.
     ⚠ LIVE'S COPY BOX IS 501 AND 501 DOES NOT REPRODUCE LIVE'S WRAP IN THE SHIPPED
     FACE. Measured on this build by stepping the column 440 → 1100 px in 10 px
     increments and counting line boxes against the computed 100 px line-height:
         7 lines ≤ 440  ·  6 lines 490–500  ·  5 lines 510–600  ·  4 lines 610–860
     Live renders five lines at 501; self-hosted Albert Sans 700 renders six there.
     A sub-2 % metric difference lands one pixel the wrong side of a wrap, and the
     VISIBLE consequence is a 100 px band error. 540 is the middle of the five-line
     band, so the wrap is robust to that metric difference rather than balanced on
     it. ⛔ The parity target is the FIVE-LINE WRAP (home.md §2), not the 501 px
     number — the number is the means. Recorded as GENERATION_DECISIONS D-8. */
  /* ⛔ THE HERO HAS A LEADING SPACER, AND THE HEIGHTS WERE GREEN WITHOUT IT.
     READ OFF THE CAPTURES WITH MY OWN EYES — w1440/home.png and
     w2560/home.viewport.png — because no committed artifact carries a column's
     x-offset (LAYOUT_TRUTH §0: "There is no y-offset anywhere in Source A"; there
     is no x-offset either):

       @1440  container 15..1425   copy starts 287   photo 805..1425
       @2560  container 330..2230  copy starts 601   photo 1362..2229

     The copy column is inset ~272 px from the container's left edge AT BOTH
     WIDTHS, and the photo is flush right. The first pass put the copy hard
     against the container edge with a 251 px gap in the MIDDLE — which produced
     a band height inside B-5's +/-2% at 1440 AND 2560 and a visibly different
     page. ⚠ THIS IS THE `rhythm-gate` LESSON IN ITS OWN RIGHT: "34 green rows on
     a build with no internal spacing". A height is a sum; the same sum has many
     arrangements, and only one of them is the composition.

     Three columns, and the free space is the FIRST one. 540 (not live's measured
     501) is the five-line wrap band — see GENERATION_DECISIONS 5. */
  .gmg-band--hero .gmg-split {
    /* ⚠ 216, NOT THE 272 THE CAPTURE SHOWS, AND THE SECOND TABLE IS WHY.
       Live spends its 1410 as 272 spacer + 501 copy + 17 + 619 photo, with ONE
       gap. A CSS grid puts a gap between every pair, so 272 + 2 x 17 left 1104
       for two columns and the PHOTO came out 590 x 393 against its measured
       619 x 413 (box-truth). The photo is a measured box and the spacer is not
       measured anywhere — no committed artifact carries a column x-offset — so
       the photo wins and the spacer absorbs the difference:
           1410 - 216 - 2 x 17 = 1160 -> copy 540, photo 620.
       ⛔ RESIDUAL, recorded rather than hidden: the copy column now starts 248 px
       into the container against live's 287. GEOMETRY_DIFF carries it. */
    grid-template-columns: 216px minmax(0, 540fr) minmax(0, 619fr);
    gap: 17px;
  }
  .gmg-band--hero .gmg-split__copy  { grid-column: 2; }
  .gmg-band--hero .gmg-split__media { grid-column: 3; }
  /* ⛔ AND THE 251 px GAP IS A 1440 MEASUREMENT THAT DOES NOT SURVIVE 1025.
     At 1025 the hero's container is 995, so 251 of gap leaves 744 for the two
     columns and the copy lands at 347 — narrower than the word "Wellness" set
     at 85 px. MEASURED: B-15 INK TIER, two elements at w1025 on `/` — the h2 and
     its column — `350/347 excess 3`. ⚠ A THREE-PIXEL ink overflow, invisible in
     a screenshot, at the one rung where an 85 px display face meets the
     narrowest desktop container. Below 1280 the gap drops to the `xl` step,
     which leaves the copy 440 px — wider than the longest word at that size. */
  /* ⛔ BELOW 1280 THE HERO STACKS, AND THAT IS PARITY, NOT A RETREAT.
     The 272 px spacer plus a 540 px copy column plus a 619 px photo needs
     ~1430 px of container. At 1025 the hero's container is 995, so the copy
     column lands at 290 — narrower than the word "Wellness" set at 85 px — and
     B-15's INK TIER measured exactly that: `<H2> 351/290 excess 61 @85px`, two
     elements, one rung. ⚠ A 61 px ink overflow is invisible in a screenshot and
     it is a real horizontal scroll.
     LIVE STACKS HERE TOO: its hero is 1420 px at 1025 against 940 at 1440 — a
     +480 jump that is the photograph moving below the copy, because live's own
     501 + 619 does not fit in 995 either. So the single column at this rung
     reproduces the source rather than departing from it.
     ⚠ 1280 is NOT included: live is 1160 there and the build measured 1138 with
     two columns — inside tolerance — so the two-column form still holds. */
  @media (max-width: 1279px) {
    .gmg-band--hero .gmg-split {
      grid-template-columns: minmax(0, 1fr);
      gap: var(--gmg-gap-xl);
    }
    .gmg-band--hero .gmg-split__copy,
    .gmg-band--hero .gmg-split__media { grid-column: 1; }
  }
  /* home about — READ OFF w1440/home.png: the container is 63..1377, the three-piece
   photo composition occupies 96..699 and the copy column 811..1377. So the media
   column is ~637 and the copy ~566 (which is the h2's own measured box, three
   lines) with ~111 between them — NOT the 544/566 with a 204 px gap the first
   pass used, which pulled the trio left and opened a channel down the middle. */
  .gmg-band--about .gmg-split {
    grid-template-columns: minmax(0, 637fr) minmax(0, 566fr);
    gap: 111px;
  }
  /* home why — copy 621 (h2 box 621 x 130) / panel 673 (its h2 box is 573 inside
     50 px of panel padding a side). */
  .gmg-band--why .gmg-split {
    grid-template-columns: minmax(0, 621fr) minmax(0, 673fr);
    gap: var(--gmg-gap-md);
  }
  /* /services/ why — copy 620 (h2 box 620 x 195) / media 550 (photo 550 x 321). */
  /* ⚠ READ OFF w1440/services.png, not off the h2's box. The h2 measures 620 and
     the COLUMN is 673 — the two outlined cards below it span 64..737 while the
     checklist and photo occupy 825..1377. A column sized to the heading is 53 px
     narrow and opens a 144 px channel where live has 88. */
  .gmg-band--svcwhy .gmg-split {
    grid-template-columns: minmax(0, 673fr) minmax(0, 552fr);
    gap: 89px;
  }
  /* /services/ FAQ — media 520 (photo 520 x 516) / copy 632 (h2 box 632 x 130). */
  .gmg-band--faq .gmg-split {
    grid-template-columns: minmax(0, 520fr) minmax(0, 632fr);
    gap: 162px;
  }
  /* /about-us/ practice — copy 626 (photo 626 x 295) / three rows. */
  .gmg-band--practice .gmg-split {
    grid-template-columns: minmax(0, 626fr) minmax(0, 660fr);
    gap: var(--gmg-gap-lg);
  }
  /* /contact-us/ booking — copy 647 (h2 box 647 x 130) / panel outer 547. */
  /* ⛔ 547 IS THE PANEL'S INNER WIDTH, NOT THE PANEL. LAYOUT_TRUTH §2.4 records
     row 3a's `outerWidth: 547` and that figure was used as the COLUMN — but the
     same row carries `pad 40/50/50/50`, so 547 is what is left INSIDE 50 px a
     side. w1440/contact-us.png shows the grey panel spanning 728..1374, i.e.
     ~646 wide, against a copy column of 63..710. MEASURED consequence of the
     first pass: a panel 99 px narrower than live's with a 120 px channel beside
     it. 647 + 646 + 20 = 1313 against a 1314 container.
     ⚠ `align-items: stretch` because live's panel is the full height of the
     band's content row rather than a centred box. ⛔ It does NOT bleed into the
     band's 96 px padding the way live's does — that residual is recorded in
     GEOMETRY_DIFF rather than chased. */
  .gmg-band--booking .gmg-split {
    grid-template-columns: minmax(0, 647fr) minmax(0, 646fr);
    gap: var(--gmg-gap-md);
    align-items: stretch;
  }

  /* ---- The seven derived stack gaps ------------------------------------------
     stack = (band height − 2 × register − Σ content boxes) ÷ gaps, per the formula
     above. Each line shows its own arithmetic so the next reader can re-derive it
     instead of trusting it. The default (`md`, 20 px) is what five of the twelve
     bands already need, so only the exceptions are listed.

     ⚠ THE HERO IS THE ONE BAND WHOSE LIVE HEIGHT DEPENDS ON WIDTH — 940 at 1440
     and 780 at 2560, a 160 px difference driven by its 1900 px container rewrapping
     the h2 from five lines to four. Its internal gap on live is therefore NOT a
     constant either: the same decomposition gives 20.7 px at 1440 and 10.7 px at
     1920/2560. Both rungs are authored, because pretending one constant fits both
     is how a band lands 28 px out at the owner's review width. */
  .gmg-band--about    { --gmg-stack:  9px; }  /* (958 − 192 − 721) ÷ 5 gaps  ≈  9  */
  /* ⚠ 9px IS TIGHT AND IT IS NOT A SLIP. rhythm-battery R-7 flags this band's copy
     stack as the outlier in its cohort (gaps 9..57 across the three `.gmg-split__copy`
     instances on `/`), and it is right that they differ. MEASURED at the `md` step
     instead: the band came out 1011 against 958, +5.5%. The About column carries SIX
     blocks — eyebrow, a three-line h2, body, two info cards and a CTA — in the same
     766px of content box that the hero spends on four. ⛔ The uniform-stack reading
     and the measured band height cannot both hold here, and the band height is the
     one live measured. Recorded in GEOMETRY_DIFF and GENERATION_DECISIONS A-1. */
  .gmg-band--why      { --gmg-stack: 57px; }  /* (954 − 192 − 592) ÷ 3 gaps  ≈ 57  */
  /* ⛔ 31 -> 18, AND THE RE-DERIVATION IS THE POINT, NOT THE NUMBER. Round 3
     grew the two why-choose discs from 40 to live's measured 67 (§15b), which
     added 27 px to this band's driving column — and R-4 went red at 1440, 1920
     AND 2560 at once, +27 (3.4 %) on all three. ⚠ THREE IDENTICAL DELTAS AT
     THREE WIDTHS IS THE SIGNATURE OF A CHANGED BOX, not of a wrong composition,
     which is exactly what it was.
     The band height is live's and does not move; the BOX moved, so the residue
     the stack absorbs has to move with it — GENERATION_DECISIONS A-1's whole
     rule. Re-derived with the new box: */
  .gmg-band--svcwhy   { --gmg-stack: 18px; }  /* (795 − 192 − 568) ÷ 2 gaps ≈ 17.5 → 18; 568 = 541 + 27 disc */
  .gmg-band--practice { --gmg-stack: 47px; }  /* (821 − 192 − 535) ÷ 2 gaps  ≈ 47  */
  .gmg-band--intro    { --gmg-stack: 49px; }  /* (865 − 192 − 64 − 130 − 430) ÷ 1 gap = 49 — the LEFT column drives: its 64px offset IS the interleave */
  /* ⛔ ROUND-3 — THE BOOKING BAND IS RE-DERIVED, BECAUSE ITS REGISTER IS ZERO.
     TL row T-8: live's grey panel is FLUSH to the blue title band above it.
     MEASURED on w1440/contact-us.png against the r2 build at the same width:
         live   title band 100..504 · panel 505..1325 (821) · GAP **0 px**
         build  title band 100..504 · panel 601..1245 (645) · gap **96 px**
     ⚠ AND THE BAND HEIGHT MATCHED ANYWAY — 837 built against 821 live, inside
     B-5's ±2 %. The build spent 96 + 645 + 96 where live spends 0 + 821 + 0.
     ⛔ THIS IS THE `rhythm-gate-and-truth` SHAPE IN MINIATURE: the sum agreed and
     every one of its terms was wrong, which is precisely why box-truth exists as
     a second table and why a height row is never the verdict on its own.
     The note two blocks up said so at the time — *"it does NOT bleed into the
     band's 96 px padding the way live's does — that residual is recorded rather
     than chased"*. Round 3 chases it, and the arithmetic closes exactly:
         panel  605 + 4 × form-gap = 821  ->  form gap **54**
         copy   497 + 3 × stack    = 575  ->  stack **26**
                (575 is live's own left column, ink y 555..1130)
     ⛔ TWO NUMBERS, NOT ONE, because `--gmg-stack` drives BOTH the copy column's
     flex gap AND `.gmg-form form`'s row gap (§12). One variable cannot satisfy
     two measured columns, and forcing it to would have put ~85 px of invented
     slack under the booking photograph. */
  .gmg-band--booking  { --gmg-stack: 26px; }
  .gmg-band--booking  { padding-block: 0; }
  .gmg-band--booking .gmg-split__copy { padding-top: 50px; }   /* live's copy starts 50 below the band top */
  /* ⛔ 54 -> 40, AND THE REASON IS THAT FIXING THE TURNSTILE GATE ADDED A BOX
     LIVE DOES NOT HAVE.
     Rounds 1–2 shipped `cfturnstile_tested` as `1`, which the plugin's own gate
     (`empty($opt) || $opt == 'yes'`) treats as neither set nor unset — so the
     widget never rendered, `gmg_site_strip_unregistered_turnstile` emptied its
     paragraph, and the row cost nothing. With the value corrected to `'yes'` the
     widget renders and `.gmg-form__turnstile` measures **83 × 447**, taking the
     panel from 821 to **904** and putting R-4 red at 1440, 1920 AND 2560 at once
     — ⚠ the changed-BOX signature again (see `--svcwhy` above).
     ⛔ THE BAND DOES NOT MOVE: 821 is live's, and the panel IS the band (T-8).
     The Turnstile widget is an AUTHORED ADDITION — live's form carries no visible
     challenge at all (owner ruling R-9, no reCAPTCHA badge) — and GENERATION_BRIEF
     §4.5 requires it. An authored box inside a measured band is paid for out of
     the band's internal spacing, never out of the band:
         4 form-row gaps × (54 − 33) = 84 ≈ the widget's 83
     ⚠ FOUR gaps, not six. `.gmg-form form` has seven children and two of them —
     CF7's `hidden-fields-container` and its `wpcf7-response-output` — are
     `display: none`, so a flex column spends no gap on them. Counting DOM
     children instead of FLEX ITEMS gave 6, a gap of 40, and a panel 27 px too
     tall; the arithmetic was re-derived against the measurement rather than the
     markup. ⛔ A gap count read off the HTML is a guess about layout.
     Every measured BOX is untouched — fields 209 × 52, textarea 447 × 180, submit
     h 44, all still asserted by box-truth. ⚠ This is the one lever the derivation
     permits (`--gmg-stack`'s rule, A-1) and it is used because a box was ADDED on
     purpose, not because a band came out wrong. */
  .gmg-band--booking .gmg-form form   { gap: 33px; }
  @media (min-width: 1920px) {
    .gmg-band--hero   { --gmg-stack: 10px; }  /* (780 − 192 − 556) ÷ 3 gaps  ≈ 10.7 */
  }
  /* The card stacks. Same formula, applied to the grid ROW height rather than to a
     column: the band is as tall as its tallest card plus the register. */
  .gmg-band--services .gmg-card--photo { gap: 28px; }   /* (983 − 192 − 196 − 50 − 461) ÷ 3 ≈ 28 */
  .gmg-band--svcgrid  .gmg-card        { gap: 39px; }   /* two rows: ((861 − 192 − 30) ÷ 2 − 242) ÷ 2 ≈ 39 */

  /* ---- The measured image boxes ----------------------------------------------
     ⛔ EVERY PHOTOGRAPH ON THIS SITE IS SERVED 1:1 OR NEARLY SO, AND ITS RENDERED
     WIDTH IS SMALLER THAN ITS COLUMN. A build that runs each photo to its column
     width gets the section height wrong AND the composition wrong — the home "why"
     photo measured 621 x 368 against a live 550 x 326 before this block existed.
     Each cap is the rendered box from the route's page spec. */
  .gmg-band--why    .gmg-img--sm { max-width: 550px; aspect-ratio: 550 / 326; object-fit: cover; }
  .gmg-band--svcwhy .gmg-img--sm { max-width: 550px; aspect-ratio: 550 / 321; object-fit: cover; }
  .gmg-band--faq    .gmg-img--md { max-width: 520px; aspect-ratio: 520 / 516; object-fit: cover; }
  .gmg-band--practice .gmg-img--md { max-width: 626px; aspect-ratio: 626 / 295; object-fit: cover; }
  /* ⛔ THE INTRO'S TWO PHOTOGRAPHS ARE A FIXED RENDER, NOT A SCALING ONE.
     about-us.md §2 gives both as "410 x 430 (natural 410 x 430 — SERVED 1:1)".
     Under `aspect-ratio` they tracked the column, so at a 1250 px container they
     came out 385 x 404 and the band measured 825 against live's 908 — −9.1 %, and
     the same shape at 1025. A served-1:1 photograph does not rescale with its
     column; live's does not, and neither does this one. `max-width: 100%` keeps
     it inside a narrower column without letting the height follow. */
  .gmg-trio3 .gmg-img--sm { width: 410px; max-width: 100%; height: 430px; object-fit: cover; aspect-ratio: auto; }
  .gmg-band--booking  .gmg-img--square { max-width: 550px; aspect-ratio: 550 / 261; object-fit: cover; }
  .gmg-band--hero     .gmg-img--square { aspect-ratio: 619 / 413; object-fit: cover; }

  /* ---- The measured heading boxes --------------------------------------------
     A heading that sits in a FULL-WIDTH block rather than in a column has nothing
     to wrap against. The home Services h2 measures 647 x 130 — TWO lines — and
     rendered at the full 1314 container it is one line and the band is 65 px
     short. Live's own box is the cap. */
  .gmg-band--services .gmg-sectionhead .gmg-section-title { max-width: 647px; }
  /* ⚠ A COLUMN IS NOT A MEASURE. Two headings sit in columns wider than their
     own measured boxes, and left uncapped they wrap one line short — which is
     both a composition change and, because a line is 65 px, a band-height error:
       /services/ why   h2 box 620 in a 673 column -> 2 lines, band -45
       /about-us/ prac  h2 box 576 in a 625 column -> the same shape
     box-truth caught both from the other side (`svcWhyH2 673 x 130 vs 620 x 195`). */
  .gmg-band--svcwhy   .gmg-split__copy .gmg-section-title { max-width: 620px; }
  .gmg-band--practice .gmg-split__copy .gmg-section-title { max-width: 576px; }

}

/* ---- Grids ----------------------------------------------------------------- */
.gmg-grid { display: grid; gap: var(--gmg-gap-lg); }
.gmg-grid--3   { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.gmg-grid--3x2 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.gmg-grid--2   { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.gmg-sectionhead { display: flex; flex-direction: column; align-items: flex-start; gap: var(--gmg-gap-md); }

/* ---- Cards ----------------------------------------------------------------- */
/* §11 above owns the card's ground, border, radius and its NEGATIVE hover rule
   (O-8 / B-13). Only the box model is here. ⛔ Do not add a hover, a lift, a
   shadow or a transition: this site separates cards with a 1 px hairline on a
   tinted field, never with elevation, and B-13 is a negative row with an
   identity assertion so it fails on any added property. */
.gmg-card {
  display: flex;
  flex-direction: column;
  gap: var(--gmg-gap-md);
  padding: var(--gmg-gap-lg);
  overflow: hidden;
}
/* The home photo card. ⚠ THE PHOTO IS NOT FLUSH, AND THE MEASUREMENT SAYS SO:
   the card column at 1440 is (1314 − 2 × 30) / 3 = 418 and the photo renders
   379 x 253 — a 19.5 px inset a side, i.e. the card's own `md` padding. A build
   that runs the photo to the card edge is 40 px wider per photo and loses the
   hairline the whole card system is separated by. */
.gmg-card--photo { padding: var(--gmg-gap-md); gap: var(--gmg-gap-lg); }
.gmg-card--photo img.gmg-img { aspect-ratio: 379 / 253; object-fit: cover; }

.gmg-card__top { display: flex; align-items: center; justify-content: space-between; gap: var(--gmg-gap-md); }
.gmg-card__icon, img.gmg-card__icon {
  width: 56px; height: 56px; flex: 0 0 auto;
  border-radius: 50%;
  background: var(--gmg-brand-blue);
  padding: 12px;
  object-fit: contain;
}
.gmg-pill {
  display: inline-flex; align-items: center;
  background: var(--gmg-chip-pale);
  color: var(--gmg-ink);
  border-radius: var(--gmg-radius-pill);
  /* ⛔ ROUND-3 — TL ROW T-10, AND IT IS A MAX-WIDTH, NOT "PADDING". MEASURED off
     w1440/services.png, the three pills on row 1 of the grid:
         "Medical Care"      146 x 62   TWO lines
         "Wellness"          133 x 44   one line
         "Preventive Health" 146 x 62   TWO lines
     Two of the three sit at exactly 146 — that is a cap the text is breaking
     against, not a padding that happens to land there. The build shipped one
     line at 123 x 36 on all six, because nothing capped them.
     ⚠ 29 px of side padding is what makes an eight-letter word measure 133:
     "Wellness" is ~75 px of Rubik 14. Both numbers are live's. */
  max-width: 146px;
  padding: 7px 29px;
  font-family: Rubik, system-ui, sans-serif;
  font-size: 14px; line-height: 24px;
  text-align: center;
}

/* The two /services/ outlined cards — ⛔ the ONLY place accentIndigo paints. */
.gmg-card--outlined {
  background: transparent;
  border: 1px solid var(--gmg-rule-on-blue);
  border-radius: var(--gmg-radius-md);
  --gmg-ink-here: var(--gmg-white);
  --gmg-ink-here-body: var(--gmg-white);
}
/* ⛔ MOVED TO §15b with the glyph set — `.gmg-icon--accent` now carries the fill
   for BOTH why-choose cards, which since round 3 draw two DIFFERENT glyphs. */

/* ---- Panels ---------------------------------------------------------------- */
/* The home "Our Benefit" panel: a large white panel, radius lg, generous
   padding, three rows separated by --gmg-hairline rules. */
.gmg-panel {
  background: var(--gmg-white);
  border-radius: var(--gmg-radius-lg);
  padding: var(--gmg-gap-xl);
  display: flex;
  flex-direction: column;
  gap: var(--gmg-gap-lg);
}
.gmg-panelrow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: var(--gmg-gap-md);
  row-gap: var(--gmg-gap-xs);
  align-items: center;
  padding-top: var(--gmg-gap-lg);
  border-top: 1px solid var(--gmg-hairline);
  min-height: 44px;
}
.gmg-panelrow__icon, img.gmg-panelrow__icon {
  grid-row: 1 / 3;
  width: 56px; height: 56px;
  border-radius: 50%;
  background: var(--gmg-tint-lavender);
  padding: 12px;
  object-fit: contain;
  align-self: start;
}
/* ⚠ /about-us/'s three rows are the SAME SHAPE with a DIFFERENT SEPARATOR —
   solid blue circles and NO rules between them, unlike this panel's hairlines.
   Both are live and the difference is real design information (about-us.md §4). */
.gmg-prows { display: flex; flex-direction: column; gap: var(--gmg-gap-lg); }
.gmg-prow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: var(--gmg-gap-lg);
  row-gap: var(--gmg-gap-xs);
  align-items: center;
  min-height: 44px;
}
/* ⛔ ROUND-3 — TL ROW T-5, RE-MEASURED. The row says "~40 px in build vs ~72 px
   live"; neither number is what the two artifacts hold. MEASURED:
     live  (w1440/about-us.png, brandBlue discs) y 1527..1632, 1719..1824,
           1928..2033 at x 790..895  ->  **106 x 106**, 200 px row pitch
     build (review/generated-2026-09-11-r2, same route) x 719..772
           ->  **54 x 54** rendered from the 56 declared here
   ⚠ The direction of T-5 is right and both of its magnitudes are estimates off a
   screenshot — which is exactly what `conversion-gate-lessons` says to do with a
   reported number: run it against the REFERENCE before building to it. 106 is
   what the reference holds, so 106 is what ships.
   The glyph inside is a PNG from content-source/media/, so the disc grows by
   padding rather than by scaling the artwork past its natural size. */
.gmg-prow__icon, img.gmg-prow__icon {
  grid-row: 1 / 3;
  width: 106px; height: 106px;
  border-radius: 50%;
  background: var(--gmg-brand-blue);
  padding: 31px;                       /* (106 - 44) / 2 — live's glyph is ~44 in the disc */
  object-fit: contain;
  align-self: start;
}
/* ⛔ NOT ON A PHONE. A 106 px disc beside a 345 px column is a third of the row;
   the authored surface keeps the 56 it had (§17 owns the mobile rungs and there
   is no live mobile truth to match — LAYOUT_TRUTH §7.9). */
@media (max-width: 1024px) {
  .gmg-prow__icon, img.gmg-prow__icon { width: 56px; height: 56px; padding: 12px; }
}

/* The two stacked white info cards on the home About band. */
.gmg-infocard {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: var(--gmg-gap-md);
  row-gap: var(--gmg-gap-xs);
  align-items: center;
  background: var(--gmg-white);
  border-radius: var(--gmg-radius-sm);
  padding: var(--gmg-gap-md);
}
.gmg-infocard__icon, img.gmg-infocard__icon {
  grid-row: 1 / 3;
  width: 56px; height: 56px;
  border-radius: 50%;
  padding: 12px;
  object-fit: contain;
  align-self: start;
}
.gmg-tint--lavender { background: var(--gmg-tint-lavender); }
.gmg-tint--peach    { background: var(--gmg-tint-peach); }

/* ---- The home About trio — ⛔ THE ARRANGEMENT IS THE DESIGN ------------------
   *"This is the one place on the site where the arrangement is the design"* —
   239 x 359 behind and left, 329 x 493 overlapping it and sitting HIGHER, and a
   white 170 x 68 badge card docked to the pair's lower-left corner.
   ⚠ The wrapper takes `overflow-x: clip` (§5) — the badge is WHY the overhang
   exists (CAT-035). */
.gmg-trio { position: relative; display: block; padding-bottom: var(--gmg-gap-xl); min-height: 493px; }
.gmg-trio .gmg-trio__a, .gmg-trio img.gmg-trio__a {
  position: absolute; left: 0; top: 68px;          /* "behind and left", lower than the pair */
  width: 239px; aspect-ratio: 239 / 359; object-fit: cover;
}
/* ⛔ `left` IS 273, MEASURED OFF LIVE'S OWN SCREENSHOT, AND 215 WAS WRONG.
   At 215 the two photos OVERLAP by 24px. On live they do not touch: a spans
   x 93..332 and b spans x 366..695 at 1440 — a 34px GAP — so b starts 273px
   right of a's left edge. ⚠ The prose above says b "overlaps the first", and it
   is the prose that is loose: what overlaps is the BADGE CARD, not the photos.
   273 + 329 = 602, which is live's pair span (93..695) to the pixel — the
   arithmetic closing on an independently measured number is the check. */
.gmg-trio .gmg-trio__b, .gmg-trio img.gmg-trio__b {
  position: absolute; left: 273px; top: 0;         /* sits higher; 34px clear of a */
  width: 329px; aspect-ratio: 329 / 493; object-fit: cover;
}
/* ⛔ 230 x 214, A COLUMN, AND THE SPEC'S "170 x 68" IS NOT A THING THAT CAN
   EXIST. home.md §3 gives the card as 170 x 68 AND its title as `h3` Albert Sans
   24/34 reading "12+ Years Of Experience". box-truth.mjs already refused to
   assert the 68 because the two contradict: at 24px that string needs three
   34px lines in a 170px box — 102px of text before any padding.
   ⚠ THE BUILD BELIEVED THE 170 ANYWAY, AND IT SHOWED. Screenshot review at 1440
   caught the h3 spilling OUT of the white card and onto the photograph: the
   background ended and the word "Experience" kept going. ⛔ A battery had not
   caught it and could not have — the band height was inside ±2%, both photo
   boxes were inside ±6px, and every contrast row passed, because an overflowing
   child changes none of those. It took looking at the picture.
   MEASURED off live's own w1440 screenshot, by white-run detection on the card's
   background rather than by eye:
       card   x 176..405, y 1947..2160   ->  230 x 214
       glyph  ABOVE the text, not beside it; title on TWO lines
       trio   origin x 93, b's top y 1613, content height 493
   so left = 176-93 = 83, top = 1947-1613 = 334, and the card's bottom (548)
   hangs 55px BELOW the trio's 493 — which is the CAT-035 overhang the spec
   names and the reason the wrapper takes `overflow-x: clip` rather than hidden.
   ⛔ The h3 keeps the cardTitle role at 24/34: B-6 asserts cardTitle is 24 at
   EVERY width, so shrinking the type here to "make it fit" would redden a global
   ramp row from one component. The card is sized to the type, not the reverse. */
.gmg-badgecard {
  position: absolute; left: 83px; top: 334px; bottom: auto;
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--gmg-gap-md);
  width: 230px; min-height: 214px;
  background: var(--gmg-white);
  border-radius: var(--gmg-radius-sm);
  padding: var(--gmg-gap-md);
}
.gmg-badgecard__glyph .gmg-icon--badge {
  background: var(--gmg-brand-deep);
  border-color: transparent;
  color: var(--gmg-white);
  width: 56px; height: 56px;
}

/* ---- The /about-us/ interleaved three-column intro -------------------------
   ⚠ "Interleaved" is NOT a hedge — IT IS THE COMPONENT (LAYOUT_TRUTH §2.3).
   Three uneven columns whose photos sit at DIFFERENT vertical offsets so the eye
   threads down rather than across. ⛔ A builder who blocks the media to one side
   has built CAT-016 and lost the section. */
.gmg-trio3 {
  flex-direction: row; align-items: flex-start;    /* §6a-bis */
  display: grid;
  grid-template-columns: minmax(0, 413fr) minmax(0, 410fr) minmax(0, 400fr);
  /* 1314 - 2 x 47 = 1220, which is 413 + 410 + 397 — the three measured columns.
     At the `lg` step the gap left 1254 and each column came out ~13 px wide, so
     both photographs measured 423 x 444 against a served-1:1 410 x 430. */
  gap: 47px;
}
.gmg-trio3__col { display: flex; flex-direction: column; gap: var(--gmg-stack, var(--gmg-gap-lg)); }
.gmg-trio3__col--b { padding-top: 0; }
.gmg-trio3__col--a { padding-top: 64px; }          /* the centre photo SITS HIGHER — the offset IS the component */
.gmg-trio3__col--c { padding-top: 32px; }
.gmg-trio3 .gmg-img--sm { aspect-ratio: 410 / 430; object-fit: cover; }

/* Checklists — four rows, glyph + one line, 44 px rows.

   ⛔ ROUND-3 TYPE CORRECTION — TL ROW T-4. These shipped as REGULAR-WEIGHT body
   grey (`inkBody` #2D4443, Rubik 400) against live's BOLD near-navy. MEASURED off
   w1440/about-us.png, the four rows at y 759..885 on a 36 px pitch: the ink is
   **#031B33** — `--gmg-brand-deep` — and the face is bold.
   ⚠ brandDeep's own token note says it is "NOT a heading ink after
   normalisation"; that collapse (a11y.$collapses) folded the three HEADING uses
   of #031B33 into `--gmg-ink`. A checklist row is not a heading, and this is the
   value live paints on it, so the token gains a consumer rather than the row
   gaining an invented colour. Recorded in GENERATION_DECISIONS.
   ⛔ THE SIZE DOES NOT MOVE. Live measures 16 px here and the build ships 18/30 at
   >=1025; changing it would move the `/about-us/` intro band, which is inside
   B-5 at 1440 today. Weight and ink are what T-4 names and weight and ink are
   what change — a type row is not a licence to re-cut the band.
   ⛔ AND THE ON-BLUE OVERRIDE IS WRITTEN OUT, NOT LEFT TO A `var()` FALLBACK.
   `.gmg-band` SETS `--gmg-ink-here-body` on every band, so
   `var(--gmg-ink-here-body, <fallback>)` can never reach its fallback — the
   fallback arm of a var() that is always defined is dead code that reads like a
   default. The /services/ checklist is white because a rule says so. */
.gmg-checklist { display: flex; flex-direction: column; gap: var(--gmg-gap-sm); list-style: none; margin: 0; padding: 0; }
.gmg-check {
  display: flex; align-items: center; gap: var(--gmg-gap-sm);
  min-height: 44px;
  font-family: Rubik, system-ui, sans-serif;
  font-weight: 700;
  font-size: 16px; line-height: 28px;
  color: var(--gmg-brand-deep);
}
.gmg-band--blue .gmg-check { color: var(--gmg-white); }
@media (min-width: 1025px) { .gmg-check { font-size: 18px; line-height: 30px; } }
/* ⛔ THE TICK MOVED TO §15b. What was here drew a `clip-path` polygon inside a
   `currentColor` square and the ring came from the span's own border; the ring
   is now part of the authored path set, so the /services/ FILLED treatment and
   the /about-us/ OUTLINED one are one fill declaration apart. */

/* Stat tiles — the two /about-us/ counters.

   ⛔ ROUND-3 — TL ROW T-6, AND EVERY NUMBER BELOW IS MEASURED off
   w1440/about-us.png rather than taken from the row's estimate:
     tile box          398 x 145, x 979..1376, y 930..1074
     "10+" digit ink   y 985..1018 = 34 px tall  ->  48 px Albert Sans 700
                       (Albert Sans' digit height is ~0.72em; 34 / 0.72 = 47.2)
     number box        x 1017..1087 (70), left padding 38, gap to label 66
     label             TWO LINES at a 30 px pitch (y 981..994, 1011..1027),
                       breaking after "Medical" — "Years Of Medical / Experience"
   The build shipped 40/50 and a label that wrapped wherever the column let it.
   ⚠ THE LABEL WRAP IS A COMPOSITION FACT, NOT A COINCIDENCE: at 16/30 in the
   authored column the string fits on one line, so the two-line shape only
   reproduces if the label box is capped. `max-width: 15ch` breaks it after
   "Medical" and leaves "Happy / ourPatients" (the live typo, carried under R-3)
   on its own two lines exactly as live does. */
.gmg-stat {
  display: flex; align-items: center; gap: var(--gmg-gap-md);
  border-radius: var(--gmg-radius-md);
  padding: var(--gmg-gap-md);
  min-height: 44px;
}
.gmg-stat__value {
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700; font-size: 48px; line-height: 60px;
  color: var(--gmg-ink);
  flex: 0 0 auto;
}
.gmg-stat__label {
  font-family: Rubik, system-ui, sans-serif;
  font-size: 16px; line-height: 30px;   /* MEASURED: 30 px label pitch, y 981/1011 */
  color: var(--gmg-ink);
  /* ⛔ 130px, AND THE WINDOW IS MEASURED IN THE SHIPPED FACE, NOT GUESSED.
     Live breaks BOTH labels after their first phrase — "Years Of Medical /
     Experience" and "Happy / ourPatients" (w1440/about-us.png, label lines at
     y 981/1011 and 1146/1176). Reproducing that is a range, not a value:
         "Years Of Medical"            122 px   must FIT      -> cap >= 122
         "Happy ourPatients"           138 px   must NOT fit  -> cap <  138
         "Years Of Medical Experience" 209 px   (never fits)
     130 is the middle of [122, 138). ⚠ A `ch` unit was tried first and was the
     wrong instrument: 15ch computes to 152.4px in Rubik here, which is above
     BOTH thresholds, so tile 2 stayed on one line while every number looked
     plausible. The cap is a measured px because the thing it has to clear is a
     measured px. */
  max-width: 130px;
}
/* ⛔ AT <=767 THE CAP COMES OFF. The two tiles go two-up at 163 px each there
   (GENERATION_DECISIONS 12, the last 14 px of document overflow round 2 closed),
   and a 15ch cap beside a 48 px number is how that overflow comes back. The
   number steps down with it — this is an authored mobile rung, not a parity one. */
@media (max-width: 767px) {
  .gmg-stat__value { font-size: 32px; line-height: 40px; }
  .gmg-stat__label { max-width: none; line-height: 24px; }
}

/* FAQ — a real <details>/<summary>. Item 1 is open on load, exactly as live. */
.gmg-faq { display: flex; flex-direction: column; gap: var(--gmg-gap-sm); width: 100%; }
.gmg-faq__item {
  border: 1px solid var(--gmg-hairline);
  border-radius: var(--gmg-radius-sm);
  padding: var(--gmg-gap-md);
  background: var(--gmg-white);
}
.gmg-faq__q {
  font-family: 'Albert Sans', system-ui, sans-serif;
  font-weight: 700; font-size: 18px; line-height: 28px;
  color: var(--gmg-ink);
  cursor: pointer;
  display: flex; align-items: center;
  min-height: 44px;
  list-style: none;
}
.gmg-faq__q::-webkit-details-marker { display: none; }
/* ⛔ ROUND-3 — TL ROW T-7, AND THE ROW'S OWN DESCRIPTION IS WRONG ABOUT LIVE.
   T-7 reads "live = CIRCLED chevron-down / chevron-up on the open item". Zoomed
   on w1440/services.png at the open item's toggle (x 1280..1360, y 2590..2670)
   and at a closed one (y 2780..2860): there is **no ring, no disc and no
   background of any kind** — live draws a bare ~14 px chevron, ~2 px stroke,
   pointing UP on the open item and DOWN on the closed ones. Building the ring
   the row describes would have put a circle on the page that live does not have.
   ⚠ `conversion-gate-lessons` again: a reported defect is a pointer at the
   reference, not a substitute for it.
   What WAS wrong is the glyph: `\203A` is a single right-pointing guillemet — a
   sideways chevron that never turns, so "open" and "closed" differed only in
   which way it leaned. It is now a real chevron that rotates.
   ⛔ A `mask-image`, NOT a child `<svg>`, because this is a `::after` on a
   `<summary>` and a pseudo-element cannot carry a node. B-24's second tier
   asserts the mask resolves to a data URI carrying a path, for exactly the
   reason the first tier exists: an empty pseudo-element still has a box. */
.gmg-faq__q::after {
  content: "";
  margin-left: auto;
  flex: 0 0 auto;
  width: 14px; height: 14px;
  background-color: currentColor;
  -webkit-mask: var(--gmg-glyph-chevron) center / contain no-repeat;
          mask: var(--gmg-glyph-chevron) center / contain no-repeat;
  transition: rotate var(--gmg-duration-reveal, 200ms) var(--gmg-ease-standard, ease);
}
.gmg-faq__item[open] .gmg-faq__q::after { rotate: 180deg; }
.gmg-faq__a {
  font-family: Rubik, system-ui, sans-serif;
  font-size: 16px; line-height: 28px;
  color: var(--gmg-ink-body);
  margin: var(--gmg-gap-sm) 0 0;
}

/* The /services/ emergency card. ⚠ A SMALLER treatment than every other card
   title on the site — Rubik 18/30, not Albert Sans 24/34. Carried. */
.gmg-emergency {
  display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto auto;
  column-gap: var(--gmg-gap-md); row-gap: 0;
  background: var(--gmg-band-mint);
  border-radius: var(--gmg-radius-sm);
  padding: var(--gmg-gap-md);
  align-items: center;
}
.gmg-emergency__glyph { grid-row: 1 / 3; }
.gmg-emergency__glyph .gmg-icon--phone { background: var(--gmg-brand-blue); border-color: transparent; color: var(--gmg-white); width: 48px; height: 48px; }
.gmg-emergency__title {
  font-family: Rubik, system-ui, sans-serif; font-weight: 400;
  font-size: 18px; line-height: 30px; color: var(--gmg-ink); margin: 0;
}
.gmg-emergency__num {
  font-family: 'Albert Sans', system-ui, sans-serif; font-weight: 700;
  font-size: 24px; line-height: 34px; color: var(--gmg-brand-blue); margin: 0;
}

/* The /contact-us/ map band — full bleed, the ONE map on that route.
   ⛔ 525 IS A HEIGHT, NOT A RATIO, ABOVE 1025. MEASURED: the band is 525 px at
   1440 AND at 2560 (tokens-measured.json, both rungs). An `aspect-ratio` of
   1440/525 measured 933 px at 2560 — +77.7 %, the single largest delta in the
   round-2 diff, and it looked perfectly reasonable at the width it was authored
   at. Below 1025 the authored surface caps it at 16:9 / 260 px (§17). */
.gmg-mapband { padding-block: 0; }
.gmg-mapband__frame { width: 100%; height: 525px; }
.gmg-mapband__frame iframe { display: block; width: 100%; height: 100%; border: 0; }

/* The form panel — ⛔ --gmg-panel-grey #E6E6E6, NOT #F6F7FA (that is the input
   well). Two adjacent greys, one name; GENERATION_BRIEF §9 lists this among the
   six contradictions Stage C already found. */
/* ⛔ 40 / 50 / 50 — the one authored vertical padding on the entire live site,
   and it is MEASURED: LIVE's row 3a reports `pad 40/50/50/50` on a 547-wide
   outer box (LAYOUT_TRUTH §2.4, the row that is nested rather than a band). */
.gmg-panel--form { background: var(--gmg-panel-grey); padding: 40px 50px 50px; gap: var(--gmg-gap-md); }
/* ⛔ THE FORM IS INSET AGAIN INSIDE THE PANEL, AND THE CAPTURE SHOWS IT.
   w1440/contact-us.png: the panel spans 728..1374 and the FIELDS span 829..1275
   — a 446 px form inside a 646 px panel, i.e. ~100 px a side. The panel heading
   "Make an Appointment" starts at 778, i.e. 50 from the panel edge. So the panel
   carries 50 and the form carries another 50, which is how live gets a 209 px
   field and a 447 px textarea out of a 547 px inner box (contact-us.md §3).
   Without this the fields measured 263 and the textarea 547 — both correct
   arithmetic on the wrong inset. */
/* ⛔ THE INSET IS CLAMPED SO IT CAN NEVER SQUEEZE THE FORM BELOW 300 px, AND
   THAT NUMBER IS CLOUDFLARE'S, NOT OURS. The Turnstile widget's `normal` size
   has an immovable 300 px minimum — the injected `.cf-turnstile` reports
   `scrollWidth 300` whatever box it is given. A FIXED 50 px inset is correct at
   1440 (live's measured nesting, above) and too large everywhere the panel is
   narrower: MEASURED, the moment the widget started rendering, `.gmg-form` inner
   width came out **287 at 1025** and **205 at 375**, and B-15's ink tier went red
   at 360, 375, 390, 414, 430 AND 1025.
       clamp(0px, (100% - 300px) / 2, 50px)
   is the same 50 px wherever there is room for it (1440: 547 inner → 123 > 50 →
   50, so every box-truth field row is untouched) and exactly as much as fits
   where there is not. ⚠ A constraint expressed as a clamp stays true at widths
   nobody adds a media query for — which is how this one was missed. */
.gmg-form { display: block; width: 100%; padding-inline: clamp(0px, (100% - 300px) / 2, var(--gmg-gap-xl)); }
.gmg-form form { display: flex; flex-direction: column; gap: var(--gmg-stack, var(--gmg-gap-sm)); }
.gmg-field { display: flex; flex-direction: column; gap: var(--gmg-gap-xs); }
/* ⚠ CF7 WRAPS EVERY FIELD IN A `<p>`, and a `<p>` carries a 1em margin block by
   default. Five fields is ~160 px of margin nobody authored and nothing in the
   composer can see, because the markup is the plugin's. */
.gmg-form p { margin: 0; }
/* ⛔ AND AN EMPTY PARAGRAPH STILL COSTS A GAP. The `[cf7-simple-turnstile]` tag
   sits in its own `<p>`, and `gmg_site_strip_unregistered_turnstile` empties that
   paragraph without removing it — so the flex column spends a full row gap on a
   zero-height box. ⚠ `:empty` is the right test rather than a class: the moment
   Turnstile registers, the paragraph has a widget in it and reappears by itself.
   ⛔ Never `display: none` on the wrap or the widget — B-21 asserts the widget is
   PRESENT, and hiding it is the non-fix the brief rejects by name. */
.gmg-form form > p:empty { display: none; }
.gmg-form .wpcf7-form-control-wrap { display: block; }
.gmg-form .screen-reader-response, .gmg-form .hidden-fields-container { display: none; }
/* ⛔ `display: block`, NOT `flex column`. CF7 emits the required marker as a
   `<span>` INSIDE the label — `First Name <span class="gmg-req">*</span>` — so a
   column flex label puts the asterisk on a line of its own: MEASURED 53 px per
   label instead of 24, five times over, +145 px of panel nobody authored. The
   label is one line of text and it is laid out as one line of text. */
.gmg-field label,
.gmg-form label {
  font-family: 'Albert Sans', system-ui, sans-serif; font-weight: 500;
  font-size: 16px; line-height: 24px; color: var(--gmg-ink);
  display: block;
}
.gmg-req { display: inline; }
.gmg-form input[type="text"], .gmg-form input[type="email"], .gmg-form textarea {
  width: 100%;
  background: var(--gmg-field-grey);
  border: 1px solid #767D8C;            /* --gmg-field-border, 3.86:1 on the well */
  border-radius: var(--gmg-radius-xs);
  min-height: 52px;                     /* rendered 209 x 52 — and >=44, so B-16 holds */
  padding: var(--gmg-gap-sm);
  font-family: Rubik, system-ui, sans-serif; font-size: 16px; line-height: 24px;
  box-sizing: border-box;
}
/* ⛔ `height`, not `min-height`. A `<textarea>` sizes from its `rows` attribute
   and CF7's default is 10 — measured 294 px here against a 180 px target, and a
   `min-height` cannot pull a box DOWN. */
/* ⛔ `display: block`. A `<textarea>` is `inline-block` by default, which puts it
   on a text baseline and adds ~8px of descender space under it that belongs to no
   box and appears in no padding — MEASURED: the comments field measured 217
   against a 24 + 5 + 180 = 209 stack. */
.gmg-form textarea { height: 180px; min-height: 180px; display: block; }   /* rendered 447 x 180 */
/* ⛔ THE FOCUS STATE IS A REAL STATE, AND B-11 MEASURED ITS ABSENCE: *"no focus
   treatment on the first form input — I-12 measures FIVE changed properties,
   including the well lightening."* A border-colour change alone is one property
   and round 1 did not even have that reach the input (the rule below was written
   against `:focus-visible`, which a mouse click on a text field does not always
   trigger; `:focus` is what a form field needs). Live's own focus changes
   `borderColor`, `backgroundColor` and `outlineColor` together — three — and the
   authored ring (§6) adds `outlineStyle` and `boxShadow`. That is the five.
   ⚠ tokens a11y.focusRing.$assertionTrap: assert `outlineStyle`, NEVER
   `outlineWidth` — at rest the computed value is `none` with a non-zero width
   still set. */
.gmg-form input:focus,
.gmg-form textarea:focus,
.gmg-form input:focus-visible,
.gmg-form textarea:focus-visible {
  border-color: var(--gmg-brand-blue);
  background-color: var(--gmg-white);          /* the well lightens — I-12 */
  outline: 3px solid var(--gmg-brand-deep);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--gmg-white);
}

/* /styleguide — the ADR-026 gate artifact, not a parity surface. */
.gmg-swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--gmg-gap-md); list-style: none; margin: 0; padding: 0; }
.gmg-swatch { display: flex; align-items: center; gap: var(--gmg-gap-sm); font-family: Rubik, system-ui, sans-serif; font-size: 16px; line-height: 28px; }
.gmg-swatch__chip { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; border: 1px solid var(--gmg-hairline); border-radius: var(--gmg-radius-xs); }
.gmg-swatch--carried .gmg-swatch__chip { width: auto; padding-inline: var(--gmg-gap-md); }

/* The 404 stage — ⛔ 1140, not 1314, and it opens directly onto white with a
   LEFT-aligned h1. It does NOT use the page-title band and must not be tidied
   into consistency (LAYOUT_TRUTH §2.5). */
.gmg-404 { padding-block: var(--gmg-rhythm-tight); }
/* ⛔ ROUND-4 — THE STACK IS 16, AND 16 IS A MEASUREMENT.
   Live's 404 spends its stage as `h1` 108..208, body `p` 224..254 — a **16 px**
   gap, and the stage element (`main.site-main`) is 146 px tall. With the body
   line restored by owner ruling (D-6, 2026-09-11) the build's stack is live's
   three-part one plus the ONE authored row:
       h1 100 + 16 + body 30 + 16 + link 44  =  **206**
   ⚠ The PR-body ledger predicted "~206" when it filed D-6 as the owner's call.
   The parity half of that — 100 + 16 + 30 = 146 — is live's stage EXACTLY.
   ⛔ `--gmg-gap-sm` (10) was the round-3 value and it was chosen to land the old
   two-part stack on 164; it is not live's leading and it is not kept. */
.gmg-404__stage { display: flex; gap: 16px; }
/* ⛔ SUPERSEDED BY THE BLOCK ABOVE — kept so the change reads as a diff.
   `sm`, NOT `lg`, AT EVERY WIDTH — and the mobile half is a measured target,
   not a tidy-up. `pages/404.md`'s AUTHORED 375 register gives the stage as
   **≤ 260** and the document as **≤ 1456** (1.4 × the 1040 desktop). With a 30 px
   stack the stage measured **264** and the document **1464** — over both, by 4
   and by 8.
   ⚠ THE MISS ONLY APPEARED WHEN THE DESKTOP GOT CORRECT. Round 2's document
   ratio cleared its target against an INFLATED denominator (1286 instead of
   1040, audit F-1); fixing the heading moved the denominator and the authored
   mobile bound started to bite. ⛔ That is the F-3 shape from `GENERATION_DECISIONS`
   10 in miniature — a ratio row is only as honest as the number underneath it —
   and the right response is to meet the bound, not to re-derive it from the
   build. Measured after: stage **244**, document **1444**. */

/* ⛔ ROUND-3 — THE 404's PARITY REGISTER, AND IT IS **ZERO** (audit F-1).
   `pages/404.md` §MEASURED gives the stage as y 104..267 — **164 px** — sitting
   directly under the 100 px header wrapper, with a 1040 px document. Live spends
   that 164 as `h1` 100 + body 28 + ~36 of leading and NO band padding at all;
   the band's own register is 0.
   The build shipped `--gmg-rhythm-tight` (64 at >=1025) top AND bottom, which
   with the invented two-line heading produced 64 + 284 + 64 = **412 px**, +248
   on a 164 px target, and a 1286 px document against 1040.
   THE ROUND-3 DECOMPOSITION, which landed on the same 164:
       h1 100  +  stage gap 10  +  the link-home row 44  +  its margin 10  =  164
   ⛔ AND IT LANDED THERE BY SPENDING LIVE'S BODY LINE ON THE AUTHORED LINK.
   Round 3 read *"live's body line pays for it at 28 + leading — so the deviation
   is visible in WHAT is on the page and invisible in HOW TALL it is"*, filed the
   dropped line as deviation **D-6**, and sent it to the owner as *"the owner's
   call, not a builder's"*. ✅ **OWNER RULING 2026-09-11: CARRY IT.** Both the
   body line and the link home ship, so the 164 no longer holds and is not made
   to: the stage is **206**, of which 146 is live's own stage and 60 is the one
   authored row. ⚠ The parity number did not change — what changed is that the
   build stopped paying for an addition with a deletion.
   ⛔ >=1025 ONLY. Below it the authored surface keeps a real register — a 404
   flush against the header on a phone reads as a broken page, and the 375 target
   is an upper bound (<=260), not a parity number. */
@media (min-width: 1025px) {
  .gmg-404 { padding-block: 0; }
}
/* ⛔ THE h1 BOX IS 1140 — THE STAGE'S FULL WIDTH — AND IT HAS TO BE SAID.
   `404.md` §Live content gives the box as **1140 × 100**, and the stage is a
   flex column with `align-items: flex-start`, so its children shrink-to-fit: the
   heading measured 978 wide on a 1140 stage. ⚠ Invisible in a screenshot, because
   a left-aligned heading looks identical either way — and box-truth's `notFoundH1`
   row asserts the WIDTH as well as the height for exactly that reason.
   ⛔ The link home is NOT stretched: it is an anchor, and a 1140-wide click
   target is a different defect from a 978-wide heading.
   ⚠ ROUND-4: the body line takes the same stretch, for the same reason and with
   the same evidence — live's `p` box is 1140 × 30, the stage's full width. */
.gmg-404__stage > .gmg-hero-title,
.gmg-404__stage > .gmg-404__body { align-self: stretch; }

/* ⛔ ROUND-4 — THE BODY LINE'S ROLE, AND IT IS AN EXISTING ONE.
   `404.md` §Live content and `content-source/content/pages/404.md` BOTH say
   "Rubik 16 / lh 28". ⚠ Measured on live at 1440 it is **18 / 30** — the ≤1024
   band of the `body` role quoted as though it were the 1440 value. The line is
   ordinary body copy and joins `tokens.json type.body` (16/28 ≤1024, 18/30
   ≥1025), exactly the way the h1 joins `heroTitle` rather than acquiring a face
   of its own. ⛔ Nothing here restates the numbers: `.gmg-body` already carries
   the role and this class only places the element. */
.gmg-404__body { margin: 0; }


/* =============================================================================
   §17. THE <=1024 AUTHORED SURFACE.

   ⛔ NOTHING HERE IS MEASURED FROM LIVE, AND NOTHING HERE MAY BE COMPARED WITH
   IT. LAYOUT_TRUTH §5 / §7.9: *"There is no measured mobile truth on this
   engagement and there is not meant to be: the legacy mobile render is a defect
   ledger, not a target, and ⛔ no battery row may cite it as a comparison."*
   rhythm-diff refuses to diff below 1025 for the same reason.

   The floor is F-3 (B-17): every route's document at 375 must be <= 1.4x its own
   1440 height. The three levers, in order of effect (home.md §375):
     1. everything stacks to ONE column, MEDIA ALWAYS ABOVE ITS COPY;
     2. the photo caps — aspect-ratio 4/3, width 100%, <=45vh;
     3. the section padding ladder (§13), which is what makes live's 1.79x a
        spacing problem rather than a content problem.

   ⚠ RE-MEASURE THE PAGE HEIGHT AFTER ANY TYPE CHANGE. With line-height fixed on
   both sides, prose height scales roughly by the SQUARE of the type step.
   ============================================================================= */

@media (max-width: 1024px) {
  /* 1 — everything is one column, media above copy. ONE rule, no exceptions. */
  .gmg-split,
  .gmg-trio3,
  .gmg-grid--3,
  .gmg-grid--3x2,
  .gmg-grid--2,
  .gmg-footer__cols { grid-template-columns: minmax(0, 1fr); }
  .gmg-split__media { order: -1; }
  .gmg-split__copy  { order: 0; }
  .gmg-trio3__col--a, .gmg-trio3__col--c { padding-top: 0; }
  /* The derived desktop stacks do not survive the collapse: a 57 px gap between
     a heading and a photograph is a desktop proportion, and at 375 it is a hole. */
  .gmg-band { --gmg-stack: var(--gmg-gap-md); }

  /* Chrome. The header is 64 px and the logo takes a 44 px cap height — 44 x the
     logo's own 3.553 aspect is 156 px of width (_global-chrome.md, authored).
     ⛔ AND THE SHELL IS STILL A ROW. §2 declares `flex-direction: row` on
     `.gmg-header__shell` INSIDE `@media (min-width: 1025px)` — correct for the
     877 px desktop shell, and below 1025 the element falls back to
     `.gmg-container`'s `column`, which stacks the logo ABOVE the hamburger:
     MEASURED 122 px of header at 375 against a 64 px target, on all six
     surfaces. The row is not a desktop property; only its 877 px width is. */
  .gmg-header__logo { width: 156px; }
  .gmg-header__shell {
    flex-direction: row; align-items: center; justify-content: space-between;
    gap: var(--gmg-gap-sm);
    /* ⛔ THE GUTTER IS PADDING BELOW 1025, NOT A MARGIN. §1c: "BELOW 1025 it
       DOES take the content container — 540 cap / 15 px at <=767, 720 / 15 at
       768-1024". `.gmg-container`'s `min(100% - 30px, cap)` produces the right
       INNER width by shrinking the box, and B-4 asserts the PADDING as well,
       because that is how live spends the 30 px. MEASURED: `side padding 0/0,
       expected 15/15` at all eight sub-1025 rungs. The cap gains the 30 px back
       so the content box still lands on the ladder. */
    width: min(100%, 570px);          /* 540 cap + 2 x 15 gutter */
    padding-inline: 15px;
  }
  @media (min-width: 768px) {
    .gmg-header__shell { width: min(100%, 750px); }   /* 720 cap + 2 x 15 */
  }

  /* 2 — the photo caps. Every photograph on this site is >=520 px tall at
     desktop and taller still on a phone (M-8). */
  .gmg-img--sm, .gmg-img--md, .gmg-img--square,
  img.gmg-img--sm, img.gmg-img--md, img.gmg-img--square {
    aspect-ratio: 4 / 3; object-fit: cover; max-height: 45vh;
  }
  .gmg-card--photo img.gmg-img { aspect-ratio: 16 / 9; max-height: 45vh; }

  /* 3 — the trio collapses to ONE photo with the badge card docked to its
     corner (M-8). ⛔ Three interleaved photos in a 345 px column is a scroll,
     not a layout. */
  .gmg-trio { min-height: 0; padding-bottom: 0; display: block; position: relative; }
  .gmg-trio .gmg-trio__a, .gmg-trio img.gmg-trio__a { display: none; }
  .gmg-trio .gmg-trio__b, .gmg-trio img.gmg-trio__b {
    position: static; width: 100%; aspect-ratio: 4 / 3; max-height: 45vh;
  }
  /* ⛔ `top` AND `min-height` MUST BE RESET HERE, NOT JUST `left`/`bottom`.
     The desktop rule now docks the card with `top: 334px; min-height: 214px`
     (measured off live's trio). Those are INHERITED into this block, which
     previously overrode only left/bottom — and an absolutely positioned box with
     BOTH `top` and `bottom` set and `height: auto` is stretched between them, so
     the mobile card would have been pulled to whatever 334px-from-the-top to
     0-from-the-bottom happens to span. ⚠ This is the specificity-leak class that
     put 151/57/14/63px of overflow at 375 earlier this round: making the desktop
     more correct silently redefined the mobile surface. B-15 and B-17 are the
     guard, and they are re-run after this. */
  .gmg-badgecard {
    position: absolute; left: 0; top: auto; bottom: 0;
    width: min(100%, 240px); min-height: 0;
  }

  /* The footer's map: 16:9, width 100%, capped 240 px, with the address in real
     text beside it (_global-chrome.md, authored). */
  .gmg-footer__mapframe { height: auto; aspect-ratio: 16 / 9; max-height: 240px; }
  .gmg-footer { padding-block: var(--gmg-rhythm-tight); }
  .gmg-footer__mark { max-width: 240px; }

  /* The map band caps too — live's 525 px of map on a phone is 936 px of map
     across the route once the footer's is counted. */
  /* ⛔ `height: auto` FIRST — §16 sets an explicit 525 px height above 1025 and
     an `aspect-ratio` cannot override a used `height`. */
  .gmg-mapband__frame { height: auto; aspect-ratio: 16 / 9; max-height: 260px; }

  /* Panels and cards lose their desktop padding. */
  .gmg-panel { padding: var(--gmg-gap-lg); }
  .gmg-panel--form { padding: 24px; }   /* the 375 register: "panel padding 50 -> 24px" */
}

/* ---- 768 – 1024: THE TABLET BAND IS TWO COLUMNS, NOT ONE -------------------
   ⚠ The one-column rule the package states is for ≤767 — LAYOUT_TRUTH §5: *"The
   stack order AT ≤767 is ALWAYS media-above-copy"* — and `about-us.md` spells the
   tablet band out separately: *"768 – 1024: two columns, two photos, at their
   natural offsets."* Collapsing the CARD GRIDS to one column across the whole
   768–1024 band is what pushed /services/ to 1.53 x its own desktop height at
   768 and /about-us/ to 1.41, both over the F-3 cap, while 375 sat at 1.33.
   ⚠ The 720 px container cap binds across this entire band (720 against a 1024
   viewport is 152 px of gutter a side), so two columns here are ~350 px each —
   wider than the 345 px column at 375, not narrower. */
@media (min-width: 768px) and (max-width: 1024px) {
  .gmg-grid--3, .gmg-grid--3x2, .gmg-grid--2,
  .gmg-trio3, .gmg-footer__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The brand column keeps the full width — its paragraph is the footer's only
     prose and a 350 px measure turns six lines into eleven. */
  .gmg-footer__col--brand { grid-column: 1 / -1; }
}

@media (max-width: 767px) {
  /* ⛔ /about-us/ intro: the stat tiles go SIDE BY SIDE, not stacked. They are
     short, and two 50% tiles are ~120 px where two stacked tiles are ~260
     (about-us.md §2 collapse). */
  .gmg-trio3__col--c { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gmg-gap-md); }
  .gmg-trio3__col--c > :not(.gmg-stat) { grid-column: 1 / -1; }
  /* ⛔ AND THE TILE ITSELF HAS TO STACK. Side by side, each tile is 163 px wide;
     a 40 px "10+" beside the word "Experience" needs 190 px of min-content and
     MEASURED 14 px of horizontal document overflow on this route — the only
     overflow left anywhere after the split collapse was fixed. The tile is the
     one component the two-up treatment makes narrower than its own content. */
  .gmg-stat { flex-direction: column; align-items: flex-start; gap: var(--gmg-gap-xs); }
  .gmg-stat__label { min-width: 0; }

  /* /services/: six cards become six COMPACT ROWS — icon and pill on one line,
     title, two lines of body. ~1250 px instead of 2175, and it deletes nothing. */
  .gmg-card--service { padding: var(--gmg-gap-md); gap: var(--gmg-gap-sm); }
  .gmg-card--service .gmg-card__icon, .gmg-card--service img.gmg-card__icon { width: 44px; height: 44px; }

  /* ⚠ 767 IS THE TALLEST SUB-DESKTOP RUNG, ON LIVE AND ON THE BUILD, AND IT IS
     WHY THESE LEVERS SIT AT ≤767 RATHER THAN AT ≤479. LAYOUT_TRUTH §5 says it in
     as many words — *"767 is the tallest rung on `/`, taller than 360. The mobile
     ladder is NOT MONOTONIC — a builder who spot-checks 375 and 1440 and
     interpolates will miss it"* — and the build reproduced exactly that: with
     these levers scoped to ≤479, 375 measured 1.33x and 767 measured 1.43x, over
     the F-3 cap at the one width nobody looks at. Only the `bandTight` PADDING
     rung is ≤479, because that is the rung the spec pins.
     ⛔ Every lever here is SPACING. None deletes a card, a paragraph or an
     accordion item, and item 1 of the FAQ stays open. */
  .gmg-grid  { gap: var(--gmg-gap-sm); }
  .gmg-card  { gap: var(--gmg-gap-sm); padding: var(--gmg-gap-md); }
  .gmg-card--service { gap: var(--gmg-gap-xs); }
  .gmg-split { gap: var(--gmg-gap-md); }
  .gmg-panelrow, .gmg-prow { padding-top: var(--gmg-gap-md); }
  .gmg-prows { gap: var(--gmg-gap-md); }
  .gmg-checklist { gap: 0; }
  .gmg-faq { gap: var(--gmg-gap-xs); }
  .gmg-faq__item { padding: var(--gmg-gap-sm) var(--gmg-gap-md); }
  .gmg-panel { padding: var(--gmg-gap-lg); }
  /* ⛔ `:not(.gmg-band--flush)`. `.gmg-band--flush` is ONE class and so is
     `.gmg-band`; the mobile re-statement is declared later and therefore wins,
     which put 40 px of padding on the two bands whose whole definition is that
     they have none. MEASURED: the /contact-us/ map band read 291 px against a
     ≤260 authored target, and 80 of that was this. */
  .gmg-band--tight, .gmg-band:not(.gmg-band--flush) { padding-block: var(--gmg-rhythm-band); }
  .gmg-container { gap: var(--gmg-gap-lg); }

  /* ⚠ THE COPYRIGHT BAR IS FULL BLEED AND THE SPEC CONTRADICTS ITSELF HERE.
     `_global-chrome.md`'s 375 register asks for BOTH "Copyright 68 — parity" and
     "Copyright link 375 x 19 -> 44 px line box". A 44 px box inside 20/20 padding
     is 84 px; 68 px leaves 28. The two cannot both hold, and the 20/20 register
     is the one measured non-zero vertical register on the site. ⛔ THE TAP TARGET
     WINS — it is an accessibility floor (B-16) and the bar is 84, not 68.
     Recorded as GENERATION_DECISIONS F-7 rather than resolved by picking quietly.
     The line is held to ONE line so the bar does not also wrap to 100. */
  .gmg-copyright__inner { width: 100%; padding-inline: var(--gmg-gap-md); }
  .gmg-copyright__link { font-size: 14px; line-height: 28px; }
}

@media (max-width: 479px) {
  /* bandTight at <=479 on the dense bands — the register the 375 targets name.
     ⛔ Again `:not(.gmg-band--flush)`: the banner and the map band pad internally. */
  .gmg-band:not(.gmg-band--flush), .gmg-footer { padding-block: var(--gmg-rhythm-tight); }
  .gmg-panel { padding: var(--gmg-gap-md); }
}

/* =============================================================================
   §18. THE CLASSES THE COMPOSITION EMITS THAT NOTHING ELSE HAD CLAIMED.

   P-1 asserts that every `gmg-*` class the composition RENDERS resolves to at
   least one non-empty rule, and it found nineteen that did not. ⚠ Most were
   harmless hooks — and one of them, `gmg-hamburger__bars`, was an invisible
   control that every other row in the battery passed. A class with no rule is
   not automatically a defect; a class with no rule is UNCHECKED, and the row is
   right to refuse to tell them apart.
   ============================================================================= */

/* The last four the composition emits. ⚠ `gmg-form__turnstile` and
   `gmg-formsubmit` come from `content-source/forms.json` — the form markup is
   DB-bound, so these classes arrive from the form contract rather than from a
   composer, and P-1 sees them all the same. */
.gmg-form__turnstile { display: block; margin-block: var(--gmg-gap-md); min-height: 0; }
.gmg-formsubmit      { display: flex; align-items: center; }

/* =============================================================================
   ⛔ THE TURNSTILE WIDGET ON A PHONE — FOUND THE MOMENT THE GATE WENT GREEN.

   Cloudflare's `normal` widget has a HARD 300 px minimum: the injected
   `.cf-turnstile` reports `scrollWidth 300` whatever box you give it. For two
   rounds the widget never rendered (`cfturnstile_tested` was `1`, which the
   plugin's gate treats as neither set nor unset), so nothing measured it.
   MEASURED at 375 the moment it did render:

       .gmg-panel--form   345 wide, padding 20
       .gmg-form          305 wide, padding-inline **50**      <- the leak
       form / .cf-turnstile  205 wide, scrollWidth **300**
       document           scrollWidth 385 > clientWidth 375    <- B-15 RED

   ⚠ TWO DEFECTS, AND THE FIRST ONE WAS ALREADY THERE.
   (1) `.gmg-form`'s 50 px inset is a MEASUREMENT OF LIVE'S DESKTOP NESTING —
       §12 derives it from live's 446 px form inside a 646 px panel — and it was
       applying at every width. At 375 that left a 205 px form inside a 345 px
       panel: every field 100 px narrower than the panel it sits in, on the one
       surface where width is scarce. ⛔ Nothing caught it because nothing
       overflowed while the widest child was a text input, which shrinks. The
       widget does not shrink, and that is the only reason this is visible now.
       ⚠ *An authored-surface defect can hide behind every element being
       flexible; it surfaces the day one element is not.*
   (2) Even with the inset gone the panel gives 305 at 375 and only 290 at 360,
       against the widget's immovable 300.

   The fix is both halves, and neither is a media query about this widget:
   (1) §12's `.gmg-form` inset is CLAMPED so it can never take the inner width
       below 300 — one rule, true at every width, including the 1025 rung nobody
       would have written a query for;
   (2) below 390 the panel itself is narrower than 300, so the widget is clipped
       AND scaled. ⛔ `overflow: hidden` alone would clip Cloudflare's branding
       out of a security control, so the scale comes first and the clip is only
       the belt.
   ⚠ The widget is an AUTHORED ADDITION — live's form carries no visible
   challenge at all (R-9) — so there is no live measurement to be wrong about
   here; this is the authored ≤1024 surface, judged against the spec.
   ============================================================================= */
@media (max-width: 389px) {
  .gmg-form__turnstile { overflow: hidden; }
  .gmg-form__turnstile .cf-turnstile {
    display: block;
    width: 300px;                  /* the widget's own minimum, stated rather than fought */
    transform: scale(0.93);        /* 300 -> 279, inside the 290 a 360 px viewport gives */
    transform-origin: left top;
  }
}
/* The 404's way home. ⚠ AUTHORED — "the added link home costs a 44px row"; the
   legacy 404 offers no way back at all.
   ⛔ ROUND-4: the extra `margin-top` is GONE. It existed to make round 3's
   two-part stack total 164 (10 gap + 10 margin = live's ~20 of leading). With
   the body line restored the stack is uniform — one 16 px gap between all three
   rows, which is live's own measured leading — and a second spacing mechanism
   on one child would put the stage 10 px off its re-derived 206.
   ⚠ AND THE RULE DID NOT BECOME EMPTY — P-1 CAUGHT THAT IT NEARLY DID. Deleting
   the margin left `.gmg-404__home` rendering with no rule of its own, and
   CLASS-HAS-CSS went red on it immediately (155 classes, 1 unstyled). What
   replaces the margin is the declaration §16 above already argues for in prose
   and had been leaving to the stage's default: ⛔ the link home is NOT stretched.
   A 1140-wide click target is a different defect from a 978-wide heading, and
   the two siblings beside it ARE stretched — so the exception is stated on the
   element it applies to instead of being inferred from an absence. */
.gmg-404__home { align-self: flex-start; }
/* /styleguide's carried-exception paragraph. It has to READ as prose beside the
   swatch, because the exception is carried on a RULING and not on a threshold. */
.gmg-carried__note   { max-width: 72ch; }

/* The drawer's CTA is the header CTA's twin and fills the panel's width. */
.gmg-drawer__cta { width: calc(100% - 2 * var(--gmg-gap-md)); margin-inline: var(--gmg-gap-md); justify-content: center; }

/* The footer's address column and its mail link. The address is NOT a link on
   live and is not made one here; the mail row is. */
.gmg-footer__col--address { gap: var(--gmg-gap-sm); }
.gmg-footer__mail { word-break: break-word; }

/* Each half of a two-up form row. ⛔ Both go 1-up at <=767 — the grid above owns
   that; this owns the cell. */
.gmg-formrow__half { min-width: 0; }

/* The wrapper Bricks renders around a `template` element. It must not introduce
   a box of its own between the page and the shared band. */
.gmg-pagetitle__ref { display: contents; }

/* ⚠ `--media-first` is a COMPOSITION-ORDER marker and it now says so in CSS
   rather than only in a comment: the composer writes the media column first in
   the tree, and this is the rule that makes that explicit at desktop.
   ⛔ AND IT IS SCOPED TO DESKTOP, BECAUSE UNSCOPED IT DELETED THE ABOUT BAND'S
   PHOTOGRAPH ON EVERY PHONE.
   `grid-column: 2` does not merely place a child — it GUARANTEES a second track
   exists. §17's collapse sets `.gmg-split { grid-template-columns: minmax(0,1fr) }`
   at ≤1024, and this rule then forced an implicit second column straight back in.
   MEASURED at 375 on `/`, before the fix:
       .gmg-split  grid-template-columns: 0px 315px      <- TWO tracks
       .gmg-trio   0 x 0        .gmg-trio__b (the photo)  0 x 0
   The media track had no intrinsic width (its image is `width: 100%`, which
   resolves against a track that is sizing to its content — a circular
   constraint that settles at zero), so the About band rendered with NO PHOTO and
   NO BADGE CARD at all, against M-8's "collapses to ONE photo with the badge
   card docked to its corner".
   ⛔ NOT ONE BATTERY ROW COULD HAVE CAUGHT THIS, and that is the part worth
   keeping: a 0x0 image is not VISIBLE, so P-8 skips it; it overflows nothing, so
   B-15 passes; and a missing photo makes the page SHORTER, so B-17's ≤1.4x
   passes more easily. It was found by cropping the 375 review screenshot and
   looking at the band. ⚠ Same shape as the specificity regression earlier this
   round: a rule added for the DESKTOP quietly redefined the mobile surface. */
@media (min-width: 1025px) {
  .gmg-split--media-first .gmg-split__media { grid-column: 1; }
  .gmg-split--media-first .gmg-split__copy  { grid-column: 2; }
}

/* ⚠ THE ONE CONTENT SECTION ON THE SITE WITH NO SIDE PADDING AT ALL —
   LAYOUT_TRUTH §2.2 records `/services/`'s FAQ container as `padding: 0/0/0/0`.
   Recorded as a real measurement, not a rounding of 15. */
.gmg-band--flushx > .gmg-container { padding-inline: 0; }

/* The /about-us/ intro band. Its three-column composition is `.gmg-trio3`; this
   is the band that carries it, and it takes the white field. */
.gmg-band--intro { background-color: var(--gmg-white); }

/* The banner. ⛔ 475 px, AND IT IS WIDTH-INVARIANT ABOVE 1025 — measured, not
   assumed: Σ sectionPadding at 1440 is 4706 against docScrollH 5183 (+477) and
   at 2560 it is 4546 against 5023 (ALSO +477). The one row in the whole layout
   table with no Source A container, so there is no content to derive a height
   from; it is a parity constant with its arithmetic beside it, exactly like the
   877 header shell and the 1140 stage in §2. */
.gmg-banner { min-height: 320px; padding-inline: var(--gmg-gap-md); align-items: flex-start; justify-content: center; }
@media (min-width: 1025px) { .gmg-banner { min-height: 475px; } }
/* The heading's BOX is the viewport less a 20 px gutter a side — 1400 at 1440,
   2520 at 2560 — not the 1314 column. */
.gmg-banner .gmg-display { width: 100%; text-align: left; }
/* ⛔ ROUND-4 — AND THE GLYPH RUN IS CENTRED IN THAT BOX.
   `home.md` §1 reads *"left-aligned, hanging off the left edge of the content
   column"*, and at 1440 that is almost true by accident: live's run is 1377 px
   wide in a 1400 px box, so centring moves it by (1400 − 1377) / 2 = 11 px and a
   human reading a 1440 raster cannot see the difference. ⛔ AT 2560 THE SAME BOX
   IS 2520 AND THE SAME RUN IS STILL 1377 — the error is 571 px, and it is the
   first thing the owner saw. Measured on live: `e-heading-base` is
   `display: flex` with `justify-content: center`; run x 31 (cx 720) at 1440 and
   x 591 (cx 1280) at 2560 — the run's centre IS the viewport's centre at both.
   ⚠ A BOX ASSERTION CANNOT SEE THIS. box-truth's `displayH1` row asserted the
   1400 width and passed on both sides while the ink sat 571 px apart, because a
   full-width box says nothing about where the ink inside it is. P-2 now measures
   the RUN (a Range over the text) against the viewport centre at 1440/1920/2560.
   ⛔ >=1025 only: below it the clamped heading wraps and the authored surface
   keeps its own left-aligned register. */
@media (min-width: 1025px) {
  .gmg-banner .gmg-display { text-align: center; }
}
