/* ============================================
   edition.css | SitesHive™ House Edition
   Version: v0.46.2-dev
   ============================================
   v0.45.0-dev MINOR — Layer 2: the marketing site OPTS OUT of the 2026-09-08
   surface brightening. --color-surface returns to --neutral-900 oklch(0.158)
   [13,13,13]; the shared tokens.css keeps --neutral-875 [19,19,19] and is NOT
   touched (TIER 1, App-canonical). That remap was a DASHBOARD decision and
   siteshive.com only darkened because the token is shared. ⚠ This is an
   INTENTIONAL divergence from the App repo's edition.css of the same name —
   two products, one token, different answers, which is what the edition layer
   is for. The eight derived aliases follow automatically (verified moving, not
   assumed); .on-dark is independent by construction; all five text rungs keep
   their values and every contrast ratio against surface IMPROVES, subtle
   4.640 -> 4.854. Reaches the two 'siteshive'-edition installs only.
   ============================================
   v0.44.1-dev PATCH — COMMENT-ONLY. Re-measures the mobile header CTA after the
   App label changed to "Log in", which the block itself demanded. Slack at 360px
   goes 5.69px -> 18.69px, so no header.css change is needed; the number now
   carried is the 59.00px label CEILING rather than the slack. The "My Hive"
   53.31px reference is KEPT as a control — the new harness reproduces it to
   0.0025px, which is what makes the new figures evidence. Records that the
   webfont trap was reproduced deliberately and is still live: a probe against the
   rendered production page measured the documented 55.14px FALLBACK width with
   fonts.check() false. "Dashboard" would overflow by 13.86px.
   ============================================
   v0.41.1-dev PATCH — --hero-lead-measure 44ch -> 43ch, re-derived by sweeping
   the value in a browser after a computed window proved wrong. See the note at
   the declaration.
   ============================================
   v0.41.0-dev MINOR — REVERTS v0.40.0-dev. --header-menu-chevron-display: none is restored.
   ============================================
   The house edition — SitesHive's own brand skin.
   Loaded LAST after tokens.css + all component CSS
   so its :root and component-scoped declarations
   override any earlier values via plain cascade order
   (no !important required).

   Scope (v0.3.0-dev onward — per the "Edition ownership"
   RD in CLAUDE.md): per-edition overrides may land
   across ALL THREE LAYERS:
     • Layer 1 — palette primitives (brand / interactive
       / status / neutrals / font families) AND structural
       primitives (spacing, radius, motion, type scale,
       etc. — anything declared in tokens.css can be
       overridden per edition)
     • Layer 2 — alias VALUE overrides (the locked alias
       NAMES still can't be renamed, but their values can
       be re-aliased per edition)
     • Layer 3 — component-scoped overrides (e.g.,
       `.hero { --hero-bg-image: url(...); }`, etc.)

   Selection: SITESHIVE_EDITION constant in wp-config.php,
   read by siteshive_edition() in functions.php.
   Defaults to 'siteshive' (this file) when unset.

   Status: TIER 1 with the Site-canonical exception — Site
   is source of truth; App mirrors. See "Edition ownership —
   Site-canonical exception to Sacred Rule #16" RD in
   CLAUDE.md for the sync direction (Site → App, reversed
   from default Shared Core flow).

   Cross-blueprint scope (Layer 1 shared / Layer 3 Site-only,
   scope refinement 2026-06-08):
     • Layer 1 palette mirrors to App byte-identical — same
       brand identity rendered both sides.
     • Layer 3 entries below (.hero / .footer / .page--auth
       bg-images + .header chevron-display + .header submenu
       divider rules + the back-to-top shape selection) are Site-unilateral. App's render
       surface doesn't include those selectors, so any Layer
       3 entries here are inert on App and intentionally NOT
       mirrored there. App's edition.css carries Layer 1 +
       a header narrative documenting this model; nothing
       more. Mirroring Layer 3 across is documentation
       theater — selectors that never resolve render no rules.

   Naming: chromatic Layer 1 primitives use the edition-
   neutral "-base" suffix; the inline comment is the plain
   color name. Neutrals carry their Layer 2 surface role
   inline. Pure white is the --color-white constant in
   tokens.css (not an edition concern). Convention from the
   post-v3.0.0 "Edition primitive naming + commenting
   convention" RD remains in force; only the scope
   restriction in that RD is superseded.
   ============================================ */

:root {

  /* Layer 1 — Brand primitives (theme-independent, swap per client).
     TRUE brand (LOCKED, verified): Sea Green #2E8B57 = oklch(0.5685 0.1187 154.95),
     Medium Sea Green #3CB371 = oklch(0.6841 0.1441 155.00). The earlier
     oklch(0.549 0.130 153)=#1D864B / oklch(0.677 0.150 154)=#35B16B were DRIFT
     (too dark + oversaturated) — corrected; do NOT restore the drift values. */
  --brand-base:   oklch(0.5685 0.1187 154.95); /* Sea Green #2E8B57 (true brand) */
  --brand-accent: oklch(0.6841 0.1441 155.00); /* Medium Sea Green #3CB371 (true brand) */

  /* Layer 1 — Interactive primitives (theme-independent). Primary = the true Sea
     Green, secondary = Medium Sea Green (see the brand block re: the corrected
     drift). Primary is kept as-is — NO decoupled darker variant. */
  --primary-base:   oklch(0.5685 0.1187 154.95); /* Sea Green #2E8B57 (true brand) */
  --secondary-base: oklch(0.6841 0.1441 155.00); /* Medium Sea Green #3CB371 (true brand) */

  /* Layer 1 — Status primitives (theme-independent) */
  --info-base:    oklch(0.66 0.16 240); /* blue */
  --success-base: oklch(0.62 0.16 150); /* green */
  --warning-base: oklch(0.86 0.17 95);  /* amber */
  --danger-base:  oklch(0.63 0.21 27);  /* red */

  /* Layer 1 — Neutral primitives (lower = lighter, higher = darker; gaps reserved) */
  --neutral-50:   oklch(0.969 0 0); /* light: surface-elevated; dark: surface-inverse */
  --neutral-100:  oklch(0.937 0 0); /* light: surface */
  --neutral-200:  oklch(0.905 0 0); /* light: page background */
  --neutral-700:  oklch(0.290 0 0); /* reserved */

  /* ⚠ TWO RUNGS MINTED FOR THE DARK SURFACE PAIR (approved 2026-09-06). They are
     UNCONSUMED until tokens.css points at them, so adding them moves no pixel.

     WHY THEY EXIST: the approved dark surface pair is sRGB [24,24,24] / [35,35,35]
     and NEITHER lands on an existing rung -- --neutral-900 is [13,13,13] and
     --neutral-800 is [27,27,27]. App carried them as LITERALS in tokens.css v3.12.0
     with a note saying the durable home was a pair of rungs here, because this file
     is Site-canonical whole-file under the Sacred Rule #16 EXCEPTION and App cannot
     mint them itself. This is that home.

     WHY THE VALUES ARE THESE. For an ACHROMATIC colour the OKLab transform collapses
     to L = cbrt(Y_linear) -- the L row of the matrix sums to 1 -- so these are SOLVED,
     not fitted. The model was checked against the rungs already here before being
     trusted: L 0.158 -> 13 and L 0.222 -> 27, both exact. Round-tripped, oklch(0.209)
     is [24,24,24] and oklch(0.256) is [35,35,35].

     WHY THE NAMES WORK. The ramp runs lower = lighter, and both sit where their number
     says. 750 falls between 700 (0.290) and 800 (0.222) -- and lands on its EXACT
     midpoint, 0.034 from each, so the ramp is more regular there afterwards than
     before. 850 falls between 800 (0.222) and 900 (0.158). It sits nearer 800 than 900
     (0.013 against 0.051), and that is fine: the number is an ORDINAL POSITION in the
     ramp, not an interpolation of lightness. This scale never was linear in L -- the
     existing steps are 0.068, 0.064, 0.053 -- so reading 850 as "half way in value"
     would be a promise the convention has never made. What it does promise is that
     850 is darker than 800 and lighter than 900, which holds.

     ⚠ MINTED IN ALL FOUR EDITIONS, NOT JUST THE HOUSE ONE. Every edition declares its
     own neutral ramp, so the moment tokens.css references --neutral-850 an edition
     lacking it resolves to an INVALID declaration -- and --color-surface is the page's
     own card colour. All four ramps are identical in the dark range today, which is why
     one value serves all four; an edition that later diverges recomputes its own. */
  --neutral-750:  oklch(0.256 0 0); /* [35,35,35] */
  --neutral-800:  oklch(0.222 0 0); /* [27,27,27] */
  --neutral-850:  oklch(0.209 0 0); /* [24,24,24] */
  --neutral-875:  oklch(0.187 0 0); /* [19,19,19] — minted 2026-09-08, see tokens.css v3.16.0 */
  --neutral-900:  oklch(0.158 0 0); /* [13,13,13] */
  --neutral-950:  oklch(0.105 0 0); /* [4,4,4] */

  /* Layer 1 — Font families */
  --text-family-sans:     "Geist Sans", "Inter", system-ui, sans-serif;
  --text-family-heading:  "Tomorrow", "Geist Sans", system-ui, sans-serif;
  --text-family-mono:     "Geist Mono", "JetBrains Mono", monospace;
}

/* Layer 2 override — dark label on brand fills (a WCAG-AA COROLLARY of the true
   brand green, NOT a discretionary style choice). White text on the true Sea
   Green primary (#2E8B57) measures ~4.25:1 and FAILS AA for normal text; black
   measures ~4.95:1 and passes. We keep the true brand green as the fill (no
   decoupled darker primary) and invert the label to pure black instead. IDLE
   only: --color-text-on-primary-hover already resolves a dark near-black label
   that passes on the lighter hover fill (--color-primary-hover = #3CB371, ~6.4:1),
   so only the idle token needs inverting. Covers every --color-text-on-primary
   consumer — primary buttons, the skip-link, and the pricing featured/savings
   badges — all on green fills. Applies to every edition whose primary is this
   light (siteshive + blueprint-live); mgrwcorp's Clear Emerald passes with white
   and keeps the tokens.css default. */
:root {
  --color-text-on-primary: var(--color-black);
}

/* Layer 2 — MARKETING OPTS OUT OF THE 2026-09-08 SURFACE BRIGHTENING.
   --color-surface returns to --neutral-900 oklch(0.158) [13,13,13]; tokens.css
   v3.16.0 remapped the shared alias to --neutral-875 oklch(0.187) [19,19,19].

   WHY, and this is the part that must not be tidied away as redundant: THAT
   REMAP WAS A DASHBOARD DECISION. It was argued and approved on App stat cards
   that were not separating from the page, and it is right there. siteshive.com
   darkened along with it purely because tokens.css is TIER 1 and shared — the
   marketing site was never the subject of the complaint, and a near-black page
   is the house look it was designed around. So this is not a disagreement about
   the value; it is two products with the same token wanting different answers,
   which is precisely what the edition layer exists to absorb.

   ⚠ THE DIVERGENCE THIS PARAGRAPH ANNOUNCED IS OVER, AND THE CLAIM IS RETIRED
   RATHER THAN DELETED. It read, 2026-09-09: "INTENTIONAL DIVERGENCE ... The App
   repo's editions/siteshive/edition.css deliberately does NOT carry this override:
   siteshive.app keeps --neutral-875." That was TRUE WHEN WRITTEN. It is FALSE as of
   App's own v0.7.0-dev, which pins --color-surface: var(--neutral-900) for the same
   reason this file does. App RECORDED that it had made this note stale and said the
   correction belonged in this repo, because a cross-repo claim that has quietly
   become false is exactly the kind of thing nobody goes looking for. This is that
   correction.
   CURRENT STATE: both editions carry the same Layer 2 override, for the same reason,
   so a sync round finding them AGREEING here has found the intended state. They
   still differ elsewhere and are NOT byte-identical files; only the Layer 1 ramp is.
   identical to App's per Sacred Rule #16; only this Layer 2 alias diverges, and
   Layer 2 value re-aliasing per edition is explicitly in scope for this file
   (see the layer scope in the header) and permitted by Sacred Rule #1, which
   allows a Layer 2 value to be reassigned to a different primitive.

   ⚠ tokens.css IS NOT TOUCHED AND MUST NOT BE. It is TIER 1 / App-canonical
   (Sacred Rules #1 + #16); the fix for a Site-only preference is an edition
   override, never an edit to the shared contract.

   THE NINE DERIVED ALIASES FOLLOW THIS AUTOMATICALLY and were verified moving,
   not assumed to: glass, hover, active, disabled, the FOUR borders (subtle, default, emphasis, strong) and
   skeleton are all color-mix()es of --color-surface declared on :root in
   tokens.css, and this declaration is also on :root, so substitution resolves
   against the winning value on the same element. MEASURED in a browser against
   the real files (sRGB via canvas readback, because Chrome reports these
   computed values in oklch and a naive parse of those numbers as RGB produces
   nonsense ratios): surface [19,19,19]→[13,13,13], glass [10,10,10]→[7,7,7],
   hover [26,26,26]→[20,20,20], active [34,34,34]→[28,28,28], border
   [42,42,42]→[36,36,36], border-subtle [28,28,28]→[22,22,22], border-emphasis
   [67,67,67]→[61,61,61], skeleton [33,33,33]→[27,27,27].

     ⚠ THAT READBACK COVERS SEVEN OF THE NINE, AND THE TWO IT OMITS ARE NAMED RATHER
     THAN QUIETLY ROUNDED UP. --color-surface-disabled is literally `var(--color-surface)`,
     so it takes the parent's values exactly ([19,19,19]→[13,13,13]) BY DEFINITION rather
     than by measurement. --color-border-strong did not exist when that session ran —
     tokens.css v3.17.0 minted it — and it moves by construction like its three border
     siblings; it has NOT been browser-verified and is not claimed to have been.

     ⚠ THE COUNT WAS ACCIDENTALLY RIGHT BEFORE IT WAS CORRECT, WHICH IS WHY THIS
     PARAGRAPH EXISTS. Before --color-border-strong was minted the border-inclusive set was
     EIGHT and this line already said NINE, i.e. it was simply wrong. The mint then made the
     DIGIT true while leaving the LIST one short. A number that is accidentally correct for an
     unmeasured reason is indistinguishable from a verified one, and only re-deriving the set
     tells them apart. RE-DERIVE ON EVERY tokens.css CHANGE; do not trust the digit.
     The authoritative set, read off tokens.css rather than remembered: glass, hover, active,
     disabled, border-subtle, border, border-emphasis, border-strong, skeleton.

   ⚠ .on-dark IS NOT AFFECTED AND NEEDS NO COMPANION DECLARATION — checked,
   because the declaration-site-bound var() behaviour recorded in this repo makes
   that the obvious hazard. It re-anchors --color-foreground/--color-background
   to white/black on a DESCENDANT and re-derives the text ramp there from those,
   never from --color-surface, so it is independent of this override by
   construction rather than by luck.

   CONTRAST, computed rather than assumed, since a surface change moves every
   text PAIR without moving a single text token (the Sacred Rule #8 incident).
   The text ramp is anchored to --color-background, so all five rungs are
   byte-identical before and after; every ratio against surface IMPROVES:
   text 16.304→17.055, soft 10.653→11.143, muted 7.814→8.174,
   subtle 4.640→4.854, emphasis 18.580→19.436. The AA-critical rung is subtle,
   and it gains headroom rather than spending it. Elevation separation
   (surface-elevated : surface) also improves, 1.1822→1.2366, so the modal-over-
   card gap the brightening round was protecting is not harmed here — it is the
   surface:page ratio that falls, 1.1034→1.0549, which IS the intended effect.

   ⚠ REACH — this override lands on installs running SITESHIVE_EDITION
   'siteshive' ONLY, i.e. siteshive.com and site.siteshive.dev. blueprint-live,
   gretatravel and mgrwcorp each run their OWN edition file and do not inherit
   from this one.

   ⚠ CORRECTED 2026-09-09, TWO ERRORS, BOTH IN ONE SENTENCE. It read that those
   three "are unaffected today ... each will darken on the deploy that levels it
   up". FIRST, THE DIRECTION WAS INVERTED: v3.11.0 maps surface to --neutral-900
   oklch(0.158) and v3.16.0 maps it to --neutral-875 oklch(0.187), which is
   LIGHTER, so levelling an install up BRIGHTENS it — the override is what keeps
   it dark, not what darkens it. SECOND, AND THE REASON THE LIST WAS THE WRONG
   INSTRUMENT: it enumerated installs BY NAME and so missed frame.siteshive.site
   entirely, which runs SITESHIVE_EDITION='blueprint-live' and is NOT held — it
   was already serving oklch(0.187) when this paragraph claimed nothing outside
   the house edition was affected. That is footgun 37: a census with a subject
   finds things about its subject. ENUMERATE BY EDITION CONSTANT, NEVER BY
   INSTALL NAME — the constant is what the loader reads.

   STANDING AS OF 2026-09-09, read from each wp-config rather than inferred:
   'siteshive' → siteshive.com, site.siteshive.dev (this file).
   'blueprint-live' → blueprint-live.siteshive.dev AND frame.siteshive.site (THE
   CLONE SOURCE) — both now covered by editions/blueprint-live/edition.css
   v0.7.0-dev. 'gretatravel' and 'mgrwcorp' → one install each, still UNCOVERED;
   both are held on tokens.css v3.11.0 and will BRIGHTEN on the deploy that
   levels them up unless their own edition gets the same override in that pass. */
/* Layer 2 — AND THE ELEVATED SURFACE, PINNED 2026-09-10. A REVERT, NOT AN IMPROVEMENT.
   --color-surface-elevated returns to --neutral-800 oklch(0.222) [27,27,27]; tokens.css
   v3.16.0 remapped it to --neutral-750 oklch(0.256) [35,35,35] in the same round that moved
   --color-surface, and NOTHING PINNED IT BACK. The target is exactly what these installs
   rendered before that lift, which is why the acceptance is an equality against a
   pre-change capture rather than a judgement about the new value.

   ⚠ THE ORIGINAL PIN WAS PARTIAL AND THAT IS THE LESSON. The 2026-09-09 override
   named --color-surface and stopped, because that was the alias the complaint named.
   --color-surface-elevated is a SEPARATE alias on a SEPARATE rung: it is not a derived
   token, so it does not follow its parent, and an opt-out written for one of them silently
   left the other brightened on four live installs for two days. MEASURED before this edit,
   off the served CSS chain on all four: surface --neutral-900 [13,13,13] (pinned, correct)
   beside surface-elevated --neutral-750 [35,35,35] (unpinned, lifted).

   WHAT IT FEEDS, so the blast radius is stated rather than implied: EIGHT declarations in
   css/ consume it directly — .card (--card-bg-alt), .card__icon, .card--accent,
   .card--muted, .plans__toggle-switch, .plans__savings-badge, and three card-hex rules.
   It derives NOTHING inside tokens.css, so unlike --color-surface it has no subtree.

   ⚠ tokens.css IS NOT TOUCHED AND MUST NOT BE — TIER 1 / App-canonical, same as
   the sibling override above. The Site-only preference belongs in the edition.

   ⚠ DEAD DEFAULTS: tokens.css's OWN VALUES FOR THESE TWO ALIASES ARE UNREACHABLE ON A
   SITE INSTALL AND MUST NOT BE READ AS WHAT A SITE SURFACE RENDERS. Annotated as a
   decision 2026-09-10. tokens.css maps --color-surface → --neutral-875 and
   --color-surface-elevated → --neutral-750. Every Site edition overrides BOTH back:
   this file and blueprint-live do so here, and gretatravel/mgrwcorp take the same pair at
   their catch-up. So on every Site install the shared values resolve to nothing a visitor
   ever sees. THEY REMAIN LIVE ON APP, which is the product the 2026-09-08 remap was decided
   for, so they are not wrong — they are simply not ours.

   THE HAZARD IS A FALSE READING, NOT WASTE. A later round opens tokens.css, sees
   --neutral-875, and concludes that is a Site surface. It is not, on any install. Same shape
   as the rung comments that stated a ROLE instead of a lightness: a value that looks
   authoritative and is overridden everywhere. Read the EDITION for what a Site renders.

   ⚠ DO NOT 'FIX' THIS BY REPOINTING THE TIER 1 DEFAULT. That is the real repair and it
   is a Sacred Rule #1 value change across the whole estate, including App. It gets its own
   round AFTER the fleet is unified, with a before/after measurement proving it is a no-op
   on every install. See the follow-up carrying that exit condition. */
:root {
  --color-surface:          var(--neutral-900);
  --color-surface-elevated: var(--neutral-800);
}

/* House button hover glow — opt-in via the blueprint's --button-{primary,
   secondary}-shadow-hover knobs (buttons.css v1.7.0, default none → fleet-inert).
   A soft green bloom that fades in on hover, reusing the established .links-glow
   form (blur only, no offset, color-mix in oklch off the HOVERED colour at 40%
   alpha, --space-m blur). BOTH primary and secondary hover to a solid green fill
   (#3CB371 — --color-primary-hover / --color-secondary respectively), so the same
   bloom applies to each (keyed off its own hover-fill token). box-shadow is in
   --button-transition so none → glow fades. Dark-first: the bloom reads best on
   the near-black page; on light it is subtle by design (a faint green halo — tune
   the alpha/blur here if it needs more presence). Secondary consumers incl. both
   footer CTAs (.footer-main__cta + .footer-main__subscribe-button). */
:root {
  --button-primary-shadow-hover:
    0 0 var(--space-m) color-mix(in oklch, var(--color-primary-hover), transparent 60%);
  --button-secondary-shadow-hover:
    0 0 var(--space-m) color-mix(in oklch, var(--color-secondary), transparent 60%);
}


/* ==============================
   Layer 3 — Component-scoped overrides (house edition)
   ============================== */

/* Two-tier house display-heading tracking, both fallback-at-element (unset →
   --text-tracking-normal 0, so non-house editions are unaffected), both declared
   once here on :root:
     DISPLAY --text-heading-tracking (-0.06em) → h1/h2, .section-header__title
       (36px → -2.16px), .text-headline (36px), .plans__price-display (36px).
     SUB     --text-heading-tracking-s (-0.04em) → h3-h6, .plans__name
       (28px → -1.12px), bare split-column headings, hex card titles.
   Rationale: tracking is a property of type SIZE — smaller type needs
   proportionally LESS negative tracking; a single value across both tiers read
   correct at 36px and too tight at 28px on live review (Round 3 → Round 4 split).
   The display -0.06em ≈ the hero's -0.04em × 52px = -2.08px, so h1/h2 + the 36px
   titles share the hero's optical kerning weight. .hero__title keeps its own
   --hero-title-tracking: -0.04em (separate knob — larger size). Layer 3,
   house-only — NOT mirrored to App. */
:root {
  --text-heading-tracking:   -0.06em;
  --text-heading-tracking-s: -0.04em;
}

/* House-edition component-scoped overrides — two semantic groups:

     (a) OPT-IN per-edition affordances. Blueprint defaults are `none`
         (Sacred Rule #10 — built once, deployed many; brand-neutral
         + clean defaults so client installs don't carry SitesHive-
         specific decoration or affordance). This edition opts IN to:
           • Branded decorative SVGs on hero + footer + auth pages
             (per hero.css v2.7.5 + footer.css v1.13.4 + login.css
             v2.15.1 brand-neutralization)
           • Depth-2 submenu first-child visual divider — the line +
             spacing that groups the lead item visually from the
             rest (per header.css v1.10.1 polarity flip from opt-out
             to opt-in; SitesHive-specific affordance, restored here
             only on the SitesHive feature site)

     (b) OPT-OUT chrome affordance — chevron only. Blueprint default is
         visible; this edition opts OUT. Removed at v0.40.0-dev, RESTORED at
         v0.41.0-dev once header.css v2.6.0 made the trigger cost no layout
         width — see the note at the declaration site.

   Both groups land per the v0.3.0-dev scope expansion in the "Edition
   ownership — Site-canonical exception to Sacred Rule #16" RD in
   CLAUDE.md. Per-client editions inherit the blueprint defaults — they
   get the bg-images NONE, the submenu divider NONE, and the chevron
   VISIBLE — until they opt in or out themselves. */
/* Decorative hero background — HOUSE-ONLY (relocated from the blueprint
   hero.css at its v3.0.0, so client editions get a clean .hero::before free
   for a Bricks Background Overlay). The three --hero-bg-* vars + the
   .hero::before pseudo live here verbatim; .hero's `position: relative` +
   `overflow-x: clip` still come from hero.css. Geometry: the SVG anchors on
   the container's inner-left edge (max() with --gutter on narrow viewports),
   72rem wide, bleeding 50% left / 50% behind .hero__content, z-index 0
   beneath .hero__layout (z-index 1). Layer 3, Site-unilateral — NOT mirrored
   to App (App has no .hero surface). */
.hero {
	/* Hero lead measure. 44ch, down from the blueprint 60ch (css/hero.css).
	   HOUSE-ONLY, and deliberately NOT a blueprint default: 44ch is at the
	   narrow floor of the readable 45-75ch band, and it is tuned to THIS copy.
	
	   WHAT IT FIXES. The lead ended on a 17-character orphan line at desktop.
	   Narrowing the measure redistributes the same words so the last line
	   carries real weight. MEASURED at 360 / 390 / 430 / 768 / 1440, both
	   installs, characters per rendered line:
	                        1440 before   1440 after
	     siteshive.com      65/70/17  ->  54/59/39
	     site.siteshive.dev 60/68/17  ->  60/57/28
	
	   IT BINDS AT 1440 AND NOWHERE ELSE, and that is geometry, not luck: the
	   hero content column is one half of a 2-col grid, so it is NARROWER than
	   44ch at every width below 1440 (measured column 328 / 358 / 395.6 / 337.3
	   against a 44ch box of 466.8px at 16px and 583.4px at 20px). All four
	   sub-1440 widths render BYTE-IDENTICAL before and after, including the
	   390px 17-character last line, which is ACCEPTED and is a different line
	   from the desktop one this fixes.
	
	   WARNING: THE TWO INSTALLS' LEAD COPY DIVERGES - 147 chars on dev, 154 on
	   prod ("professional websites ... grow online" vs "custom websites ...
	   look professional online"). One value, two line shapes, both acceptable.
	   Re-measure after any copy edit; a measure tuned to a string is only as
	   stable as the string.
	
	   NO text-wrap, and that is a decision rather than an omission: `balance`
	   OVERRIDES this measure and re-breaks the line itself, costing the shape
	   the value exists to produce; `pretty` targets single-word orphans and the
	   trailing run here is two words, so it measured inert. */
	/* MEASURED IN A BROWSER, not computed — and the computed value was wrong.
	   Sweeping the measure on the live page at 768 and 1440 gives an identical
	   window at both: 39-41ch -> 49/51/52 ("help" drops), 42-44ch -> 54/53/45
	   (the target), 45-48ch -> 54/59/39 ("while" rides up). 43ch is mid-window.

	   ⚠ WHY THIS IS MEASURED: a Phase A derivation put the window at
	   [39.21, 41.48)ch and it was WRONG, because it took the ch unit from
	   fontTools' advance for "0" (0.663em) while the browser computes ch as
	   0.621em — a ~6.4% error, enough to shift the window past itself. It
	   briefly shipped as 40ch and rendered 49/51/52. Font metrics read out of
	   a file are not the metrics the browser uses; sweep the value instead.

	   It only BINDS above the clamp's 20px ceiling, where the column keeps
	   growing but the type stops. Below that the box is the container and the
	   3.8cqi ratio holds the shape on its own. */
	--hero-lead-measure: 43ch;
  --hero-bg-image: url("/wp-content/uploads/siteshive-bg.svg");
  --hero-bg-size: 72rem;
  --hero-bg-inset-block-start: calc(-1 * var(--space-4xl));
  /* Hexagon comb tile — UX-tuned constants, partial-pattern precedent
     (Rule #13). 3.8rem/6.6rem == 38px/66px at the 62.5% (10px) root. */
  --hero-comb-tile: 3.8rem 6.6rem;

  /* Radial falloff center-x for the comb mask. UX-tuned constant,
     partial-pattern precedent (Rule #13). Pushed right of center so the
     high-alpha region sits over .hero__media rather than the .hero__content
     text column. */
  --hero-comb-x: 75%;

  /* Comb desaturation. Dark runs 0.5 (half-grey) so the grid reads as
     texture instead of competing with the brand green doing signal work
     elsewhere in the hero; light overrides to 0 (green preserved) — green
     already optically desaturates against a near-white ground, so it needs
     no help there. UX-tuned constant, partial-pattern precedent (Rule #13). */
  --hero-comb-gray: 0.5;

  --hero-comb-opacity: 0.12;

  /* House-only headline kerning (Layer 3, NOT mirrored to App). Blueprint default
     stays var(--text-tracking-tight) (-0.02em) — tracking is typeface-dependent
     and the house heading face is Tomorrow. em-based so it scales proportionally
     across the 36→48px title clamp (a fixed -2px would be -0.042em at 48px but
     -0.056em at the 36px floor). REVERSES the hero.css v3.4.1 repoint (-0.04em →
     --text-tracking-tight, "too tight at the h1 size") — deliberate, and it lands
     in the EDITION not the blueprint, so no fleet site is affected. The -0.06em
     literal (tightened -0.04em → -0.05em → -0.06em across v0.32.2/.3-dev — visual
     review at the 48px clamp ceiling: now -2.88px) is a Rule #13 UX-tuned constant
     in a Layer 3 edition file; do NOT re-open the retired --text-tracking-tighter
     TIER 1 promotion. */
  --hero-title-tracking: -0.06em;

  /* House headline ceiling 5.2rem/52px. 5.2rem is NOT a token and is NOT a TIER 1
     candidate — the scale runs --text-4xl 4.8 → --text-5xl 6.4, and a 5.2 rung
     would break the top-end ratio for one site's preference. Sanctioned Layer 3
     house literal (Rule #12 edition exception), same class as the -0.04em tracking
     literal above. Middle re-anchored for the raised ceiling: floor --text-3xl
     (36px) at container 368px, ceiling 52px at container ~1141px (= 1240px
     viewport). Verified @368=36.0, @554=39.9, @1141=52.0, @1320 (container capped)
     = 55.7 → clamped to 52. Layer 3, house-only — NOT mirrored to App. */
  --hero-title-size: clamp(var(--text-3xl), 2.84rem + 2.07cqi, 5.2rem);
}
.hero::before {
  content: "";
  position: absolute;
  inset-block: var(--hero-bg-inset-block-start) 0;
  inset-inline-start: max(var(--gutter), calc((100% - var(--content-width)) / 2));
  inline-size: var(--hero-bg-size);
  transform: translateX(-50%);
  background-image: var(--hero-bg-image);
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
  pointer-events: none;
  z-index: 0;
}

/* ==============================
   Hexagon comb — decorative background grid
   ==============================
   Tiled SVG data-URI hex grid on ::after (::before is taken by the
   blur SVG above). Paints ON TOP of ::before at equal z-index (source
   order), UNDER .hero__layout / .footer-main / .footer-utility (z-index 1).
   Paint-only: no animation, no layout, one composited layer per section.
   Hero uses a two-mask composite (radial L/R falloff + linear T/B fade).
   Footer carries a symmetric concave fade over --footer-comb-fade at each
   edge, so the tile grid dissolves at the card's top and
   bottom rather than cutting hard; border-radius: inherit clips the
   left/right edges to the card's rounded corners. Fade distance is absolute,
   NOT a percentage of card height — a percentage resolves
   to less than one tile on a short card and the dissolve collapses back into
   a cut. The ramp is CONCAVE (climbs fast, holds high, releases at
   the edge): a convex ramp spends most of the span at low alpha, so the fade
   region reads as missing grid rather than softening grid.
   Note .footer-main::after sits at z-index -1 (not 0 like the
   hero): .footer-main is already a stacking context, so -1 lands the comb
   above the glass fill and below the content.
   Both combs run filter: grayscale() off a theme-conditional var — dark 0.5,
   light 0. The filter creates a stacking context on the pseudo, which is
   inert here: a negative-z-index child still paints above its parent's
   background and below in-flow content, so .footer-main::after's placement
   is unaffected. */

.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  filter: grayscale(var(--hero-comb-gray));
  /* The raw brand-hex literal inside the data-URI is a knowing, scoped
     exception to Sacred Rule #12: edition files are the documented home for
     house brand literals, and a data-URI string cannot consume a var().
     Mask-image conversion was evaluated and rejected — 3-layer mask +
     mask-composite chain costs more compositing than it saves, and
     -webkit-mask-composite has no `intersect` keyword. (The two-layer fade
     mask below is why the -webkit- twin has to say source-in.) */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='27.94' height='48.38'%3E%3Cg fill='none' stroke='%232E8B57' stroke-width='1'%3E%3Cpolygon points='13.97,-16.13 27.94,-8.06 27.94,8.06 13.97,16.13 0,8.06 0,-8.06'/%3E%3Cpolygon points='13.97,32.25 27.94,40.32 27.94,56.44 13.97,64.51 0,56.44 0,40.32'/%3E%3Cpolygon points='0,8.06 13.97,16.13 13.97,32.25 0,40.32 -13.97,32.25 -13.97,16.13'/%3E%3Cpolygon points='27.94,8.06 41.91,16.13 41.91,32.25 27.94,40.32 13.97,32.25 13.97,16.13'/%3E%3C/g%3E%3C/svg%3E");
  background-size: var(--hero-comb-tile);
  opacity: var(--hero-comb-opacity);
  -webkit-mask-image:
    radial-gradient(135% 115% at var(--hero-comb-x) 40%, #000 0%, rgba(0,0,0,.55) 58%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, #000 14%, #000 78%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    radial-gradient(135% 115% at var(--hero-comb-x) 40%, #000 0%, rgba(0,0,0,.55) 58%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, #000 14%, #000 78%, transparent 100%);
  mask-composite: intersect;
}

/* ── Eyebrow status dot (house "live" indicator) — RELOCATED from hero.css at its
   v4.0.0 (this edition v0.30.0-dev). House-only Layer 3 art: it was shipping dormant
   to every fleet install from the TIER 2 shared hero.css. The generic .siteshive-menu
   / .siteshive-menu__link shortcode styling STAYS in hero.css (any edition using the
   shortcode wants it); only this house art moved. edition.css enqueues AFTER hero.css,
   so the dot + pulse render IDENTICALLY on the house site (zero visual change). NOT
   mirrored to App (no eyebrow menu there). Mirrors the v3.0.0 `.hero::before`
   relocation. The v2.7.0 rationale is preserved below; the pulse + wave mechanism is
   v0.31.0-dev (see the per-keyframe comments).

   v2.7.0: 3D status dot before the FIRST eyebrow item only — pulsing green "live"
   indicator on the SitesHive feature site (currently the only [siteshive_menu]
   consumer). Selector is bare per the v2.5.3 audit (Bricks Shortcode element places
   shortcode output outside the .hero__eyebrow descendant chain, so an ancestor scope
   wouldn't match). Renders dormant on installs that don't author the eyebrow. Color
   path: Layer 2 aliases (--color-brand, --color-brand-accent) per Sacred Rule #12 —
   auto-adapts per SITESHIVE_EDITION. The dot sizes off --space-xs (− 1px = 7px);
   the 6px margin / glow distances / wave SVG (green literal + stroke + scale) + color-mix
   percentages remain raw per Rule #13/#12 partial-pattern — dot-specific, not worth vars. */

/* first-child gets a stacking context (position: relative + z-index: 0) so the ::after
   radar wave (z-index: -1) sits BEHIND the hex and stays contained (not behind the hero).
   v0.31.1-dev: also inline-flex + align-items:center so the hex ::before becomes a flex
   item centred deterministically on the link's cross axis — that centre equals the wave's
   inset-block-start:50%, so hex + wave share one vertical origin. Fixes the wave reading
   slightly high vs the hex (the old vertical-align:middle hex sat below the geometric 50%
   the wave used). Horizontal already coincides (hex left 0 + half-width = the wave's 4px
   centre), so only the vertical anchor needed pinning. */
.siteshive-menu__link:first-child {
  position: relative;
  z-index: 0;
  display: inline-flex;
  align-items: center;
}

.siteshive-menu__link:first-child::before {
  content: "";
  display: inline-block;
  inline-size: calc(var(--space-xs) - 1px);
  block-size: calc(var(--space-xs) - 1px);
  margin-inline-end: 6px;
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  /* v2.7.1: pointy-top hex. Flat-top alt if it matches the logo better:
     polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%) */
  vertical-align: middle;
  background: var(--color-brand-accent);
  /* v0.31.0-dev: the PULSE now cycles the FILL between the two house greens (see
     @keyframes statusPulse below) — brightness() retired: the greens differ mostly
     in LIGHTNESS, so brightness() only washed the same green rather than shifting
     between --color-brand and --color-brand-accent. This subtle STATIC drop-shadow
     keeps the core lit at all times (NOT animated → painted once, no per-frame cost);
     drop it entirely for a fully crisp dot if wanted. Static solid hex, no transform /
     no shape-shift. v0.31.4-dev: sized `--space-xs − 1px` (7px, down 1px — rendered
     natively at 7px so the clip edges stay sharp) + the glow tightened `0 0 2px → 0 0 1px`
     so the soft halo stops bleeding past the edge (a crispness pass). v2.7.1's clip-path
     → filter:drop-shadow swap remains in force (clip-path clips box-shadow, so the glow
     must follow the hex silhouette via drop-shadow). */
  filter: drop-shadow(0 0 1px color-mix(in srgb, var(--color-brand-accent) 55%, transparent));
  animation: statusPulse 3s ease-in-out infinite;
}
/* Pulse = fill cycling between the two house greens — a COLOUR shift (sea green
   #2E8B57 ↔ medium sea green #3CB371), not a brightness swing. background-color is a
   paint property, but the dot is 8px (negligible dirty rect) and it is the only
   mechanism that reads as a token-to-token green shift (hue-rotate wouldn't travel
   between two same-hue greens; an opacity cross-fade would need a spare pseudo, and
   ::before/::after are both taken). */
@keyframes statusPulse {
  0%, 100% { background-color: var(--color-brand); }
  50%      { background-color: var(--color-brand-accent); }
}

/* Radar wave — a single ring expanding out from BEHIND the hex and fading, so the dot
   reads as actively emitting. transform + opacity ONLY (compositor path, no per-frame
   repaint). It fades IN as it emerges from behind the hex (no pop) and OUT as it
   radiates; opacity is 0 at both loop ends → the seam is invisible (continuous). Synced
   to the 3s pulse. The vertical origin is the link's flex centre (see the :first-child
   rule); the horizontal centre is HALF THE DOT SIZE (`calc((--space-xs − 1px) / 2)`), so
   the ring tracks the left-aligned dot's centre at whatever size the dot is (the dot's
   centre = its width ÷ 2 since it sits at flex-start). v0.31.4 REVERTED the v0.31.3 −1px
   nudge (it over-shot left) and instead ties to the dot centre, which moved 0.5px left
   when the dot shrank to 7px — so wave + dot stay concentric with no standalone nudge to
   drift out of sync. The ring is a HEXAGON OUTLINE
   (a `fill:none` stroked polygon SVG data-URI, `contain` in the 8px box), not a circle —
   v0.31.3 its points are a TRUE regular pointy-top hexagon (width ≈ 0.866 × height, like
   the logo / comb hexes), replacing the v0.31.2 squished-1:1 shape that read stretched.
   The 1.8 stroke in the 24-viewBox renders ~0.6px at the 8px box → ~0.83px at the visible
   peak (opacity 0.8 / scale ~1.38); it scales with the transform like any ring, so it
   thins perceptually as it fades out. The green literal `%233CB371` (= --color-brand-accent,
   house) is a Rule #12 data-URI exception — same as the comb SVGs in this file (a data-URI
   can't consume a var); the dot is house-only art. NOTE the dot ::before still uses its
   original squished clip-path — the wave being a true regular hex is intentional per the
   tuning ask. The stroke-width / scale / opacity / 1px nudge are Rule #13 partial-pattern
   constants (dot art; 1px per Rule #12). */
.siteshive-menu__link:first-child::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: calc((var(--space-xs) - 1px) / 2);
  inline-size: var(--space-xs);
  block-size: var(--space-xs);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpolygon points='12,1.5 21.09,6.75 21.09,17.25 12,22.5 2.91,17.25 2.91,6.75' fill='none' stroke='%233CB371' stroke-width='1.8'/%3E%3C/svg%3E") center / contain no-repeat;
  z-index: -1;
  pointer-events: none;
  transform: translate(-50%, -50%) scale(0.6);
  animation: statusWave 3s ease-out infinite;
}
@keyframes statusWave {
  0%   { transform: translate(-50%, -50%) scale(0.6); opacity: 0; }
  30%  { opacity: 0.8; }
  100% { transform: translate(-50%, -50%) scale(3.2); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .siteshive-menu__link:first-child::before { animation: none; }  /* rests at the static accent green + glow */
  .siteshive-menu__link:first-child::after  { display: none; }    /* no wave at all */
}

/* Footer edge fade — a SHORT SYMMETRIC mask that softens BOTH the top and bottom
   boundaries of the glass card, so its fill stops cutting hard against the page
   (top) and against the utility band below (bottom). Newly noticeable at the top
   now the CTA band above dissolves to transparent at its bottom (v0.27) — a soft
   edge immediately followed by a hard one; the bottom gets the same treatment for
   symmetry. This is NOT the CTA band's full-height center-out dissolve: the footer
   is a large content region and stays SOLID through its body (`#000` from
   --footer-fade to 100% − --footer-fade). The mask fades the whole card — fill +
   backdrop blur together — turning each hard fill/blur boundary into a gradient over
   --footer-fade (32px). CLEARANCE is symmetric: the card's padding-block
   (--section-padding-block, mobile floor 40px) sits above the top content (logo/nav)
   AND below the bottom content (the auto-bottom-baselined Contact button + Subscribe
   form, which sit at the content-area bottom), so a 32px fade leaves ≥8px clearance
   at BOTH edges on the tight mobile floor (more on desktop) — content is never
   touched. The comb ::after keeps its own independent edge fade; masking the parent
   just makes the comb a touch fainter at the very edges too (harmless — separate
   masks, separate elements). No stacking-context change: `.footer-main` is already
   one (position: relative + z-index: 1), and `mask` doesn't create another, so
   ::before (0) / ::after (−1) / content (1) layering is unchanged. Independent of
   --brand-dissolve by design — the two cooperate (CTA fades out from above, footer
   fades in from below) but neither reads the other's value. House Layer 3, NOT
   mirrored to App; other editions keep the crisp edge. */
.footer-main {
  --footer-fade: var(--space-2xl);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--footer-fade), #000 calc(100% - var(--footer-fade)), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 var(--footer-fade), #000 calc(100% - var(--footer-fade)), transparent 100%);
}

.footer-main::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  border-radius: inherit;
  filter: grayscale(var(--footer-comb-gray));
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='27.94' height='48.38'%3E%3Cg fill='none' stroke='%232E8B57' stroke-width='1'%3E%3Cpolygon points='13.97,-16.13 27.94,-8.06 27.94,8.06 13.97,16.13 0,8.06 0,-8.06'/%3E%3Cpolygon points='13.97,32.25 27.94,40.32 27.94,56.44 13.97,64.51 0,56.44 0,40.32'/%3E%3Cpolygon points='0,8.06 13.97,16.13 13.97,32.25 0,40.32 -13.97,32.25 -13.97,16.13'/%3E%3Cpolygon points='27.94,8.06 41.91,16.13 41.91,32.25 27.94,40.32 13.97,32.25 13.97,16.13'/%3E%3C/g%3E%3C/svg%3E");
  background-size: var(--footer-comb-tile);
  opacity: var(--footer-comb-opacity);
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    rgba(0, 0, 0, 0.35) calc(var(--footer-comb-fade) * 0.15),
    rgba(0, 0, 0, 0.7) calc(var(--footer-comb-fade) * 0.35),
    rgba(0, 0, 0, 0.9) calc(var(--footer-comb-fade) * 0.6),
    #000 var(--footer-comb-fade),
    #000 calc(100% - var(--footer-comb-fade)),
    rgba(0, 0, 0, 0.9) calc(100% - var(--footer-comb-fade) * 0.6),
    rgba(0, 0, 0, 0.7) calc(100% - var(--footer-comb-fade) * 0.35),
    rgba(0, 0, 0, 0.35) calc(100% - var(--footer-comb-fade) * 0.15),
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0,
    rgba(0, 0, 0, 0.35) calc(var(--footer-comb-fade) * 0.15),
    rgba(0, 0, 0, 0.7) calc(var(--footer-comb-fade) * 0.35),
    rgba(0, 0, 0, 0.9) calc(var(--footer-comb-fade) * 0.6),
    #000 var(--footer-comb-fade),
    #000 calc(100% - var(--footer-comb-fade)),
    rgba(0, 0, 0, 0.9) calc(100% - var(--footer-comb-fade) * 0.6),
    rgba(0, 0, 0, 0.7) calc(100% - var(--footer-comb-fade) * 0.35),
    rgba(0, 0, 0, 0.35) calc(100% - var(--footer-comb-fade) * 0.15),
    transparent 100%
  );
}

/* ── CTA-band honeycomb — the brand tint DISSOLVES center-out, so the band has no
   edge left to be hard: strongest behind the content at the vertical centre, easing
   away to nothing before either boundary (v0.27 supersedes the v0.25 toothed edge
   AND the ①/②/③ tooth-depth trade — there is no cut to trim, so the depth wall is
   gone). TWO decorative layers on .section--brand, both below content (the
   isolation: isolate stacking context contains their negative z-index without a
   section z-index that would fight the sticky header):
     ::before  z-index -2 — the TINT (flat green fill, dissolved center-out)
     ::after   z-index -1 — the interior line-art hex grid (BLACK), same dissolve
   House Layer 3 — NOT mirrored to App. HOOK: .section--brand (the only live
   brand-band hook — Round 4's .final-cta was re-authored to it; today = the Home
   final CTA).

   MECHANISM — ONE shared mask, --brand-dissolve (declared on the host): a to-bottom
   linear-gradient with a quadratic (1−d²) profile — full at 50%, gentle falloff near
   the middle, ACCELERATING to transparent at 0%/100%. BOTH pseudos consume it, so
   the tint fill AND the hex grid fade TOGETHER across the full band height — no
   boundary anywhere to read as a cut. The honeycomb read is carried by the ::after
   grid OUTLINES: whichever cells survive the dissolve are COMPLETE hexagons, never
   cut fragments (the v0.24 trap was a cell-SHAPED fill, which rendered inverted
   pointy-top slivers; outlines don't have that failure mode). --brand-comb-fade
   RETIRED — a full-height dissolve trivially outruns the 66px grid tile, so the old
   edge-depth floor + its tooth/grid coupling no longer exist. Tune the whole falloff
   in one place (the host --brand-dissolve stops).

   ::after STROKE is BLACK, single SVG both themes. On the near-black dark band black
   measures ~1.3:1 — an INTENTIONAL deboss (faint darker grooves), not a failure: it
   gives crisp definition on the pale LIGHT band (where it lands) + a subtle
   surface-break on dark. Lever: the dark --brand-comb-opacity (louder deboss, capped
   ~1.3:1). A CRISP grid on dark would want a theme-conditional light-stroke SVG —
   flagged, not built (single SVG preferred). grayscale RETIRED (meaningless on
   black). */
.section--brand {
  position: relative;
  isolation: isolate;
  background-color: transparent;   /* tint moves to ::before so its edges can be shaped */
  /* ::before fill strength — RAISED from 8% (v0.26). The page renders near-black
     (#040404), so at 8% the band was #070F0B — an 11-unit green window that starved
     the grid below the visibility floor (three geometry passes were invisible because
     of THIS, not the mechanism). 14% puts the band at #0A1710 — a +19-unit green
     window vs the page — the room the ::after grid deboss needs, now the SOLE
     honeycomb carrier (the v0.27 center-out dissolve retired the toothed edge). Grid
     deboss at --brand-comb-opacity 0.3 = groove ≈ 0.7× the band green → ~−7 G on dark
     (was −8 G at 16%), still above the floor (far from the −4 G that was invisible at
     8%); if the live render reads the grid faint, raise --brand-comb-opacity to 0.35
     (restores −8 G). Stepped down 18% (v0.26) → 16% → 14% across live reviews; below
     the old 15%-flat rejection is fine because a TEXTURED band reads as MATERIAL, not
     a flat slab, and this is the CTA. Range ~14–22% (floor: below ~14 the green window
     + grid thin out). */
  --brand-tint: 14%;
  --brand-comb-tile: 3.8rem 6.6rem;   /* ::after grid tile */
  --brand-comb-opacity: 0.3;          /* ::after black-grid deboss (dark). Range ~0.2–0.4. */
  /* Center-out dissolve — the SHARED mask for BOTH the ::before tint and the ::after
     grid. Quadratic ease (1−d²): full at the vertical CENTRE (behind the content),
     gentle falloff near the middle, ACCELERATING to transparent at both edges — so
     the whole band dissolves and there is NO boundary left to be hard. Replaces the
     v0.25 tooth strips + edge fade (--brand-comb-fade RETIRED — a full-height
     dissolve trivially outruns the 66px grid tile, so that floor/coupling is gone).
     Tune the profile here, one place: pull the inner stops toward 50% for a tighter
     core, toward the edges for a broader glow. */
  --brand-dissolve: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(0, 0, 0, 0.44) 12.5%,
    rgba(0, 0, 0, 0.75) 25%,
    rgba(0, 0, 0, 0.94) 37.5%,
    #000 50%,
    rgba(0, 0, 0, 0.94) 62.5%,
    rgba(0, 0, 0, 0.75) 75%,
    rgba(0, 0, 0, 0.44) 87.5%,
    transparent 100%
  );
}

.section--brand::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: -2;   /* below the ::after grid (-1) and the content */
  background-color: color-mix(in srgb, var(--color-primary) var(--brand-tint), var(--color-background));
  -webkit-mask-image: var(--brand-dissolve);
  mask-image: var(--brand-dissolve);
}

.section--brand::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: -1;   /* above the ::before tint, below the CTA content */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='27.94' height='48.38'%3E%3Cg fill='none' stroke='%23000000' stroke-width='1'%3E%3Cpolygon points='13.97,-16.13 27.94,-8.06 27.94,8.06 13.97,16.13 0,8.06 0,-8.06'/%3E%3Cpolygon points='13.97,32.25 27.94,40.32 27.94,56.44 13.97,64.51 0,56.44 0,40.32'/%3E%3Cpolygon points='0,8.06 13.97,16.13 13.97,32.25 0,40.32 -13.97,32.25 -13.97,16.13'/%3E%3Cpolygon points='27.94,8.06 41.91,16.13 41.91,32.25 27.94,40.32 13.97,32.25 13.97,16.13'/%3E%3C/g%3E%3C/svg%3E");
  background-size: var(--brand-comb-tile);
  opacity: var(--brand-comb-opacity);
  -webkit-mask-image: var(--brand-dissolve);
  mask-image: var(--brand-dissolve);
}

/* Light theme: opacity ramp AND full re-saturation. The opacity split is
   asymmetric (dark .12/.086 vs light .095/.057), not proportional — same
   posture as the --color-text-soft dark-20/light-10 rung in tokens.css:
   the correction a theme needs is not always symmetric. The green is kept
   on light deliberately — against a near-white ground it reads as grey
   linework already, so desaturating it costs the brand tint for nothing,
   which is why --*-comb-gray goes to 0 here rather than tracking the
   opacity direction. */
html[data-theme="light"] .hero {
  --hero-comb-opacity: 0.095;
  --hero-comb-gray: 0;
}
html[data-theme="light"] .footer {
  --footer-comb-opacity: 0.057;
  --footer-comb-gray: 0;
}
html[data-theme="light"] .section--brand {
  /* Black grid reads crisp on the pale-green light band, so a LIGHTER touch than
     dark's deboss — keep it subtle to avoid harshness. The tint (::before) is the
     same 16% color-mix, theme-adaptive via the token, so it needs no light knob. */
  --brand-comb-opacity: 0.06;
}

.footer {
  /* House pins the footer text + icon scale DOWN to the 14/16 pairing — the
     blueprint default is now 16px text / 20px icons (footer.css v1.20.0), which
     only the house does not want. Restores the pre-v1.20.0 rendering exactly:
     --footer-text-size --text-s (14px) drives the body/nav/link text AND the
     contact-icon nudge's line box; --footer-icon-size --icon-xs (16px) sizes the
     contact + social icon boxes + the --no-icon gutter; --footer-logo-icon-size
     --icon-xs (16px) sizes the logo mark, and the wordmark derives 6.857rem from
     it (= the old 6.86rem literal's exact 30/7 ratio, paints identically). */
  --footer-text-size: var(--text-s);
  --footer-icon-size: var(--icon-xs);
  --footer-logo-icon-size: var(--control-xs);

  --footer-bg-image: url("/wp-content/uploads/SitesHive-icon-blurr.svg");
  /* Consumed by .footer-main::after (the comb is scoped to the glass card,
     not the utility band). Declared here at the landmark level to sit with
     the other footer edition knobs. */
  --footer-comb-tile: 3.8rem 6.6rem;

  /* Comb desaturation — see the --hero-comb-gray comment. Same dark 0.5 /
     light 0 split. No radial mask on the footer, so no --footer-comb-x
     counterpart. */
  --footer-comb-gray: 0.5;

  /* Comb edge-fade distance. UX-tuned constant, partial-pattern precedent
     (Rule #13). Deliberately absolute rather than a % of card height: the
     fade must outrun the 6.6rem tile, and a percentage stops doing that as
     the card shrinks. Value is paired with the concave ramp weights in
     .footer-main::after — the two are tuned together, and changing the ramp
     shape changes which distances are usable. */
  --footer-comb-fade: 16rem;
  --footer-comb-opacity: 0.086;
}
/* Auth backdrop: the house uses the BLUEPRINT DEFAULT (login.css `:root
   --auth-backdrop-display: grid` — default-visible, automatic per client). The
   hand-made SitesHive-icon-blurr.svg `--auth-bg-image` override was RETIRED at
   v0.32.0-dev in favour of that automatic per-client icon backdrop (the house's
   OWN App-pulled icon, blurred live). No override needed here — falling back to
   the base `--auth-bg-image: none` drops the file, and the default-on backdrop
   supplies the house icon. Documented so a maintainer doesn't re-add the SVG. */
.header {
  /* House pins the nav text + submenu-icon scale DOWN to the 14/16 pairing — the
     blueprint default is now 16px text / 20px icons (header.css v2.3.0), which only
     the house does not want. Restores the pre-v2.3.0 rendering exactly:
     --header-text-size --text-s (14px) for the nav links / trigger / submenu-link
     text; --header-submenu-icon-size --icon-xs (16px) for the submenu ::before
     boxes, both grid first columns, and the flyout pull calc. */
  --header-text-size: var(--text-s);
  --header-submenu-icon-size: var(--icon-xs);

  /* CHEVRON: OPT OUT — no chevron beside the primary-nav parents.
     Restored at v0.41.0-dev after v0.40.0-dev removed it. That round argued
     that hiding the glyph left an 8x8 invisible tab stop failing WCAG 2.5.8,
     and 8px of dead trailing space on five of six items. Both were true; the
     conclusion was not. Showing the chevron made the gap difference LEGIBLE
     (44/44/44/20/44) rather than removing it, which is not what was wanted.
     header.css v2.6.0 fixes the cause instead: the trigger no longer has
     padding, so with the chevron hidden its box is 0 and every text-to-text
     gap is identical, while an .icon--interactive-style ::after keeps a
     24x24 target and carries the focus ring. So this opt-out now costs
     nothing — which is the condition under which it should have shipped. */
  --header-menu-chevron-display: none;
  --header-submenu-divider: 1px solid var(--color-border);
  --header-submenu-divider-gap: var(--space-2xs);
}

/* MOBILE HEADER-BAR CTA — house OPT-IN, gated at >=360px (header.css v2.4.0).
   Surfaces the primary CTA in the collapsed mobile bar so the house header
   isn't logo | toggle | hamburger with no action. `inline-flex` is the .button
   base display (buttons.css:109) — NOT block/inline-block, which drop the
   label centering + .icon slot.

   DECLARATION SITE IS LOAD-BEARING: the knob is declared on .header
   (header.css), so the override must also target .header — a :root override
   would NOT reach it (a declaration on the element itself beats an inherited
   ancestor value). Same specificity (0,1,0); editions enqueue last, so this
   wins on source order. Same shape as --header-menu-chevron-display above,
   which is why this can't just live in the block above: a media query can't
   wrap a declaration, only a rule.

   WHY THE 360px GATE — measured, and the reason is nav, not aesthetics.
   Nothing in the header shrinks (.button is white-space: nowrap;
   .header__logo-wordmark is flex: 0 0 auto), so a CTA that doesn't fit does
   not wrap or truncate: the grid overflows and the HAMBURGER — grid-column 4,
   the last track, and the only mobile nav affordance — is pushed off the right
   edge. Losing navigation to keep a CTA is the wrong trade, so the CTA yields.
   House chain at 360px (container 328px = 360 - 2x --gutter at its 16px floor):
   logo 119.0 (house account carries NO App inline style, so the CSS 2.1rem +
   --space-xs + 9rem governs) + 3x --space-l 60 + toggle 20 + --space-m 16 +
   CTA box 34 (2x --space-m padding + 2x 1px border) + label + hamburger 20.
   ⚠ RE-MEASURED 2026-09-09 — THE LABEL CHANGED TO "Log in" AND THE SLACK MORE
   THAN TRIPLED. The App now serves cta_secondary_label "Log in" ->
   https://siteshive.app (it was "Login" -> siteshive.com/login, and before that
   "My Hive"). MEASURED IN A BROWSER to the standard this block sets — headless
   Chrome, the real fonts/GeistVF.woff2 loaded via the FontFace API,
   document.fonts.ready awaited, fonts.check('600 14px "Geist Sans"') TRUE, five
   runs per label, all identical:

       "Log in"      40.3125px   CTA box  74.31px   1fr track  18.69px
       "My Hive"     53.3125px   CTA box  87.31px   1fr track   5.69px   <- CONTROL
       "Login"       36.8594px   CTA box  70.86px   1fr track  22.14px
       "Dashboard"   72.8594px   CTA box 106.86px   1fr track −13.86px   <- WOULD OVERFLOW

   ⚠ THE "My Hive" ROW IS KEPT AS A CONTROL, NOT AS HISTORY, AND IT IS WHAT MAKES
   THE REST TRUSTWORTHY: it reproduces this block's own recorded 53.31px to
   0.0025px, and its computed slack of 5.6875px reproduces the recorded 5.7px. A
   harness that could not re-derive the number already on the page would not be
   evidence for a new one. Chain, unchanged in shape: 119 logo + 20 + [1fr] + 20
   + (toggle 20 + --space-m 16 + CTA box) + 20 + 20 hamburger = 328.

   ⚠ THE STALE "[1fr 3.9] + 125.1 actions" READING IS RETIRED. 3.9px was derived
   from the 55.14px FALLBACK measurement described below; with the font actually
   loaded the figure was always 5.69px. The two numbers sat in this block
   contradicting each other and only one was ever right.

   ⚠ THE HEADROOM IS NOW REAL RATHER THAN NOMINAL, and the number worth carrying
   is the CEILING, not the slack: at 360px the label may be up to 59.00px wide
   before the 1fr track hits zero. "Log in" uses 40.31 of that. **"Dashboard"
   does NOT fit — it overflows by 13.86px** — which matters because the estate
   now calls that surface the Dashboard, so the obvious next rename is the one
   this gate refuses. If it is ever wanted here, the fix is header.css, not a
   shorter label.

   ⚠ THIS NUMBER HAS BEEN WRONG TWICE, so treat any figure here as suspect until
   you have re-measured with the font confirmed loaded. The original +4.8px came
   from a fontTools width of 54.19px. It was then "corrected" to +3.9px against a
   55.1px rendered label — but 55.14px is what the FALLBACK font measures, i.e.
   that measurement was taken before Geist applied, and it made the margin look
   ~1.8px tighter than it is. With document.fonts.ready awaited and
   fonts.check('600 14px "Geist Sans"') true, the label is 53.31px across
   repeated runs. See the webfont trap in the "Live state is READ" RD.
   ⚠ THE TRAP WAS REPRODUCED DELIBERATELY ON 2026-09-09 AND IT IS STILL LIVE.
   A probe run against the RENDERED PRODUCTION PAGE reported
   fonts.check(...) === false and measured "My Hive" at 55.1406px — the fallback
   figure named above, to two decimal places. The stylesheet was present and the
   markup was real; only the font had not applied. So the trap is not a
   historical mistake that was fixed, it is the DEFAULT OUTCOME of measuring a
   page rather than a font, and the 53.31px control is the only thing that
   distinguishes the two. Assert fonts.check() and refuse any figure taken
   without it. At 320px the container is 288px, the CTA is
   display:none, and the hamburger's right edge lands exactly on the container
   edge, so SE-class keeps its nav. The gutter
   holds its 16px floor until ~400px viewport and only grows after, so slack
   increases monotonically above the gate — no band where this re-breaks.

   ⚠ RE-MEASURE IF THE LABEL CHANGES. 18.69px IS headroom, which 5.7px was not —
   that was the mere absence of overflow. cta_secondary_label is App-side
   content, so the label can change with no code change and no warning, and the
   failure ejects the HAMBURGER, not the CTA. The number to check a proposed
   label against is the 59.00px CEILING above, not the current slack: at "My
   Hive" roughly one extra character overflowed; at "Log in" there is room for
   about four. That is a reprieve, not an exemption — "Dashboard" already
   exceeds it.
   The PRIMARY knob stays default `none` — header_cta_* is NULL on both house
   accounts, and two CTAs need ~429px regardless, so opting both in would arm
   that overflow the moment either pair is configured.

   THE BOX IS THE SAME EITHER WAY, which is why the arithmetic above did not
   change when this moved from the primary knob to the secondary one:
   .button--secondary overrides only COLOUR vars, so its padding, border and
   font-size are the base .button's, and header.css puts .header__cta-secondary
   in the same --text-s rule as .header__cta. Measured after the repoint, not
   assumed: 34px chrome + 53.31px label = 87.31px, the same box the primary had.

   The 360px prelude is a raw literal: the --bp-* family starts at 768px, and
   custom properties are invalid in @media preludes anyway — the documented
   Rule #12 at-rule-prelude exception, same class as the 991.98px chrome
   breakpoint. Gating only the lower bound is sufficient: the knob is consumed
   solely inside header.css's @media (max-width: 991.98px) block, so its value
   is never read at desktop.

   WHY THE SECONDARY KNOB AND NOT THE PRIMARY — the interim this file carried at
   v0.35.1-dev is CLOSED, and the history matters because the answer looks
   arbitrary otherwise. When the mobile opt-in shipped, the two house installs
   read DIFFERENT App instances whose CTA data had diverged: dev had "My Hive"
   in the PRIMARY pair, prod had it in the SECONDARY. Once the primary CTA began
   suppressing when unconfigured (functions.php v1.34.0), that left prod with no
   mobile-bar CTA at all, and NO single knob was correct for both.
   The recorded fix was to remove the DIVERGENCE rather than code around it —
   repointing then would have fixed prod and broken dev by the same mechanism,
   and opting BOTH knobs in would have been right only by coincidence of that
   day's data while arming the ~429px two-button overflow. Mars has since moved
   dev's CTA to the secondary pair, so both accounts now read
   header_cta_* NULL / cta_secondary_label "My Hive" -> https://my.siteshive.com
   (verified live on BOTH installs with the account transient flushed first —
   the cached copy is stale for up to 12h and would have shown the old shape).
   With the data aligned, one knob is correct everywhere, and this is it. */
@media (min-width: 360px) {
  .header {
    --header-cta-secondary-mobile-display: inline-flex;
  }
}

/* Sitemap link text — house edition runs --text-s (14px), tighter than the
   blueprint --text-m (16px) default (sitemap.css v0.3.0-dev). Re-points the
   --sitemap-link-text knob on .sitemap; one value inherits to every .sitemap__link
   (parent + nested __sublist). Mirrors the --card-body-text / --footer-text-size /
   --header-text-size blueprint-16/house-14 pattern. Icon sharpness is NOT re-pointed
   here — --sitemap-title-icon-size 16px is the blueprint default (crisp everywhere). */
.sitemap {
  --sitemap-link-text: var(--text-s);
}

/* Card-body text size — SitesHive edition runs --text-s (14px), tighter than
   the blueprint --text-m (16px) default (cards.css v2.4.0). Re-points the
   --card-body-text knob; the check-icon box auto-tracks (block-size = text ×
   --line-base), so the marker stays line-centered at the smaller size. Other
   sites (mgrw etc.) keep the 16px blueprint default. */
.card__body {
  --card-body-text: var(--text-s);
  /* Consumer 1 of the hex check glyph (--hex-check-icon, defined once below):
     COLLAPSE check-list rows only (.card-grid--collapse .card__body h4::before),
     which inherit --card-list-icon straight from .card__body. Desktop
     .card__body ul markers keep icon-check.svg — cards.css declares --card-list-icon
     on .card__body ul itself, and an element's own declaration beats an
     inherited one. */
  --card-list-icon: var(--hex-check-icon);
}

/* Hex check marker glyph — the SINGLE source of truth for the shape, referenced
   by BOTH consumers (--card-list-icon above + .plans__feature below) via var()
   so the two can never drift. Inlined as a data: URI on purpose: a relative
   url() stored in a custom property resolves against the CONSUMING stylesheet,
   not the declaring one (commit 3f8fcaf), and a data: URI has no relative base
   to resolve — immune regardless of which stylesheet or depth consumes it.
   The outer hexagon mirrors card-hex.css's .card--hex .card__body li::before path
   EXACTLY (M12 0 L22.39 6 L22.39 18 L12 24 L1.61 18 L1.61 6 Z): a true regular
   pointy-top hex (width = height * √3/2 ≈ 0.866; x compressed to 1.61/22.39
   inside the square 24×24 viewBox so mask-size's single value still renders it
   correctly) — same silhouette as the cards, no second source of truth for the
   shape. Check knocked out via fill-rule='evenodd' on the <path>. */
:root {
  --hex-check-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='black' fill-rule='evenodd' d='M12 0 L22.39 6 L22.39 18 L12 24 L1.61 18 L1.61 6 Z M10.285 15.92 L6.68 12.315 L7.835 11.16 L10.285 13.61 L16.165 7.73 L17.32 8.885 Z'/></svg>");
  /* Hex PLUS glyph — the SAME outer hex silhouette as --hex-check-icon (byte-identical
     outer path), a "+" knocked out via fill-rule='evenodd' instead of the check.
     Single consumer: the ADD-ON pricing marker (.plans__feature--addon, Consumer 3
     below), painted --color-text-soft so it reads PRESENT (an add-on opportunity) —
     brighter than the excluded ✕, distinct from the green included check. (The
     consumer SELECTOR was renamed at v0.38.0-dev, --available → --addon; the glyph
     and its meaning are unchanged — the plus always meant "add-on".) Same data: URI rationale as the check glyph above (relative url() has no
     stable base). */
  --hex-plus-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='black' fill-rule='evenodd' d='M12 0 L22.39 6 L22.39 18 L12 24 L1.61 18 L1.61 6 Z M10.1 5.7 L13.9 5.7 L13.9 10.1 L18.3 10.1 L18.3 13.9 L13.9 13.9 L13.9 18.3 L10.1 18.3 L10.1 13.9 L5.7 13.9 L5.7 10.1 L10.1 10.1 Z'/></svg>");
  /* Hex PLAIN glyph — the base shape, hence the bare name: the sibling pattern is
     --hex-{symbol}-icon and this one has no symbol. SAME outer path as the two
     above, byte-identical, so all three sit on one silhouette in the list.

     A RING, NOT A SOLID, and that is the whole point. At --icon-2xs (14px) a solid
     hexagon beside two solid badges carrying small knockouts reads as the same
     MASS — the eye separates them by a notch, which is detail, not weight. Drawn
     hollow it separates by ink, which survives at 14px. (The v0.14.1-dev comment
     on Consumer 3 named "a hollow hex" as a possible retune of the PLUS marker; it
     became a state of its own instead.)
     Single consumer: the AVAILABLE marker (.plans__feature--available, Consumer 4
     below) — the Custom tier's bespoke capabilities. It sat on `excluded` at
     v0.37.0-dev, which was wrong; see Consumer 4.

     BUILT AS A DONUT, NOT A STROKE, and that choice is load-bearing. A stroked
     path straddles its own line, so half the stroke would fall OUTSIDE the
     silhouette and the top and bottom apexes (y=0 and y=24) would be CLIPPED FLAT
     by the viewBox — two of the three markers would have points and one would not.
     A second, smaller hex subtracted via the same fill-rule='evenodd' the siblings
     already use keeps the outer edge exactly on the shared silhouette and needs no
     viewBox change. It also means all three glyphs are constructed identically:
     one path, two subpaths, evenodd — check cutout, plus cutout, hexagon cutout.

     RING THICKNESS is 3 viewBox units = 1.75px at 14px. The inner hex is the outer
     scaled 0.71131 about the centre (12,12): outer apothem 10.392 - 3 = 7.392, and
     7.392 / 10.392 = 0.71131. Re-derive from that if the thickness is ever
     retuned — do not nudge the inner vertices by hand or the ring goes uneven. */
  --hex-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='black' fill-rule='evenodd' d='M12 0 L22.39 6 L22.39 18 L12 24 L1.61 18 L1.61 6 Z M12 3.464 L19.391 7.732 L19.391 16.268 L12 20.536 L4.609 16.268 L4.609 7.732 Z'/></svg>");
}

/* Consumer 2 — pricing tier INCLUDED feature markers. plans.css renders these as a
   TYPED "✓" character (.plans__feature::before { content: "✓" }), NOT a mask, so
   there is no --card-list-icon to re-point — the edition OVERRIDES the pseudo to a
   green mask of the shared hex glyph. Scoped :not(--excluded):not(--addon)
   :not(--available) so ONLY true "included" features get the green hex-check —
   "included" is the state with NO modifier, so every other state has to be excluded
   by name: excluded keeps its typed ✕ (plans.css), addon gets the hex-plus
   (Consumer 3) and available gets the hex-ring (Consumer 4).
   SPECIFICITY (0,4,1) — beats plans.css's .plans__feature::before (0,1,1); the
   edition also loads after plans.css. (BUG FIXED v0.14.0-dev: the earlier
   :not(--excluded) form was (0,2,1) and — because "available" IS "not excluded" —
   swallowed the available marker into this green check, rendering it IDENTICAL to
   included; the extra :not(--available) is that fix.) block-size = font-size *
   line-height uses .plans__feature's ACTUAL --text-s / --line-base (verified: the
   edition re-points neither token); flex-shrink: 0 holds the marker width because
   .plans__feature is display: flex.
   FOLLOW-UP: converting .plans__feature::before to masks in plans.css (+ a
   --plans-feature-icon / -available-icon knob pair) is the architecturally better
   fix — the typed ✓/+ are untokenized glyphs that vary by platform font and can't
   be swapped per edition. Deferred while the pricing/plans section is frozen; unwind
   BOTH overrides (Consumer 2 + 3) when those knobs land. */
.plans__feature:not(.plans__feature--excluded):not(.plans__feature--addon):not(.plans__feature--available)::before {
  content: "";
  flex-shrink: 0;
  inline-size: var(--control-2xs);
  block-size: calc(var(--text-s) * var(--line-base));
  background-color: var(--color-secondary);
  -webkit-mask: var(--hex-check-icon) no-repeat center / var(--control-2xs);
          mask: var(--hex-check-icon) no-repeat center / var(--control-2xs);
}

/* Consumer 3 — pricing tier ADD-ON feature marker ("you can buy this on top" — an
   OPPORTUNITY, not a limitation). plans.css gives addon a typed "+"
   (.plans__feature--addon::before) which reads poorly beside the hex badges here;
   the edition overrides it to a mask of the hex-PLUS glyph — the SAME badge silhouette
   as the included check (cohesive marker set), painted --color-text-soft (the body-text
   tier, brighter than the feature label's own --color-text-muted) + a "+" cutout
   instead of green + a check. So it reads PRESENT / inviting — MORE prominent than the
   quiet excluded ✕ (an opportunity should be), while gray-vs-green + "+"-vs-"✓" still
   separate it from the included check. (v0.14.1-dev: was --color-text-muted, which read
   too "disabled/unimportant" back when Custom was an all-plus tier. Custom no longer
   uses this marker at all — it carries the hollow ring, Consumer 4 — so the "next lever
   is a hollow hex" note from that round is spent: the hollow hex became its own state
   rather than a retune of this one. If --soft still reads flat here, the remaining
   lever is a distinct HUE.)
   SPECIFICITY (0,1,1) — ties plans.css's own .plans__feature--addon::before but wins
   on source order (edition loads after); NOT matched by the green Consumer-2 rule
   above (excluded there via :not(--addon)). Box mirrors Consumer 2 exactly so all
   four markers sit on one baseline.
   RENAMED at v0.38.0-dev, --available → --addon. The selector moved; the glyph, the
   colour and the intent did not. */
.plans__feature--addon::before {
  content: "";
  flex-shrink: 0;
  inline-size: var(--control-2xs);
  block-size: calc(var(--text-s) * var(--line-base));
  background-color: var(--color-text-soft);
  -webkit-mask: var(--hex-plus-icon) no-repeat center / var(--control-2xs);
          mask: var(--hex-plus-icon) no-repeat center / var(--control-2xs);
}

/* Consumer 4 — pricing tier AVAILABLE marker: the bespoke-capability state, used by
   the Custom tier. "This tier CAN do this" — deliberately not a promise (the green
   included check), not an offer (the addon plus) and not a denial (the excluded ✕).

   THE RING IS THE WHOLE POINT, and it is a ring for a legibility reason rather than a
   decorative one. At --icon-2xs (14px) a SOLID hexagon beside two solid badges
   carrying small knockouts reads as the same MASS — the eye separates them by a
   notch, which is detail, and detail does not survive at 14px. Drawn hollow it
   separates by INK, which does. plans.css renders this state as a typed "○" for
   editions without the hex set; this is the same idea drawn properly.

   Painted --color-text-soft, the same rung as Consumer 3: both are PRESENT states and
   the SHAPE carries the distinction between them, which is exactly what a ring is for.
   Box mirrors Consumers 2 and 3 exactly so all four markers sit on one baseline.
   NO row-colour override — the base .plans__feature --color-text-muted is already
   correct for a present state; only `excluded` recedes, and that is plans.css's job.

   SPECIFICITY (0,1,1) — ties plans.css's own .plans__feature--available::before and
   wins on source order (edition loads after); NOT matched by the green Consumer-2 rule
   above (excluded there via :not(--available)).

   ⚠ THIS RULE USED TO SIT ON `excluded` (v0.37.0-dev) and it was wrong there. That
   round re-meaned a reserved state because the plus had taken the name "available",
   leaving the neutral state nowhere to go — which forced this file and plans.css to
   disagree about what `excluded` meant, and forced Custom's capabilities to be authored
   as exclusions. Renaming the plus to `addon` freed the right name, so the ring sits on
   `available`, `excluded` goes back to meaning exclusion in BOTH files, and the
   disagreement stops existing rather than being documented. Do not move it back. */
.plans__feature--available::before {
  content: "";
  flex-shrink: 0;
  inline-size: var(--control-2xs);
  block-size: calc(var(--text-s) * var(--line-base));
  background-color: var(--color-text-soft);
  -webkit-mask: var(--hex-icon) no-repeat center / var(--control-2xs);
          mask: var(--hex-icon) no-repeat center / var(--control-2xs);
}

/* Compare-column group titles — left-align in collapse mode. The "Built In" /
   "Site Care" <h3> titles are centred at desktop but must go LEFT once the cards
   collapse to check-list rows, or a centred heading sits over left-aligned rows.
     • EDITION-layer, not blueprint: the centring is a Bricks/content decision
       that no blueprint rule owns, so the correction lives where the decision
       does. A generic .split--compare rule dictating heading alignment would
       override a consumer who centred deliberately; .split--compare has exactly
       one consumer today, so there is no evidence it generalizes.
     • SELECTOR is position-coupled (.split--compare > * > h3) because the
       headings carry NO semantic class — it hits the direct-grandchild h3 (the
       column title, never a nested card h3) and is inert when that slot holds no
       heading. It BREAKS if Bricks ever wraps the heading in a Div.
     • px NEVER rem in an @container prelude — @container rem resolves against the
       10px root (html font-size: 62.5%), so 64rem would silently mean 640px.
     • KEEP-IN-SYNC: this is the THIRD copy of this prelude. The other two are
       css/cards.css (collapse mode) and css/card-hex.css (hex geometry reset) —
       all three move together if the 1023.98px threshold ever changes.
     • The centring is INHERITED from the .split__main / .split__aside column Div
       (set in Bricks), not declared on the heading, so a direct rule on the h3
       beats it at any specificity. If a future author moves text-align back onto
       the heading itself, Bricks emits it at #brxe- (1,0,0) and this rule
       silently stops working. */
@container layout (max-width: 1023.98px) {
  .split--compare > * > h3 {
    text-align: start;
  }
}

/* Compare-column group titles — honeycomb recentre. The honeycomb offsets its
   rows half a track, so a box-centred <h3> leans off its OWN top row: row 1
   centres on 2.0 tracks (unmirrored) / 3.0 (mirrored) while the box centres on
   2.5 of its 5 tracks. Shift the label back onto row 1 by that 0.5 track =
   0.5/5 = 10% of the box — +10% for the mirrored group (right, toward 3.0),
   -10% for the unmirrored (left, toward 2.0).
     • COUPLED to card-hex.css's 5-track honeycomb geometry (0.5 of 5 tracks): if
       the track count ever changes, this 10% must move with it.
     • GATE min-width: 1114px — ON only where the honeycomb is actually drawn, so
       BOTH the density revert (≤1113.98px → plain 2-up, row 1 fills the box, no
       shift wanted) and the collapse (≤1023.98px → the start-align above) unwind
       it for free.
     • DIRECTION derives from the grid's mirror class via :has() on the column,
       NOT an authored second class, so it follows .card-grid--mirror wherever it
       is applied — nothing to drift out of sync. The mirror class sits on the
       GRID, a FOLLOWING sibling of the h3, so a sibling combinator can't reach
       it; :has() on the parent column can.
     • COMPOSES with the inherited text-align centring (from the column Div) — it
       ADDS transform: translateX, it does not replace the centring — so it
       survives the documented fragility of that centring moving onto the heading
       (translateX still shifts a centred label correctly, wherever the centring
       is declared).
     • The 10% resolves against the h3's OWN box, which shares the column's
       content box with the grid (both inside padding-inline-end:
       var(--split-compare-gutter)) — verified 566px h3 vs 598px column on the
       live install. If the column padding or the h3's display ever changes so
       the two boxes diverge, 10% stops equalling half a track.
     • px NEVER rem in the prelude (@container rem resolves against the 10px
       root, so 1114rem would silently mean 11140px). */
@container layout (min-width: 1114px) {
  .split--compare > :has(.card-grid--honeycomb.card-grid--mirror) > h3 {
    transform: translateX(10%);
  }
  .split--compare > :has(.card-grid--honeycomb:not(.card-grid--mirror)) > h3 {
    transform: translateX(-10%);
  }
}

/* Section-header lead size — the house edition runs --text-m (16px), one
   rung under the blueprint --text-l (18px) default. 18px reads oversized
   against the house's tighter type feel (same rationale as the --card-body-text
   14px override above). Re-points the --section-header-lead-text knob
   (section-header.css v1.2.0); other editions keep 18px. */
.section-header {
  --section-header-lead-text: var(--text-m);
}

/* Hex card surface + header rhythm — house edition. These are DESIGN
   decisions for this site's Foundation section, not per-instance tuning, so
   they live here rather than in Bricks: version-controlled, readable in the
   codebase, and they survive an export. Blueprint defaults are unchanged for
   every other edition.
     • --card-hex-bg: the glass default (surface at 50% transparency) reads
       markedly darker than sibling rectangular .card surfaces when hexes and
       rectangles share a section. Opaque surface matches them.
     • --card-hex-header-gap: the 4px blueprint default is tuned for a
       title-only header; with an icon stacked above, the pair reads as
       touching. */
.card--hex {
  --card-hex-bg: var(--color-surface);
  --card-hex-rim: var(--color-border);
  --card-hex-rim-hover: var(--color-border-emphasis);
  --card-hex-header-gap: var(--space-xl);   /* was var(--space-m) — 16 → 24 */
}

/* Back-to-top shape selection — the house edition uses the hex (the SitesHive
   brand mark); the blueprint default (every other edition) is the circle.
   Declared at :root because the consuming selectors (.back-to-top__shape--*)
   are deep SVG children of #back-to-top, so these global vars inherit down to
   them. back-to-top.css reads them with circle=inline / hex=none fallbacks, so
   omitting this block (any non-house edition) yields the circle default. */
:root {
  --back-to-top-circle-display: none;
  --back-to-top-hex-display: inline;
}
