/* HBA of Dothan — authored lane, PAGE-BODY FOUNDATION.
 *
 * Fields, section rhythm, the type ramp, the structural primitives (grid/split/
 * actions/list) and the site-wide focus ring. components.css and hero.css load
 * AFTER this file and build on it.
 *
 * PROVENANCE. Every value here is measured. In order of authority:
 *   1. docs/design/measurements/observed-tokens.json  — the mechanical sweep
 *      (componentInk signatures + counts, 10 routes x 3 widths)
 *   2. docs/design/measurements/tokens-measured.json  — the ramp at 11 widths
 *   3. hba-capture/screenshots/w{390,640,768,1024,1280,1440}/*.png — pixel-probed
 *      for column edges, gutters and intra-section bands
 *   4. docs/design/tokens.json / STYLE_GUIDE.md / LAYOUT_TRUTH.md
 * Where (4) disagreed with (1)-(3), the measurement won and the disagreement is
 * called out inline. Three such disagreements are recorded below — search
 * "TOKENS.JSON DISAGREES".
 *
 * ------------------------------------------------------------------ CASCADE ---
 * SPECIFICITY CONTRACT — this file must LOSE to components.css and hero.css.
 *
 * .hba-body (181 uses) and .hba-heading (99) sit on EVERY paragraph and EVERY
 * heading on the site, component internals included: an event chip's month, a
 * stat's value and a card's title all carry .hba-body / .hba-heading. So the
 * foundation must be beatable by a single component class in a later file.
 *
 * Rule: every context-dependent selector here is written so its specificity is
 * exactly (0,1,0) — the ancestor half is wrapped in :where(), which contributes
 * zero. A component's `.hba-eventchip__month { color: ... }` is also (0,1,0) but
 * lives in a later stylesheet, so it wins on source order. That is deliberate.
 *
 *   :where(.hba-field-charcoal, .hba-field-navy) .hba-body   ->  (0,1,0)
 *   .hba-section__inner > :where(.hba-heading)               ->  (0,1,0)
 *   .hba-heading:where(h2)                                   ->  (0,1,0)
 *
 * The ONLY selector here above (0,1,0) is the focus ring, which is an
 * accessibility floor and is meant to be hard to lose.
 *
 * BRICKS, MEASURED RATHER THAN ASSUMED (brief trap 4). What Bricks actually
 * emits against the elements this file touches, read out of the rendered pages:
 *   - `.brxe-container{align-items:flex-start;display:flex;flex-direction:column;
 *     width:1100px}` in frontend-layer.min.css, at (0,1,0), enqueued BEFORE
 *     body.css. site.css's ladder already handles the width half; the
 *     align-items half is handled below and a plain class rule beats it on
 *     source order.
 *   - Per-element id rules at (1,0,0), and they are used for exactly three
 *     things: section padding (all 40 sections), a card hairline
 *     `border:1px solid #f3f4f6`, and the home hero's background image.
 *     THE FIRST OF THOSE OWNS THE SECTION RHYTHM — see the next block.
 *   - Nothing at all on type, colour, text-align, grid or flex for any hba-*
 *     element. Those are uncontested at (0,1,0).
 * No !important anywhere in this file, and none is needed.
 *
 * ------------------------------------------------------------------- UNITS ---
 * ⛔ EVERY LENGTH IN THIS FILE IS px, DELIBERATELY. NEVER TRANSCRIBE A LIVE
 * VALUE AS rem HERE.
 *
 * Bricks' frontend-layer.min.css ships `html { font-size: 62.5% }`, so the root
 * on THIS build is 10px. The live Manus site ran at a 16px root. A live value
 * copied across as rem therefore renders at 0.625x — `2.2rem` becomes 22px where
 * it should be 35.2px — and it looks like a plausible size, so it survives any
 * "is the rule present" check. Swept 2026-08-20: this file contains zero rem,
 * em, ch, vw or vh lengths. The only relative lengths are `max-width: 100%` and
 * `width: 100%` on .hba-img, which are percentages of the parent box and are
 * unaffected by the root.
 *
 * Tailwind values quoted in the comments below are decoded to px against the
 * LIVE 16px root before being written: text-sm = 14/20, text-lg = 18/28,
 * text-2xl = 24/32, text-3xl = 30/36, text-4xl = 36/40, gap-4 = 16, gap-6 = 24,
 * gap-8 = 32, gap-12 = 48, gap-14 = 56, mb-3 = 12, mb-10 = 40, mt-8 = 32,
 * max-w-xl = 576, max-w-2xl = 672, max-w-3xl = 768, max-w-4xl = 896,
 * leading-snug = 1.375, leading-relaxed = 1.625.
 */

/* ================================================================== fields ===
 * Section field colours. tokens.json colour block; counts are painted
 * occurrences in the sweep. amberFill #fe9a00 is the FILL amber and paints
 * exactly one full-bleed section on the site (/about, the closing CTA).
 * amberInk #ffb900 is the INK amber and never paints a section field.
 */
.hba-field-charcoal  { background-color: #0d1b2a; }
.hba-field-navy      { background-color: #1e3a5f; }
.hba-field-white     { background-color: #ffffff; }
.hba-field-offWhite  { background-color: #f5f7fa; }
.hba-field-amberFill { background-color: #fe9a00; }

/* ========================================================= section rhythm ===
 * ⛔ THE RHYTHM IS NOT THIS FILE'S TO SET, AND IT IS ALREADY CORRECT.
 *
 * The composition sets every section's padding in the Bricks element settings,
 * which Bricks emits as PER-ELEMENT ID RULES AT (1,0,0) in the page head:
 *
 *     #brxe-eb2fa2 {padding-top: 56px; padding-bottom: 56px}
 *
 * Verified by reading those rules out of all ten rendered routes: 40 of 40
 * sections carry one, and the registers they carry match LAYOUT_TRUTH §4 row for
 * row — 0/0 x1, 32/32 x2, 56/56 x4, 64/64 x21, 80/80 x3, 96/96 x2, 128/64 x9.
 * NOTHING at class specificity can beat (1,0,0), so every rule below is a
 * FALLBACK that fires only if a section ever loses its Bricks setting. They are
 * kept because a section with no padding at all is a much worse failure than a
 * section with the wrong register — and because a page hero without its 128px
 * top slides under the 101px fixed header.
 *
 * Two consequences worth stating plainly:
 *   1. The "no responsive vertical padding" trap cannot be violated from this
 *      file. It could only ever be violated in the composition.
 *   2. If a register is ever wrong, THE FIX IS IN THE BRICKS ELEMENT SETTING,
 *      not here. Chasing it in CSS will not work and will look like it should.
 *
 * The registers are still width-invariant and must stay so: they are
 * byte-identical at 390, 768 and 1440 on all ten routes (re-measured after this
 * file landed). The @media block at the foot of this file changes grid columns
 * ONLY.
 */

/* Fallback default: 64/64, 21 of the 40 sections. */
.hba-section { padding-block: 64px; }

/* Fallback for the two hero registers. .hba-pagehero and .hba-hero belong to
 * hero.css; these are written at :where()-reduced specificity — (0,1,0), from
 * .hba-section alone — so a plain .hba-pagehero rule in the later file wins
 * outright. This file never competes for a hero class, it only backstops the
 * header clearance. */
.hba-section:where(.hba-pagehero) { padding-block: 128px 64px; }
.hba-section:where(.hba-hero)     { padding-block: 0; }

/* Sponsor sections whose data is owed — both measure 64/64 on live
 * (LAYOUT_TRUTH: /home §9, /events §4). Same fallback status as above.
 *
 * HBA-D-11 says of these sections: "the section does not render until HBA
 * supplies sponsors." That is a COMPOSITION decision, not a CSS one — hiding
 * them here would (a) still leave WEBOPS-PLACEHOLDER in the HTML, so gate S-1
 * would not actually pass, and (b) drop the re-authored closing line on /events
 * that events-archetype.md specifically fixes. Left rendering; escalated to the
 * team lead. */
.hba-section--empty { padding-block: 64px; }

/* Two rhythm facts found while checking the above, neither of them fixable here:
 *   - The 48/48 register has ZERO consumers in this build. Its only live owner
 *     was /calendar's Outlook block, which the composition drops (OPEN O-4).
 *   - /about has drifted from the capture independently of CSS: the composition
 *     emits 8 sections where LAYOUT_TRUTH §4 records 7 (a stats section is
 *     inserted at position 4), and the fields of "Our Core Values" (offWhite ->
 *     white) and "From Local Roots" (white -> charcoal) are flipped relative to
 *     the capture. The field classes are in the markup; changing them is a
 *     composition act. */

/* =========================================================== section inner ===
 * .hba-section__inner sits on .brxe-container, so the container ladder in
 * site.css already gives it its width and side padding. Do not restate them.
 *
 * What it owns here is the flow: browser/Bricks default margins on <p>, <h1..3>,
 * <ul> and <blockquote> would otherwise sit on top of the measured rhythm.
 */
/* ⛔ BRICKS SHRINK-WRAPS EVERY CHILD OF A CONTAINER, AND IT IS INVISIBLE UNTIL
 * SOMETHING NEEDS THE FULL WIDTH.
 *
 * `frontend-layer.min.css` ships
 *     .brxe-container{align-items:flex-start;display:flex;flex-direction:column;
 *                     margin-left:auto;margin-right:auto;width:1100px}
 * — so a section inner is a FLEX COLUMN whose children are all shrink-to-fit.
 * A grid inside it collapses to its content width instead of spanning the
 * container, and `text-align: center` on a shrink-wrapped box is a no-op
 * because the box itself is still hard left. This is the same class of defect
 * as the `width: 1100px` half of the container ladder (site.css row B-4): the
 * property that breaks the layout is not the one you would go looking for.
 *
 * Specificity, measured rather than assumed (brief trap 4): the Bricks rule is
 * (0,1,0) and lives in a stylesheet enqueued BEFORE body.css, so a plain
 * (0,1,0) class rule here wins on source order. No doubled class and no
 * !important are needed, and none are used. `display: flex` is left alone —
 * flex column with stretch behaves identically to block for this content, and
 * fighting the platform's layout mode would be a bigger change than the defect.
 *
 * Bricks sets no gap on containers (row-gap computes `normal`), so the
 * adjacent-sibling rhythm below is the only vertical spacing in play. */
.hba-section__inner { align-items: stretch; }

.hba-section__inner > * { margin-block: 0; }

/* ⛔ THE INTRA-HEADER RHYTHM USED TO BE DECLARED HERE AND IT HAS MOVED.
 * It now sits below the type primitives, under the heading "type rhythm register".
 * That is not tidying — it is the fix for the owner's "bunched up" defect, and the
 * reason is written out in full at the register itself. Nothing may re-introduce a
 * margin rule for `.hba-section__inner > *` at THIS point in the file. */

/* ⛔ SECTION HEADERS ARE NOT ALL CENTRED — CORRECTED 2026-08-21 (interior round).
 *
 * The note that stood here read "Section headers are CENTRED. Verified on
 * /benefits §3, /about §2/§4/§6/§7 and /news at 1440." The /news half of that is
 * WRONG and it was wrong when it was written. Live's three /news headings are all
 * LEFT-aligned, two of them in a `flex items-center justify-between` row with a
 * right-aligned "Visit HBAA" / "Visit NAHB" action; /events' "Featured Events" and
 * "All Upcoming Events" and /calendar's "Upcoming Events" are the same shape.
 *
 * WHERE CENTRING IS ACTUALLY LIVE'S (re-read from the capture this round, wrapper
 * class quoted):
 *   /benefits §3  "State & National Benefits"   `text-center mb-10`
 *   /benefits §4  "Ready to Join?"              `container text-center`
 *   /about   §5   "Our Core Values"             `text-center mb-12`
 *   /about   §6   "From Local Roots…"           `text-center mb-12`
 *   /about   §7   "Our Federation Partners"     `text-center mb-10`
 *   /about   §8   the amber closing band        `container text-center`
 *   /events  §4   the sponsor band              `container text-center`
 *   /become-a-member §2  "Membership Types"     `text-3xl mb-8 text-center`
 * WHERE IT IS NOT:  every /news heading, /events §2 and §3, /calendar §2, and all
 * three home section heads.
 *
 * THE RULE BELOW STILL STANDS AS THE DEFAULT and is still correct, because of the
 * CHILD COMBINATOR: it only reaches headings that sit DIRECTLY in a section inner.
 * Every left-aligned heading on the site is now wrapped in `.hba-sectionhead`
 * (components.css), which is itself the direct child — so those headings are
 * grandchildren and this rule cannot see them. That is load-bearing, not
 * incidental: unwrapping a head, or "fixing" this rule to use a descendant
 * combinator, silently centres eight live-left headings.
 *
 * Copy inside a split band lives in .hba-split__copy for the same reason and stays
 * left-aligned without an exception. Page heroes DO put their header directly in
 * the inner and are left-aligned, so they are excluded — via :where(), which means
 * this file never sets a property on a hero class, it only declines to. */
:where(.hba-section:not(.hba-hero, .hba-pagehero)) > .hba-section__inner > :where(.hba-eyebrow, .hba-heading, .hba-body, .hba-quote, .hba-placeholder) {
  text-align: center;
}
:where(.hba-section:not(.hba-hero, .hba-pagehero)) > .hba-section__inner > :where(.hba-actions) {
  justify-content: center;
}

/* `--center` is the composition's explicit marker for the two bands live writes as
 * `<div class="container text-center">` — /benefits' "Ready to Join?" and /events'
 * sponsor block. The rule above already centres DIRECT children of an inner, so on
 * those two sections this is a restatement rather than new behaviour; it is carried
 * because (a) the marker is emitted and a rendered class with no rule fails paint
 * row P-1, and (b) it keeps working if a centred band ever wraps its copy, which the
 * child-combinator rule above cannot. Descendants inherit `text-align`, so one
 * declaration on the inner covers the whole band. */
.hba-section--center > .hba-section__inner { text-align: center; }
.hba-section--center > .hba-section__inner > .hba-actions { justify-content: center; }

/* ================================================================ headings ===
 * .hba-heading is one class over h1, h2 and h3. The registers differ per level,
 * so they are split by element — with :where(hN) so the selector stays (0,1,0)
 * and a component's own heading rule still wins.
 *
 * TOKENS.JSON DISAGREES (1/3): tokens.json type.h1 gives the ladder
 * 64 / 51.2 / 38.4 / 35.2. That ladder belongs to the HOME HERO h1 alone
 * ("WELCOME TO THE") — hero.css's element. The nine PAGE-HERO h1s measure
 * 36/40 up to 1023 and 48/48 from 1024, at every one of the 11 captured widths.
 *
 * TOKENS.JSON DISAGREES (2/3): tokens.json type.h2 gives 48/60. Exactly one
 * section h2 on the site is that size. From the captured DOM, the Oswald-bold
 * section h2s decode to:
 *   `text-3xl`                 30/36 constant           x13   <- AUTHORED
 *   `text-3xl lg:text-4xl`     30/36 -> 36/40 at 1024   x9
 *   `text-2xl`                 24/32 constant           x3
 *   `text-4xl lg:text-5xl`     36/45 -> 48/60 at 1024   x1    (/home §3)
 * The modal register is 30/36 and it does NOT step with width.
 */
.hba-heading {
  font-family: "Oswald", "Arial Narrow", sans-serif;
  font-weight: 700;
  letter-spacing: normal;
  margin: 0;
}
.hba-heading:where(h1) { font-size: 36px; line-height: 40px; }
.hba-heading:where(h2) { font-size: 30px; line-height: 36px; }
.hba-heading:where(h3) { font-size: 20px; line-height: 28px; }

/* SIZE MODIFIERS — closing the HEADING GAP below (owner round 2, 2026-08-22:
 * "fonts too small in places"; the owner reviews at 2560, where live's
 * `text-3xl lg:text-4xl` h2s render 36 and /home §3's `text-4xl lg:text-5xl`
 * renders 48). Applied from the composition (hba-heading--lg / --xl / --sm);
 * live registers per tokens-measured.json byWidth 1024..2560 and the captured
 * DOM class sets quoted in the comment below. Declared AFTER the base rules on
 * purpose — same (0,1,0) specificity, later wins. */
.hba-heading--xl:where(h2) { font-size: 36px; line-height: 45px; }
@media (min-width: 1024px) {
  .hba-heading--lg:where(h2) { font-size: 36px; line-height: 40px; }
  .hba-heading--xl:where(h2) { font-size: 48px; line-height: 60px; }
}
.hba-heading--sm:where(h2) { font-size: 24px; line-height: 32px; }

/* HEADING GAP — three h2 size registers share one class and only the modal one
 * is reproducible:
 *   30/36 constant            x12 at 1440  <- authored
 *   30/36 -> 36/40 at 1024    x6           (/home §5 §6, /about §3 §4 §5,
 *                                           /benefits §4)
 *   36/45 -> 48/60 at 1024    x1           (/home §3)
 *   24/32 constant            x3           (subsection headings on
 *                                           /become-a-member, /contact)
 * Choosing the modal register is right for 12 of 22 at 1440 and 18 of 22 at 375.
 * A size modifier from the composition (hba-heading--lg / --sm) would close it.
 * ✅ CLOSED 2026-08-22 (owner round 2): --lg / --xl / --sm exist above and the
 * composition applies them (compose-pages.php). */

/* ==================================================================== body ===
 * .hba-body is on every <p>. Base register is the section lead paragraph:
 * 18px/29.25px (1.625 — Tailwind leading-relaxed), Source Sans 3 400.
 * Width-invariant: identical at 375 and 1440 in tokens-measured.json.
 */
.hba-body {
  font-family: "Source Sans 3", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-weight: 400;
  font-size: 18px;
  line-height: 29.25px;
  margin: 0;
}

/* ================================================================= eyebrow ===
 * The section eyebrow: 27 uses, and until now the single largest unstyled
 * surface on the site (battery row B-7 — it was computing an inherited
 * rgb(54,54,54)).
 *
 * READ OFF THE CAPTURED LIVE DOM, not inferred. Every one of the 25 section
 * eyebrows in hba-capture/content/html/*.html carries the identical class set:
 *
 *   <div class="text-amber-{400|500} font-['Oswald'] tracking-[0.2em]
 *               text-sm uppercase mb-{2|3|4}">Who We Are</div>
 *
 *   text-sm            -> 14px / 20px
 *   tracking-[0.2em]   -> 0.2 x 14 = 2.8px
 *   uppercase          -> text-transform (live authors title case and shouts it
 *                         in CSS; the composition emits it pre-uppercased, so
 *                         the declaration is idempotent but still correct)
 *   NO font-weight class -> weight 400. Not 700.
 *
 * THE TWO AMBERS ARE FIELD-CONDITIONAL, AND THIS IS NOT A SWAP.
 * amber-400 #ffb900 on charcoal/navy, amber-500 #fe9a00 on white/offWhite.
 * 25 of 25 obey it with ZERO counter-examples — 15 dark, 10 light. Spot checks:
 * "Our Story" (/about, white) is amber-500 while "Our Mission" (/about, navy) is
 * amber-400, in the same document. Do NOT "correct" one to the other.
 *
 * TOKENS.JSON AND STYLE_GUIDE ARE BOTH WRONG HERE (3/3). They give
 * 14/17.5, weight 700, letter-spacing 0.35px — which is `text-sm leading-tight
 * tracking-wide`, the HEADER WORDMARK lockup (.hba-lockup__line1, already in
 * site.css). It is a different element. Confirmed independently against the
 * captured DOM at the team lead's request; correcting it at source is theirs.
 *
 * mb-3 (12px) is the modal own-margin, 14 of 25 (mb-2 x10, mb-4 x1) — which is
 * what the `.hba-eyebrow + *` rule above uses.
 *
 * The /home hero's eyebrow is a DIFFERENT element (a bordered pill, text-xs
 * 12/16, tracking 2.4px, bg-amber-500/20) and belongs to hero.css. Not styled
 * here — it does not carry .hba-eyebrow's register.
 */
.hba-eyebrow {
  font-family: "Oswald", "Arial Narrow", sans-serif;
  font-weight: 400;
  font-size: 14px;
  line-height: 20px;
  letter-spacing: 2.8px;
  text-transform: uppercase;
  color: #fe9a00;
  margin: 0;
}

/* ================================================================== kicker ===
 * A small label line above a card heading. 9 uses.
 *
 * THIS CLASS COVERS THREE DIFFERENT LIVE REGISTERS and only one can be authored:
 *   a) /about x2 ("HBAA", "NAHB")  — Oswald 700, 24/32, #ffb900
 *      (span.font-['Oswald'].font-bold.text-amber-400, 8x; confirmed by eye in
 *       hba-capture/screenshots/w1440/about.png, the federation cards)
 *   b) /home x3 (news source labels) — Oswald 400, 12/16, ls 0.6px, uppercase,
 *      #ffb900 (div.text-amber-400.text-xs.font-['Oswald'], 12x)  <- AUTHORED
 *   c) /news x3 + /events x1 (dates: "August 2026", "Sep 10, 2026") — a grey
 *      caption register, 12/16 #99a1af or 14/20 #6a7282
 * (b) is authored because it is the only one that is unambiguously a kicker and
 * it matches the largest identifiable subgroup. text-transform is deliberately
 * NOT set: (c)'s content is mixed-case dates and uppercasing them would be a
 * content change made in CSS. Needs hba-kicker--lg / --date from the
 * composition. */
.hba-kicker {
  font-family: "Oswald", "Arial Narrow", sans-serif;
  /* 400, not 600 — H (Home.tsx:515) puts NO weight class on the news source line,
   * and live computes 400. Measured on live 2026-08-21; ours was rendering 600.
   * (Live also reaches uppercase via text-transform where our composition supplies
   * the string already uppercased. Same pixels, different text for a screen reader
   * — noted for the a11y pass, not changed here.) */
  font-weight: 400;
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.6px;
  margin: 0;
  /* Ink lives in the "ink modifiers" block near the foot of this file, not here
   * — .hba-kicker rides on .hba-body and the field ink rules would beat a colour
   * declared at this point in the cascade. Single source of truth, one place. */
}

/* ==================================================================== meta ===
 * Timestamps, event time/location lines, small print. 22 uses.
 * p.text-gray-500.text-sm.font-['Source_Sans_3'] — 14/20, #6a7282 (inkMuted),
 * 31 occurrences across the two orderings of that signature. */
.hba-meta {
  font-family: "Source Sans 3", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-weight: 400;
  font-size: 14px;
  line-height: 20px;
  margin: 0;
  /* Ink lives in the "ink modifiers" block near the foot of this file — see the
   * note on .hba-kicker above. */
}

/* =================================================================== quote ===
 * One use: /about's mission blockquote, on the navy field.
 * Oswald 700, white, centred, capped at 768px (max-w-3xl — at 768 the text runs
 * the full 720px content width, so the cap only binds above it).
 * The captured DOM confirms every number:
 *   <blockquote class="font-['Oswald'] font-bold text-white text-2xl
 *                      lg:text-3xl leading-snug">
 * text-2xl x leading-snug = 24 x 1.375 = 33; lg:text-3xl = 30 x 1.375 = 41.25,
 * stepping at 1024. Independently pixel-probed first: line pitch 33.0 in
 * w768/about.png and 41.25 in w1440/about.png. componentInk records only the
 * 24/33 form, which is why it was worth checking. The 768px cap is the wrapper's
 * `max-w-3xl mx-auto text-center`. */
.hba-quote {
  font-family: "Oswald", "Arial Narrow", sans-serif;
  font-weight: 700;
  font-size: 24px;
  line-height: 33px;
  max-width: 768px;
  margin: 0 auto;
}

/* ==================================================================== list ===
 * Benefit/feature lists. ul.space-y-2.5 (10px between items) with
 * li.flex.items-start.gap-2.5 (10px to the marker).
 * Item ink is inkList #364153 — one channel from inkStrong #374151 and a
 * different authored value; tokens.json names li items as its sole owner and
 * STYLE_GUIDE trap 3 forbids collapsing the two.
 *
 * Confirmed against the captured DOM — all 24 live items are identical and the
 * count matches this build exactly:
 *   <li class="flex items-start gap-2.5 text-sm font-['Source_Sans_3']
 *              text-gray-700">
 * text-sm = 14/20, gap-2.5 = 10px, and `text-gray-700` authored in oklch
 * rasterizes to #364153 — inkList, exactly as tokens.json names it.
 *
 * MARKER GAP — CLOSED 2026-08-21 (interior round). Each live <li> opens with an
 * inline Lucide `circle-check` SVG, re-read from benefits.html this round and
 * byte-identical on all 24 items:
 *   width=15 height=15 viewBox="0 0 24 24" fill=none stroke=currentColor
 *   stroke-width=2 stroke-linecap=round stroke-linejoin=round
 *   class="lucide lucide-circle-check text-amber-500 flex-shrink-0 mt-0.5"
 *   <circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>
 * i.e. a 15x15 amberFill (#fe9a00) circled check, 2px down from the text top.
 * The composition now emits it as
 *   <span class="hba-list__marker"><span class="hba-icon--check-circle"></span></span>
 * so the reserved slot is filled below. The 10px flex gap and
 * `align-items: flex-start` above are what it drops into, unchanged.
 * ARTWORK lives in components.css (`.hba-icon--check-circle`, traced from the
 * paths above); the BOX lives here, with the list primitive that reserved it. */
.hba-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  text-align: left;
}
.hba-list__item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-family: "Source Sans 3", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-weight: 400;
  font-size: 14px;
  line-height: 20px;
  color: #364153;
  margin: 0;
}
/* The marker box. `flex: none` is H's `flex-shrink-0` and it is load-bearing:
 * without it the 15px mark is squeezed to nothing by a long item, which is the
 * whole reason live carries the utility. `color` is what the glyph rides —
 * components.css paints the mask with `background-color: currentColor`, so the
 * ink is set once, here. */
.hba-list__marker {
  display: block;
  flex: none;       /* H flex-shrink-0 */
  width: 15px;      /* H width="15" */
  height: 15px;     /* H height="15" */
  margin-top: 2px;  /* H mt-0.5 */
  color: #fe9a00;   /* H text-amber-500 = amberFill. NOT amberInk — see the two-ambers trap. */
}

/* ==================================================== type rhythm register ===
 * The intra-header rhythm, measured off the capture at 1440:
 *   eyebrow box -> heading box   = 12px  (/about §4, /benefits §3, /about §2)
 *   heading     -> lead body     = 16px  (/about closing CTA: h2 ink ends 3531,
 *                                         body line box starts 3545)
 *   body        -> body          = 16px  (space-y-4, the split-copy container)
 *   header      -> content block = 40px  (`text-center mb-10`, 17 occurrences in
 *                                         the sweep vs 8 for mb-12)
 *   header      -> actions       = 32px  (`text-center mt-8`, 7 occurrences)
 * Modelled with adjacent-sibling rules so nothing carries a trailing margin into
 * the section's bottom padding.
 *
 * ⛔⛔ THIS REGISTER LIVES DOWN HERE, AFTER THE TYPE PRIMITIVES, AND MOVING IT BACK
 * UP SILENTLY SWITCHES OFF MOST OF IT. THIS IS THE OWNER'S "PADDING IS TOO BUNCHED
 * UP" DEFECT AND IT WAS NOT A MISSING RULE — IT WAS SOURCE ORDER.
 *
 * The register used to be declared next to `.hba-section__inner`, ABOVE the type
 * blocks. Every rule in it is (0,1,0). So is `.hba-heading { margin: 0 }`. So is
 * `.hba-body { margin: 0 }`, `.hba-eyebrow { margin: 0 }`, `.hba-quote
 * { margin: 0 auto }`, `.hba-list { margin: 0 }`. At EQUAL specificity the later
 * declaration wins — so every one of those resets was quietly beating the register
 * on its own targets, and had been since the file was written.
 *
 * MEASURED ON ALL EIGHT ROUTES BEFORE THE MOVE, not deduced. Every section inner on
 * the site read `eyebrow[mt0] heading[mt0] body[mt0]` — the two most-used steps in
 * the register, 12 and 16, were landing NOWHERE. What did work was exactly the
 * targets with no `margin` declaration of their own: `.hba-grid` at 40,
 * `.hba-actions` at 32, `.hba-timeline` at 16, `.hba-partners` at 12. That split —
 * some rules of one block firing and others not — is the signature, and it is what
 * makes this invisible to a "is the rule present?" check and to a section-height
 * diff, which can land inside tolerance while every block inside is jammed.
 *
 * It is also the whole of gate R-9 (no heading flush against its neighbour): live
 * scores 0 on all eight routes, and the page-hero eyebrow sat flush against the h1
 * on all seven interior routes for this reason and no other.
 *
 * ⚠ PAGE HEROES NEED NO EXCEPTION HERE, AND MUST NOT BE GIVEN ONE. The `:not(.hba-hero,
 * .hba-pagehero)` exclusion further up is on the CENTRING rule and is about
 * `text-align` only — page-hero headers are left-aligned and live. It never touched
 * margins. A page hero IS a `.hba-section__inner`, so it takes this register as
 * written: eyebrow 14/20 mb 12, h1 48/48 mb 16, both live's. The HOME hero is a
 * different element with a different register (12/16 eyebrow mb 24) and hero.css
 * already carries it — nothing here reaches it, because its eyebrow is a bordered
 * pill that does not carry .hba-eyebrow.
 *
 * WHY NOT JUST RAISE THE SPECIFICITY: because (0,1,0) is this file's cascade
 * contract (see the header) and a component in a later stylesheet has to be able to
 * beat any of this with one class. Source order is the only lever that does not
 * cost the contract. The one ordering constraint that remains is the obvious one —
 * this block must stay ABOVE `.hba-anchor { margin: 0 }`, which is a zero-box
 * marker that must never take a rhythm step.
 *
 * ------------------------------------------------------- the nested contexts ---
 * `> *` WAS ALSO A PROXY FOR "THE TYPE FLOW", AND IT IS NOT THE TYPE FLOW: the
 * register reached only the first nesting level, so a section whose copy sits in a
 * column wrapper got none of it even once the ordering was right. Measured on
 * /about §3 "Serving the Wiregrass", live against ours at 2543:
 *
 *              live   ours(before)
 *   eyebrow    mb 12      0
 *   h2         mb 20      0
 *   action row mt 32      0
 *   copy column  516    446      <- 70px short, and 64 of it is these three
 *
 * (the residual 6 is the h2's 36/40-vs-30/36 ramp and the CTA's 48-vs-46 box,
 * neither of which is this file's — see the doc's "Open parity delta" section.)
 *
 * THE CONTEXT LIST IS ADDED PER SELECTOR, NOT BY LOOSENING THE COMBINATOR.
 * Swapping `>` for a descendant combinator would have been one character and it
 * would have been wrong twice over: it would reach into every card in the section
 * (see the exclusion below), and it would break the centring rule above, which
 * depends on this same combinator to leave eight live-left headings alone.
 * Each alternative below still contributes exactly one class, so every one of them
 * is (0,1,0) and the cascade contract is unchanged.
 *
 * ⛔ .hba-grid CELLS ARE DELIBERATELY NOT IN THIS LIST, AND ADDING THEM BREAKS
 * EVERY CARD ON THE SITE. Enumerated across all ten routes: every `.hba-grid > *`
 * is a .hba-card, and components.css already spaces card internals with its OWN
 * margins — `.hba-card--center > .hba-heading{margin-bottom:12px}`,
 * `.hba-card--icon > .hba-card__iconsq{margin:0 0 16px}`,
 * `.hba-eventcard__body > * + *{margin-top:16px}` and six more. Margins on flex/grid
 * children do NOT collapse, so a `heading + * {margin-top:16px}` reaching into a card
 * would ADD to that 12px bottom margin and open 28px where live has 12. The cards
 * that genuinely have no rhythm (home's news cards) are components.css's element and
 * are the doc's own separate "News card" table, not this register.
 *
 * ⛔ AND THE ACTION ROW IS SCOPED TO THE STORY COLUMN FOR THE SAME REASON.
 * A global `.hba-actions {margin-top:32px}` would look right on /about and break
 * /become-a-member: the option blocks stack their two CTAs in a
 * `.hba-actions--stack` whose live margin-top is 0 (its buttons are held apart by
 * the 12px stack gap instead). Measured on live, not reasoned about.
 *
 * THE HEADING STEP IS 20 IN A STORY COLUMN AND 16 IN AN INNER, AND BOTH ARE LIVE'S.
 * The doc's type table reads "h2 — mb 0 in a head row, mb 20 in a story column"; the
 * 16 is /about's closing CTA, a head that sits directly in the inner.
 */
.hba-section__inner > :where(.hba-eyebrow) + *,
.hba-split__copy    > :where(.hba-eyebrow) + * { margin-top: 12px; }  /* H mb-3 → 12 */
.hba-section__inner > :where(.hba-heading) + * { margin-top: 16px; }
.hba-split__copy    > :where(.hba-heading) + * { margin-top: 20px; }  /* H mb-5 → 20, story column */
.hba-section__inner > :where(.hba-body) + :where(.hba-body) { margin-top: 16px; }
.hba-section__inner > * + :where(.hba-grid, .hba-split, .hba-list, .hba-img, .hba-quote, .hba-placeholder, .hba-optioncols) { margin-top: 40px; }
/* The journey timeline takes 48, not the 16 a heading normally hands its next
 * sibling and not the 40 of a content block: live wraps /about §6's eyebrow+h2 in
 * its own `text-center mb-12` block, so the step belongs to the head, not to the
 * heading. Measured — live's head block is 72 tall with mar 0/48, ours computed 16,
 * and the section stood 52 short (36 here; the other 16 was the section's own
 * padding, composed at 56/56 where live is 64/64).
 * Must stay BELOW the heading rule above: both are (0,1,0) and order decides. */
.hba-section__inner > * + :where(.hba-timeline) { margin-top: 48px; }
/* /about's mission band is the ONE eyebrow the 40 above would overshoot: its eyebrow
 * is followed directly by the blockquote, and live's own step there is 16, not the
 * 12 of a normal head nor the 40 of a content block (the doc's type table:
 * "eyebrow — about mission band — 14/20 — mb 16"). Before the ordering fix this pair
 * computed 0 and the overshoot could not show; it is restated here rather than
 * discovered later. */
.hba-section__inner > :where(.hba-eyebrow) + :where(.hba-quote) { margin-top: 16px; }
/* ⛔ THE PAGE HERO TAKES ITS STEP ON THE EYEBROW, NOT ON THE h1, AND THAT IS NOT A
 * WORKAROUND — IT IS WHERE LIVE PUTS IT. Gate R-9 row, added 2026-08-21.
 *
 * All seven interior page heroes had their eyebrow flush against the h1. The
 * register above DOES match the h1 (a page hero puts its header directly in the
 * inner and is not excluded from anything here — the `:not(.hba-pagehero)` further
 * up is on the CENTRING rule and concerns `text-align` alone). It simply cannot
 * win: hero.css owns `.hba-pagehero h1 { margin: 0 0 16px }` at (0,1,1) in a LATER
 * stylesheet, which is this file's cascade contract working exactly as designed,
 * and which zeroes the h1's top margin.
 *
 * Re-measured on live, all seven routes, 2026-08-21 — and live agrees with
 * hero.css rather than with the register:
 *     eyebrow 14/20  mb 12        h1 48/48  mt 0  mb 16
 * i.e. the 12 is the EYEBROW's own bottom margin and the h1 genuinely has no top
 * margin. hero.css's h1 rule is already right; the eyebrow half was simply never
 * written. `.hba-eyebrow` is this file's element and hero.css does not touch it
 * (it carries only `.hba-pagehero`, `.hba-pagehero h1` and `.hba-pagehero
 * .hba-body`), so this is the correct home for it.
 *
 * `.hba-pagehero` is wrapped in `:where()` so this file still never competes for a
 * hero class — (0,1,0), from `.hba-section__inner` alone, same discipline as the
 * padding fallback at the top of the file.
 *
 * ⚠ WATCH: the two margins cannot double TODAY only because hero.css zeroes the
 * h1's top and every page hero is eyebrow → h1. If a page hero is ever composed
 * with something else after its eyebrow, that element takes the register's 12 as
 * well as this 12 and the gap becomes 24. */
:where(.hba-pagehero) > .hba-section__inner > :where(.hba-eyebrow) { margin-bottom: 12px; }  /* H mb-3 → 12 */

/* Last on purpose, both of them: an actions row that follows a heading must take 32
 * and not the heading step, and these win on source order at equal (0,1,0). */
.hba-section__inner > * + :where(.hba-actions),
.hba-split__copy    > * + :where(.hba-actions) { margin-top: 32px; }  /* H mt-8 → 32 */

/* ==================================================================== grid ===
 * Gap is 24px, pixel-probed on the capture at six widths:
 *   1440  content 112..1328 (1216) / 3 cols / card 389.33 / gutters at 500..524
 *         and 916..940            (w1440/benefits.png, w1440/about.png,
 *                                  w1440/news.png, w1440/events.png)
 *   1280  content  32..1248 (1216) / 3 cols            (w1280/benefits.png)
 *   1024  content  32..992   (960) / 3 cols / card 304 (w1024/benefits.png)
 *    768  content  24..744   (720) / 2 cols / card 348 (w768/benefits.png)
 *    640  content  24..616   (592) / 1 col             (w640/benefits.png)
 *    390  content  16..374   (358) / 1 col             (w390/benefits.png)
 * Columns are the only thing that changes with width. Padding does not.
 */
.hba-grid {
  display: grid;
  gap: 24px;
  grid-template-columns: 1fr;
  text-align: left;
}

/* GRID--2 GEOMETRY GAP. Four uses, four different live geometries. The captured
 * DOM gives the exact class string for each, so this is a stated gap, not an
 * estimate:
 *   /home §5      `grid grid-cols-1 md:grid-cols-2 gap-6 max-w-3xl mx-auto`
 *                 -> 768 centred, gap 24            <- AUTHORED, reproduced exactly
 *   /about §6     `grid grid-cols-1 md:grid-cols-2 gap-6 max-w-2xl mx-auto`
 *                 -> 672 centred, gap 24            (96px too wide here)
 *   /benefits §3  `grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto`
 *                 -> 896 centred, gap 32            (128px too narrow, gap -8)
 *   /news §4      `grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4`
 *                 -> full width, 3-col tracks, gap 16
 * One class cannot carry four. The /home §5 register is authored because it is
 * the only one this class can be right about, and it keeps the other two centred
 * pairs in the right visual family. Closing it needs per-section modifiers
 * (hba-grid--2-w672 / -w896) from the composition. */
.hba-grid--2 {
  grid-template-columns: 1fr;
  max-width: 768px;
  margin-inline: auto;
}

/* GRID--3, from the captured class strings — 7 uses, and they do NOT agree on
 * the breakpoint ladder:
 *   `md:grid-cols-3 gap-6`                  1 -> 3 at 768, gap 24   x5  <- AUTHORED
 *      (/home §7, /become-a-member §2, /events §2, /news §2, and one more)
 *   `md:grid-cols-2 lg:grid-cols-3 gap-6`   1 -> 2 -> 3, gap 24     x1  (/benefits §2)
 *   `md:grid-cols-2 lg:grid-cols-3 gap-4`   1 -> 2 -> 3, gap 16     x1  (/news §3)
 *   `md:grid-cols-3 gap-8`                  1 -> 3 at 768, gap 32   x1  (/about §5)
 * The modal ladder is 1 -> 3 straight at md, 5 of 7, and gap-6 is 6 of 7. At 768
 * that puts three cards in a 720px content box, ~224px each — narrow, but it is
 * what live does. The two `md:grid-cols-2` grids will be 3-up at 768 here where
 * live is 2-up; /about §5's gap will be 24 where live is 32.
 * Base register restated here rather than left to .hba-grid so the class carries
 * its own. */
.hba-grid--3 {
  grid-template-columns: 1fr;
}

/* =================================================================== split ===
 * Two bands: /home §3 and /about §3. Equal halves with a 56px gutter, vertically
 * centred, stacking below 1024.
 *
 * The captured DOM gives both bands exactly, and THEY DIFFER BY ONE STEP:
 *   /about §3  `grid grid-cols-1 lg:grid-cols-2 gap-14 items-center`  -> 56px
 *   /home  §3  `grid grid-cols-1 lg:grid-cols-2 gap-12 items-center`  -> 48px
 * 56px is authored, so /home's band is 8px wide in its gutter. Both share
 * `lg:grid-cols-2` (stack below 1024) and `items-center`.
 *
 * The 56px was independently derived from the capture before the class strings
 * were read, which is why it is trusted: scanning w1440/about.png (band
 * y 790..1420) for columns that are white on every scanline gives corridors at
 * 0..111, 691..747, 1028..1047 and 1328..1439. The media column's left edge is
 * 748 in a 1216-wide content box, so each column is 580 and the gutter is
 * 1216 - 2x580 = 56. Two independent methods, same number. */
.hba-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: 56px;
  align-items: center;
}
.hba-split__copy  { min-width: 0; text-align: left; }
.hba-split__media { min-width: 0; }

/* Split copy runs a smaller, darker paragraph than a section lead:
 * 16px/26px inkStrong #374151, 16px apart. The live container is literally
 * div.space-y-4.text-[#374151].font-['Source_Sans_3'] (4x).
 *
 * THIS IS THE THIRD REGISTER OF THE STORY COLUMN and it was the only one that ever
 * worked — the other two (eyebrow 12, heading 20) and the action row's 32 are in the
 * section-rhythm block above; look there, not here, if a story column is jammed.
 * Re-measured against live 2026-08-21 and correct as it stands: 16/26 and 16 apart on
 * both /about §3 and /home §3. Deliberately NOT restated above. */
.hba-split__copy :where(.hba-body) {
  font-size: 16px;
  line-height: 26px;
}
.hba-split__copy :where(.hba-body) + :where(.hba-body) { margin-top: 16px; }  /* H space-y-4 → 16 */

/* ================================================================= actions ===
 * The CTA row, 7 uses. The captured live DOM carries exactly four of these and
 * they agree to the class: `flex flex-wrap gap-4`, once as
 * `mt-8 flex flex-wrap gap-4` and once as `flex flex-wrap justify-center gap-4`.
 * gap-4 = 16px; mt-8 = 32px (the `+ .hba-actions` rule above).
 *
 * NO `items-center`. An earlier draft of this file carried
 * `align-items: center`; no captured instance has it, so it is gone and the flex
 * default (stretch) stands. `justify-center` is the centred variant and is
 * applied by the section-header rule above, not here.
 * .hba-cta itself is site.css's; this only lays the row out. */
.hba-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}
/* `--center`: H `flex flex-wrap justify-center gap-4`, the centred variant named in
 * the block above. It was applied by the section-header rule for direct children of
 * the inner; the composition now also puts an actions row INSIDE a wrapper (the
 * /benefits and /events closing bands), where that rule cannot reach it. */
.hba-actions--center { justify-content: center; }
/* `--stack`: /become-a-member's PDF panel. H stacks its two buttons full-width with
 * `mt-3` between them — `w-full` on each and no row at all. `nowrap` because a
 * wrapping column flex needs a definite height to mean anything and this has none. */
.hba-actions--stack {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: 12px; /* H mt-3 */
}

/* ================================================================ form rows ===
 * 2026-08-21 interior round. /become-a-member pairs its fields; the build shipped
 * one field per line, thirteen lines deep. Read off the live form
 * (hba-capture/content/html/become-a-member.html, BecomeMember.tsx:286), verbatim:
 *
 *   <form class="bg-[#f5f7fa] rounded-xl p-8 border border-gray-200">
 *     <div class="grid grid-cols-1 sm:grid-cols-2 gap-5">          <- the whole form
 *       <div>First Name</div><div>Last Name</div>
 *       <div class="sm:col-span-2">Company Name</div>
 *       <div>Email</div><div>Phone</div>
 *       <div class="sm:col-span-2">Business Address</div>
 *       <div>City</div>
 *       <div class="grid grid-cols-2 gap-3"><div>State</div><div>ZIP</div></div>
 *       <div>Membership Type</div><div>License Number</div>
 *       <div>Type of Business</div><div>Sponsor</div>
 *       <div class="sm:col-span-2">Additional Comments</div>
 *
 * gap-5 = 20px in BOTH axes; the break is `sm` = 640px, not the site's 768.
 * The composition emits per-ROW wrappers (hba-formrow) rather than one grid with
 * col-spans, so a row here is what live models as "two cells of the shared grid".
 * Flex with `flex: 1 1 0` rather than `grid-template-columns: repeat(2, 1fr)` on
 * purpose: it makes an N-cell row into N equal cells with no count in the CSS, so
 * a 2-cell row and a 3-cell row both work and neither needs a modifier.
 *
 * ⚠ ONE CARRIED DIFFERENCE, STATED. Live's City / State / ZIP line is 2:1:1 —
 * City takes a full half and State+ZIP split the other half through a nested
 * `grid-cols-2 gap-3`. If the composition emits those three as three siblings of
 * one row they will render 1:1:1 here. Closing it needs either the nested wrapper
 * or a width modifier on the City cell; flagged to the TL rather than guessed. */
.hba-formrow {
  display: flex;
  flex-direction: column;
  gap: 20px;             /* H gap-5, both axes */
}
/* ⛔ ROW SEPARATION IS ONE FACT AND THE MARKUP HAS TWO SHAPES FOR IT.
 * CORRECTED 2026-08-21 (rhythm round). Live models the whole form as ONE grid with
 * `gap-5`, so every row — paired or full-width — is 20px from the next. The importer
 * emits paired rows as `.hba-formrow` but a full-width field as a BARE `.field`
 * (bin/import-forms.php:189 vs :217), and only the first of those carried the margin.
 * Measured: Company Name and Business Address on /become-a-member, and all five cells
 * of /contact, sat FLUSH against the next row at margin-bottom 0.
 *
 * The `:not()` is load-bearing and it is the 40px trap. `.field` is on the row cells
 * too (`class="field hba-formrow__half"`), and those are flex ITEMS of a row that
 * already holds them apart with `gap: 20px` — a bottom margin there would add a
 * second 20 to the row's own height. Excluding the half is what keeps this at one
 * 20px everywhere instead of 40 inside every paired row. It is wrapped in `:where()`
 * so the selector stays (0,1,0) rather than climbing to (0,2,0).
 *
 * `.hba-formrow--split` below re-zeroes its own margin and still wins on source
 * order: it is a row of a CELL, not a row of the form. */
.hba-formrow,
.field:where(:not(.hba-formrow__half)) { margin: 0 0 20px; }  /* H gap-5 → 20 */
/* Nothing carries a trailing margin into the form's 32px bottom padding. Inert in
 * both forms today — CF7 appends its own .wpcf7-response-output as the real last
 * child — and carried for the case where it stops doing that. */
.hba-formrow:last-child,
.field:where(:not(.hba-formrow__half)):last-child { margin-bottom: 0; }
.hba-formrow__half {
  flex: 1 1 0;
  min-width: 0;          /* without it a long placeholder sets the track width */
}
/* ⛔ CF7 RUNS wpautop OVER THE FORM BODY, so a cell can arrive wrapped in a <p>
 * that neither the composition nor this file put there. A wrapped cell is not a
 * flex ITEM — the <p> is — and the paired layout silently degrades to one column
 * per row while every rule still "applies".
 *
 * `display: contents` is the fix rather than styling the <p> as the cell: it takes
 * the paragraph box out of the tree entirely, so the real cells become the flex
 * items whether wpautop wrapped one cell, both, or none. Styling the <p> instead
 * only works for the one-cell-per-<p> case and breaks if wpautop groups the row.
 * Harmless when no <p> appears, which is why it is safe to carry unconditionally. */
.hba-formrow > p,
.hba-formrow__half > p { display: contents; }
/* The NESTED row — State + ZIP inside City's sibling half (import-forms.php's
 * $ROWS renders `array('city', array('state','zip'))` as a half containing its own
 * .hba-formrow--split). H's inner grid is `grid grid-cols-2 gap-3` with NO
 * responsive prefix, so it stays two-up at every width including 390 — which is the
 * whole reason the nesting exists rather than a flat trio. It also carries no
 * bottom margin: it is not a row of the outer form, it is the contents of a cell. */
.hba-formrow--split {
  flex-direction: row;
  gap: 12px;       /* H gap-3 */
  margin-bottom: 0;
}

/* =============================================================== field cell ===
 * 2026-08-21 rhythm round. THE SINGLE LARGEST HEIGHT DELTA ON THE SITE, and it is
 * ONE CELL REPEATED: live 56px, ours 73px. /become-a-member's application section
 * measured 970 on live against 1386 here (+416); /contact 1112 against 1501 (+389).
 * Thirteen cells at +17 plus the textarea at +232 is the whole of both numbers —
 * there is no second cause.
 *
 * THE ARITHMETIC IS THE GATE:  label 14  +  mb 6  +  control 36  =  56.
 *
 * The importer emits every cell as `<div class="field">` holding a bare <label> and
 * a CF7 control (bin/import-forms.php:154) and NOTHING in the authored lane styled
 * it. What painted it instead was Bricks' own form reset, read out of the rendered
 * page rather than assumed — frontend-layer.min.css, inside `@layer bricks`:
 *     label { display:block; font-weight:400; margin-bottom:5px; color:… }
 *     .input, input:not([type=submit]), select, textarea {
 *         border-width:1px; line-height:40px; padding:0 12px; width:100%; … }
 * An inherited 15/25.5 label + 5 + a 40px line box in a 1px border = 72.5 → 73.
 *
 * ⛔ THOSE RULES ARE LAYERED, WHICH IS WHY THIS BLOCK NEEDS NO SPECIFICITY AT ALL.
 * An UNLAYERED declaration beats ANY layered one whatever its specificity, so
 * (0,1,0) here wins outright — but ONLY for the properties named below. Width,
 * border-width and border-style are deliberately not restated: they are Bricks'
 * and they are already live's. Rest appearance — radius, fill, border colour,
 * shadow — is components.css's by the note in its focus-ring block, and is not
 * touched here.
 *
 * SCOPE IS `.field`, WHICH IS EXACTLY THE TWO CF7 FORMS' CELLS. Enumerated over all
 * ten routes: every visible control on the site is inside a `.field`. The only ones
 * outside are the six hidden inputs and [submit], which is .hba-formsubmit's. The
 * combinator is DESCENDANT, not child, because CF7 wraps each control in its own
 * <span class="wpcf7-form-control-wrap"> and wpautop can drop a <p> in as well.
 *
 * ⚠ THE DOC AND LIVE DISAGREE ON <select>, AND LIVE WINS.
 * RHYTHM_TRUTH's form table reads "input / select — h 36, pad 4/12". Live's select
 * is NOT the input: it is `w-full h-10 px-3` and measures h 40 with pad 0/12, so its
 * cell is 60, not 56. Re-measured on live at 2543 this round — the Membership Type
 * and License Number cells are the only two 60s in live's run of
 * 56,56,56,56,56,56,56,56,56,60,60,56,56,84. Reported to the TL for the doc.
 */
.field :where(label) {
  font-size: 14px;      /* H text-sm */
  line-height: 14px;    /* H leading-none */
  margin-bottom: 6px;   /* H mb-1.5 → 6 */
  /* Measured on live 2026-08-21: weight 600, ink rgb(13,27,42). Ours was taking
   * Bricks' 400 and var(--bricks-text-light) = rgb(158,158,158) — a washed-out
   * grey on white. This is a legibility defect, not only a parity one. */
  font-weight: 600;     /* H font-semibold */
  color: #0d1b2a;       /* H text-[#0d1b2a] */
}

/* ================================================================= measures ===
 * Live caps the MEASURE of certain body copy, and a cap is invisible until it
 * changes where a line breaks — which is why this was missed: nothing renders
 * wrong, the copy simply fits on one line where live wraps to two, and the band
 * lands short. Two instances, both `max-w-*` + `mx-auto` on live.
 *
 * These live here rather than beside .hba-sectionhead in components.css because
 * they are a property of the BODY COPY, not of the head component: the same cap
 * would apply to that paragraph in any wrapper. The head's own layout stays
 * components.css's.
 */

/* /benefits §4 and /about §8, the centred closing bands:
 * `text-lg mb-8 max-w-xl mx-auto`. Uncapped, /benefits' band measured 285 against
 * live's 328 — the 43 is one un-wrapped line plus the button size delta. */
.hba-section--center .hba-section__inner > :where(.hba-body) {
  max-width: 576px;   /* H max-w-xl */
  margin-left: auto;
  margin-right: auto;
}

/* The "Find a Member" band's paragraph cap lives in components.css beside the
 * rest of the .hba-sectionhead__copy type register (its eyebrow and heading
 * margins are already there). Splitting one component's type facts across two
 * files is how the next person misses one. */

/* ⛔ CF7 APPENDS ITS OWN RESPONSE BOX AND NO STYLESHEET OWNED IT. The plugin ships
 * `.wpcf7-response-output { margin: 30px 15px 15px; ... }` and renders it at
 * height 0 until a submit — so both forms carried ~45px of dead space below the
 * submit button that has no live counterpart and that nothing in this repo put
 * there. Zero the margin while it is empty; restore spacing only when it actually
 * has something to say, so a validation message is not jammed against the button. */
.wpcf7-response-output {
  margin: 0;
  padding: 0;
  border: 0;
}
.wpcf7-form.invalid .wpcf7-response-output,
.wpcf7-form.sent .wpcf7-response-output,
.wpcf7-form.failed .wpcf7-response-output,
.wpcf7-form.spam .wpcf7-response-output {
  margin-top: 16px;
  padding: 12px 16px;
}
.field :where(input, textarea) {
  font-size: 14px;      /* H text-sm */
  line-height: 20px;    /* live computes 20 on both; Bricks' 40 would overflow the box */
}
.field :where(input) {
  height: 36px;         /* H h-9 → 36, border-box, so the 1px border is inside it */
  padding: 4px 12px;    /* H py-1 px-3 */
}
.field :where(select) {
  height: 40px;         /* H h-10 → 40 — NOT the input's 36, see the ⚠ above */
  padding: 0 12px;      /* H px-3, and no vertical padding at all */
  font-size: 14px;      /* H text-sm */
  line-height: normal;  /* live computes `normal` here, not the input's 20 */
}
/* ⛔ min-height ALONE CANNOT BIND THIS ONE, AND READING EITHER SOURCE GETS IT WRONG.
 * Live's JSX carries `rows={4}`, which shadcn's `min-h-16` overrides — the RENDERED
 * height is 64, and that is the number the doc records. Our CF7 tag renders its own
 * `rows` attribute too, but here `rows` sets the INTRINSIC height (measured 277px),
 * and an intrinsic height beats a smaller min-height. So the live value has to be
 * restated as `height` as well or the cell stays 316px tall with a rule that looks
 * applied. Both are the same measured 64. */
/* ⛔ AND THE CELL IS STILL 9px TOO TALL UNTIL THE TEXTAREA STOPS BEING INLINE.
 * With the geometry above and nothing else, the box measures 64 and the CELL measures
 * 93 — because a UA textarea is `inline-block`, so it sits on a LINE BOX, and the
 * cell's inherited 15/25.5 strut hangs its descender below the control's baseline.
 * Nine pixels of it. The control is the right size and the cell is still wrong, which
 * is the same shape of defect as the container `align-items` note at the top of this
 * file: the property that breaks the box is not the one you would go looking for.
 * The inputs do not need this — a text input's baseline is its own internal text
 * baseline, and all thirteen input cells measure exactly 56 as they stand.
 * Live's textarea IS block-level: it computes `display: flex` (shadcn's Textarea
 * carries the `flex` utility). `block` is written instead because both are
 * block-level, neither generates a line box, and a flex container on a form control
 * would read as meaning something it does not. MEASURED, not assumed — the cell
 * lands on live's 84 with either value and on 93 with `inline-block`. */
.field :where(textarea) {
  display: block;       /* live computes `flex`; same block-level box, same 84 — see above */
  min-height: 64px;     /* H min-h-16 → 64 */
  height: 64px;         /* the same 64, as the used height — see above */
  padding: 8px 12px;    /* H py-2 px-3 */
}

/* ============================================================== option cols ===
 * /become-a-member's two-column band. H (BecomeMember.tsx:194), verbatim:
 *   `grid grid-cols-1 lg:grid-cols-3 gap-12`
 * with the Application Options block `lg:col-span-1` and the form `lg:col-span-2`.
 * Modelled as 1fr/2fr rather than a 3-track grid with spans: identical geometry,
 * and it does not need the composition to emit span classes on the children.
 * gap-12 = 48px; the break is `lg` = 1024, the container-ladder boundary.
 * Pixel-checked against the live capture (Join/Live, DPR 1 — the amber PDF button
 * measures exactly its 44px `px-5 py-3` box, which is what proves the scale): the
 * dark PDF panel's content box is 405px inside p-6, i.e. a 453px left column.
 *
 * `align-items: start` matters: without it the aside stretches to the form's height
 * and its two panels are pulled apart by the flex/grid stretch. */
/* ⛔ `minmax(0, 1fr)` for the same B-12 reason .hba-contact documents: the
 * Turnstile iframe's fixed 300px is a min-content floor a bare `1fr` track
 * must honour, and at phone widths honouring it means overflowing the page.
 * The children already carry `min-width: 0`; the track cap completes the pair. */
.hba-optioncols {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 48px;    /* H gap-12 */
  width: 100%;
  align-items: start;
}
.hba-optioncols__aside,
.hba-optioncols__main { min-width: 0; }

/* ===================================================================== img ===
 * 4 uses: the two split-band media images, and two federation lockup marks that
 * also carry .hba-partners__mark (components.css's, and it wins). */
.hba-img {
  display: block;
  max-width: 100%;
  height: auto;
}
/* Radius on the split media frame is shape.radius.card (12px, 86 uses — the
 * dominant radius on the site). NOT individually measured for this element:
 * live's /about split has a stats grid where the composition puts an image, so
 * there is no like-for-like frame to read. Flagged rather than asserted. */
.hba-split__media :where(.hba-img) {
  width: 100%;
  border-radius: 12px;
  object-fit: cover;
}

/* ============================================================= placeholder ===
 * WEBOPS-PLACEHOLDER markers for owner-awaiting-client items. 3 uses.
 *
 * NO LIVE COUNTERPART EXISTS — this is a build-only marker, so nothing here is
 * measured from the capture. COMPONENT_MAP M12 specifies the requirement rather
 * than the appearance: "visibly inert, never live-looking". Built from tokens
 * only: inkFaint ink, the `border` grey, hairline radius, and gap-scale padding.
 * BRICKS_ADAPTATION S-1 requires zero WEBOPS-PLACEHOLDER occurrences at the
 * shipped URL, so this should never be seen in production — it is here so that
 * if one survives to a review build it is unmistakable. */
.hba-placeholder {
  font-size: 14px;
  line-height: 20px;
  background-color: transparent;
  /* Ink lives in the "ink modifiers" block near the foot of this file. */
  border: 1px dashed #e5e7eb;
  border-radius: 4px;
  padding: 12px 16px;
}

/* ==================================================== field-contextual ink ===
 * Ink is a property of the FIELD, not of the element. Every rule below is
 * (0,1,0) — the field half is in :where() — so a card that puts a light face on
 * a dark field only needs its own (0,1,0) rule in components.css to win.
 *
 * From the captured live DOM, by class:
 *   dark field, heading   `text-white`      #ffffff
 *   dark field, body      `text-white/70`   rgba(255,255,255,.698)
 *   light field, heading  `text-[#0d1b2a]`  #0d1b2a
 *   light field, body     `text-gray-600`   #4a5565
 *
 * STYLE_GUIDE and tokens.json say "h2: amberInk on dark, charcoal on light".
 * That is wrong, and the captured markup settles it rather than inferring it:
 * every section h2 in the capture is either `text-[#0d1b2a]` or `text-white`.
 * The ONLY `text-amber-400` h2 on the site is the home hero's, which is
 * hero.css's element. Section headings on charcoal and navy are WHITE.
 * Escalated to the team lead; the captured markup is followed here.
 */
:where(.hba-field-charcoal, .hba-field-navy) .hba-heading,
:where(.hba-field-charcoal, .hba-field-navy) .hba-quote { color: #ffffff; }
:where(.hba-field-charcoal, .hba-field-navy) .hba-body { color: rgba(255, 255, 255, 0.698); }
/* No dark-field rule is written for .hba-meta or .hba-list__item: all 22 metas
 * and all 24 list items in this build sit on white or offWhite fields (checked
 * with querySelectorAll across the 10 routes — both dark-field selectors matched
 * ZERO elements). A rule that addresses nothing is not carried. If a component
 * later puts either on a dark ground, it owns that ink. */
:where(.hba-field-charcoal, .hba-field-navy) .hba-eyebrow { color: #ffb900; }

:where(.hba-field-white, .hba-field-offWhite) .hba-heading { color: #0d1b2a; }
:where(.hba-field-white, .hba-field-offWhite) .hba-body    { color: #4a5565; }

/* The single amber band (/about's closing CTA) carries white ink. Heading white
 * is visible in w1440/about.png; body is white/90
 * (p.text-white/90.font-['Source_Sans_3'].text-lg — the only 18px white/90
 * paragraph register in the sweep, and the amber band is the only place it can
 * sit). Body ink here is INFERRED from a single-consumer signature, not read off
 * a named element. */
:where(.hba-field-amberFill) .hba-heading { color: #ffffff; }
:where(.hba-field-amberFill) .hba-body {
  color: rgba(255, 255, 255, 0.902);
  /* `text-lg` with no leading class = 18/28, not the 29.25 base. And the copy is
   * capped and centred: max-w-xl = 576px, mx-auto. */
  line-height: 28px;
  max-width: 576px;
  margin-inline: auto;
}

/* ---- ink modifiers that RIDE ON .hba-body, restated after the field rules ----
 *
 * .hba-meta, .hba-kicker and .hba-placeholder are applied ALONGSIDE .hba-body on
 * the same <p> — `<p class="hba-body hba-meta">`. Their own ink is (0,1,0) and so
 * is the field rule above, so before this block the field rule won on source
 * order and all three painted the field's body ink. Caught by measuring:
 * .hba-meta computed rgb(74,85,101) (#4a5565, the light-field body ink) where it
 * should be #6a7282, and .hba-placeholder computed white/70 on /events' charcoal
 * field where it should be inkFaint. Same class of bug as the container's
 * align-items: invisible until something needs it.
 *
 * Split copy is here for the same reason — it keeps inkStrong rather than the
 * light-field inkBody. Still (0,1,0), so components.css continues to win. */
.hba-meta        { color: #6a7282; }
.hba-kicker      { color: #fe9a00; }
.hba-placeholder { color: #99a1af; }
:where(.hba-field-charcoal, .hba-field-navy) .hba-kicker { color: #ffb900; }
.hba-split__copy :where(.hba-body) { color: #374151; }

/* ================================================================== anchor ===
 * `hba-anchor` is the empty in-page scroll target the composition drops above the
 * join form so `/become-a-member#application` lands somewhere. site.css already
 * gives `:target { scroll-margin-top: 110px }` — the fixed header's clearance — so
 * the BEHAVIOUR is covered and nothing here changes it. What this owns is making
 * sure the marker cannot contribute a box: a Bricks div with no content still gets
 * a line box in a flex column, which would open an unmeasured gap between the
 * option columns and the form head. No live counterpart (live's anchor is an `id`
 * on a real section), so nothing here is measured — it is a zero-size marker by
 * construction. */
.hba-anchor {
  display: block;
  height: 0;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

/* ============================================================== focus ring ===
 * ACCESSIBILITY FLOOR. There is currently NO focus indicator anywhere on the
 * site — measured on an interactive element with :focus-visible true and
 * borderColor / boxShadow / outlineWidth all unchanged from rest. A keyboard
 * user cannot see where they are. INTERACTIONS.md I-10 and tokens.json
 * a11y.focusRing both say the ring is real and must be carried.
 *
 * I-10 records the SHAPE of the live register (borderColor + boxShadow +
 * outlineWidth all change on focus, transition `color, box-shadow` 150ms
 * standard) but not its values, and the capture never entered a focused state on
 * a link or a button — only on form fields. So the ring's GEOMETRY IS AUTHORED,
 * NOT MEASURED; its two colours are site tokens.
 *
 * TWO-TONE ON PURPOSE. amberInk #ffb900 against #ffffff is 1.66:1 and fails
 * WCAG 1.4.11 on the white and offWhite fields; charcoal #0d1b2a against the
 * charcoal field is invisible. A charcoal keyline inside an amber ring means one
 * of the two always carries the contrast, on all five fields including the amber
 * band. box-shadow paints the 0..2px band (inside the outline's offset gap), the
 * outline paints 2..5px.
 *
 * Written at (0,1,1) — element + pseudo-class — rather than :where()-reduced,
 * because a component must not be able to lose this by accident. Per-component
 * focus rings are deliberately NOT authored; everything inherits this one. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid #ffb900;
  outline-offset: 2px;
  box-shadow: 0 0 0 2px #0d1b2a;
}

/* I-10's third channel: form fields also move their border on focus, and carry
 * the 150ms colour/shadow transition. shadow.field at REST belongs to whoever
 * styles the form itself (components.css) — only the focus register is set here,
 * so the two do not collide. motion.css loads last and damps this under
 * prefers-reduced-motion. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  border-color: #ffb900;
}
input,
select,
textarea {
  transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1),
              border-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
              box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* ============================================================== responsive ===
 * COLUMNS ONLY. Nothing below may touch padding-block: the eight section
 * registers are byte-identical at 390, 768 and 1440 and the battery asserts it
 * across 40 sections x 10 routes. Vertical rhythm does not respond to width on
 * this site.
 *
 * Breakpoints are the container-ladder boundaries (tokens.json
 * layout.breakpoints), confirmed by pixel-probing column counts either side.
 */
/* 640 is NOT a container-ladder boundary — it is Tailwind's `sm`, and it is here
 * because live's form grid breaks on `sm:grid-cols-2` and nothing else on the
 * site does. Using the site's 768 instead would leave the join form single-column
 * across a 128px band where live is already paired. Sourced, not rounded. */
@media (min-width: 640px) {
  .hba-formrow {
    flex-direction: row;
    align-items: flex-start; /* labels are not the same height once one wraps */
  }
}

@media (min-width: 768px) {
  /* Both grids flip at md. grid--3 goes STRAIGHT to three columns here — it does
   * NOT pass through a two-column step. See the GRID--3 note above: 5 of the 7
   * are `md:grid-cols-3`, only 2 are `md:grid-cols-2 lg:grid-cols-3`. An earlier
   * draft had the 2-col intermediate because the width it was pixel-probed at
   * (/benefits, 768) happens to be one of the two minority grids. */
  .hba-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hba-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .hba-split   { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* H `lg:grid-cols-3` + col-span-1 / col-span-2, expressed as the same geometry
   * without needing span classes on the children. */
  .hba-optioncols { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }

  /* The type ramp's one step. h1 36/40 -> 48/48 and the blockquote 24/33 ->
   * 30/41.25, both at the 1024 container boundary. h2 does not step — see the
   * HEADING GAP note. */
  .hba-heading:where(h1) { font-size: 48px; line-height: 48px; }
  .hba-quote { font-size: 30px; line-height: 41.25px; }
}
