/* ==========================================================================
DESIGN TOKENS — Cactus League 2027  ·  WP-2.2
==========================================================================

Loaded AFTER Bootstrap and BEFORE styles-custom.css.

Namespace is --cl-*. Do not add new --bs-* custom names: --bs- is Bootstrap's
own namespace and --bs-black / --bs-white / --bs-gray are real Bootstrap
variables, so squatting there risks colliding with the framework on any
version bump. The old --bs-dark-green style names are kept at the bottom of
this file as deprecated aliases so existing rules keep working; migrate them
to --cl-* as pages get touched.

COLOR MODES: every value lives under :root and is structured for 5.3's native
color modes, but only the light theme ships. Adding dark later means adding a
[data-bs-theme="dark"] block here — not refactoring call sites.

CONTRAST: ratios in comments are against --cl-surface (#fff), sRGB, WCAG 2.x.
"AA" = >= 4.5:1 (normal text). "AA-lg" = >= 3:1 (large text / UI).
Ramps are generated by mixing toward white / #141414 — see WP-2.2 notes.
========================================================================== */

:root {

	/* ----------------------------------------------------------------------
	1. BRAND RAMPS — raw palette. Prefer the semantic tokens in §2 over these.
	   The -500 step of each is the original 2026 brand color, unchanged.
	---------------------------------------------------------------------- */

	/* Green — PRIMARY. Consolidated: the old "Dark Green" #21553d and
	   "MAIN LINK GREEN" #28553e were 7 units apart and visually identical.
	   #21553d wins; #28553e is retired. */
	--cl-green-100: #e9eeec;
	/* WP-2.10. A genuinely intermediate step, added rather than rounded: the
	   design's nav-link tint is CIE76 ΔE 4.50 from -100 and 6.48 from -200, so
	   it is visible against both. On dark it is a TEXT colour — 6.75:1 on
	   --cl-green-500, 14.39:1 on --cl-black. On white it is 1.28:1, i.e. a
	   surface only, which is why the §1 white convention reads as a failure
	   here and should. See §2b. */
	--cl-green-150: #dbe6e0;
	--cl-green-200: #c8d4ce;
	--cl-green-300: #9bb2a8;
	--cl-green-400: #648877;   /* AA-lg 3.94 */
	--cl-green-500: #21553d;   /* AA 8.64 — brand primary */
	--cl-green-600: #1f4b37;   /* AA 9.91 */
	--cl-green-700: #1d4231;   /* AA 11.19 */
	--cl-green-800: #1b382b;
	--cl-green-900: #192d24;

	/* Orange — ACCENT. -500 is the brand's #f15a2e but only clears 3.36:1,
	   so it is a fill / large-text / icon color, never small text on white.
	   -700 is the text-safe step. */
	--cl-orange-100: #feeeea;
	--cl-orange-200: #fcd6cb;
	--cl-orange-300: #f9b5a1;
	--cl-orange-400: #f58c6d;
	--cl-orange-500: #f15a2e;  /* AA-lg 3.36 — fills and large text only */
	--cl-orange-600: #d0502a;  /* 4.32 — still short of AA, do not use for body text */
	--cl-orange-700: #af4526;  /* AA 5.67 — the text-safe accent */
	--cl-orange-800: #8e3b22;
	--cl-orange-900: #682f1e;

	/* Brick — LINKS. New hue family, WP-2.10, and the one colour in the 2027
	   design that changes something on all 59 pages.

	   ⚠️ IT IS NOT A ROUNDING OF THE ORANGE RAMP, and that was checked rather
	   than eyeballed. Measured CIE76 ΔE from --cl-orange-700 is 8.51 and from
	   --cl-orange-800 is 8.94 — both far past the ~2.3 just-noticeable
	   difference. For scale: the #28553e / #21553d pair WP-2.2 retired as
	   "visually identical" was ΔE 2.20. So these two do not collapse into
	   orange, and orange-700 must stay where it is: it is still the button
	   accent (§2 --cl-accent-hover) and still the footer's own hue reference.

	   Two steps only, because the design declares exactly two — one link
	   colour and one hover. Do not fill in a -100…-900 ramp speculatively. */
	--cl-brick-500: #b23a2e;   /* AA 5.94 — the 2027 link colour */
	--cl-brick-700: #8f2a20;   /* AA 8.33 — hover. Note the direction: hover is
	    DARKER and higher-contrast than rest, so the hover state adds contrast
	    instead of spending it. The green it replaces went the other way
	    (8.64 → 5.67). ⚠️ On an amber panel this matters: brick-500 on
	    --cl-amber-500 is only 3.22:1 and fails AA for body text, while
	    brick-700 is 4.52:1 and passes. Nothing in the CSS puts a background
	    behind a link today, but the design has seven amber panels — so a link
	    landing on one in step 4 must be brick-700, not brick-500. */

	/* Amber — highlight / focus. Bright on dark, weak on white (1.84),
	   so it is a surface and indicator color, not a text color. */
	--cl-amber-100: #fff7ec;
	--cl-amber-200: #feebd0;
	--cl-amber-300: #fddbaa;
	--cl-amber-400: #fcc87b;
	--cl-amber-500: #fbb042;
	--cl-amber-600: #d8993b;
	--cl-amber-700: #b68134;
	--cl-amber-800: #936a2d;   /* AA 4.84 */
	--cl-amber-900: #6c4f25;

	/* Brown — secondary / editorial */
	--cl-brown-100: #f1f0ee;
	--cl-brown-200: #dcd9d6;
	--cl-brown-300: #c0bbb5;
	--cl-brown-400: #9e958c;
	--cl-brown-500: #74675a;   /* AA 5.48 */
	--cl-brown-600: #665b50;
	--cl-brown-700: #574e45;
	--cl-brown-800: #49423b;
	--cl-brown-900: #38342f;

	/* Sand — warm neutral / dividers */
	--cl-sand-100: #f9f5f0;
	--cl-sand-200: #f0e6da;
	--cl-sand-300: #e5d2bd;
	/* WP-2.10. Deliberately GREYER than the rest of the sand line — it is the
	   design's muted kicker/label tone over a photo scrim, not another tint of
	   the same sand. ΔE 6.52 from -300 and 7.66 from --cl-brown-300, so it is
	   not either of them. On-dark text colour: 10.87:1 on --cl-black. See §2b. */
	--cl-sand-350: #cfc6b4;
	--cl-sand-400: #d6b999;
	--cl-sand-500: #c59b6d;
	--cl-sand-600: #aa8760;
	--cl-sand-700: #907252;
	--cl-sand-800: #755e45;    /* AA 6.10 */
	--cl-sand-900: #574736;

	/* Blue — the background-image dark blue */
	--cl-blue-100: #e6ebee;
	--cl-blue-200: #c0ced4;
	--cl-blue-300: #8da6b2;
	--cl-blue-400: #4d7487;    /* AA 5.04 */
	--cl-blue-500: #013954;    /* AA 12.25 */
	--cl-blue-600: #04334a;
	--cl-blue-700: #072e41;
	--cl-blue-800: #0a2837;
	--cl-blue-900: #0d222c;

	/* Yellow — pale highlight. Background only; fails contrast at every step
	   above -900, which is by design for a highlighter tone. */
	--cl-yellow-100: #fffcf3;
	--cl-yellow-300: #fdf2c9;
	--cl-yellow-500: #fbe188;
	--cl-yellow-700: #b6a465;
	--cl-yellow-900: #6c6240;  /* AA 6.07 — the only text-safe step */

	/* Neutrals.

	   ⚠️ THIS RAMP IS TWO DIFFERENT LINES AND ALWAYS HAS BEEN — worth knowing
	   before picking a step. -100/-200/-300 are pure neutral (r=g=b); -400 and
	   up are blue-tinted. The 2027 design uses only the tinted line
	   (#c9cfd2 → #a4aab1 → #9aa0a4 → #6f747a → #3a3a3a), which is why the two
	   steps WP-2.10 added are tinted and why --cl-gray-300 was NOT repointed to
	   #c9cfd2: -300 is --cl-border site-wide, and moving it would restyle 59
	   pages to fix a ΔE of 4.75 nobody asked about.

	   WP-2.10 found the audit's "the one real gap is a neutral grey ramp"
	   overstated: #a4aab1, #6f747a and #3a3a3a were already -400/-500/-700
	   exactly. Only two grey values in the design were actually new. */
	--cl-white:      #ffffff;
	--cl-gray-100:   #f5f5f5;
	--cl-gray-200:   #eeeeee;
	--cl-gray-300:   #d9d9d9;
	--cl-gray-350:   #c9cfd2;  /* WP-2.10 — on-dark body copy. 11.70 on --cl-black,
	                              1.57 on white: a dark-surface text colour only. */
	--cl-gray-400:   #a4aab1;
	--cl-gray-450:   #9aa0a4;  /* WP-2.10 — on-dark secondary. Fills a real hole:
	                              -400 → -500 is ΔE 16.96, the widest jump in any
	                              ramp here. 6.96 on --cl-black, 2.65 on white. */
	--cl-gray-500:   #6f747a;  /* AA 4.71 — lightest text-safe gray */
	--cl-gray-700:   #3a3a3a;
	--cl-ink:        #232323;  /* AA 15.72 — body text */
	--cl-black:      #141414;  /* rich black — footers, overlays */

	/* 📌 TWO COLOURS FROM THE 2027 DESIGN WERE DELIBERATELY NOT ADDED, and this
	   note exists so they are not added later by someone reading the mockup.
	   Both were measured against the ramp step they resemble and both fall
	   BELOW the ~2.3 JND, i.e. closer together than the green pair WP-2.2
	   retired as indistinguishable (ΔE 2.20):

	       design #e6ede9  →  use --cl-green-100 (#e9eeec)   ΔE 1.38
	       design #eee7db  →  use --cl-sand-200  (#f0e6da)   ΔE 1.11

	   Nine further design colours were already tokens verbatim. The full
	   colour inventory of the design is 27 hexes; only 8 needed adding. */


	/* ----------------------------------------------------------------------
	2. SEMANTIC TOKENS — what components should actually reference.
	   Swap these, not the ramps, when a surface or role changes.
	---------------------------------------------------------------------- */

	--cl-surface:            var(--cl-white);
	--cl-surface-sunken:     var(--cl-gray-100);
	--cl-surface-raised:     var(--cl-white);
	--cl-surface-inverse:    var(--cl-black);
	--cl-surface-brand:      var(--cl-green-500);
	--cl-surface-accent:     var(--cl-sand-100);

	/* WP-2.10 — THE CARD SHELL. Both are consumed only by §7b.
	   🔑 NEITHER IS A TEXT SURFACE, and that is the single most important fact
	   about this reskin's colour work. See the ⚠️ at the top of §7b. */
	--cl-surface-field:      #e1ded5;  /* the beige field the card floats on */
	--cl-surface-card:       #eae6e2;  /* the card matte — a 10px frame, no text */

	--cl-text:               var(--cl-ink);          /* 15.72 */
	--cl-text-secondary:     var(--cl-gray-500);     /*  4.71 — AA, replaces .text-muted */
	--cl-text-inverse:       var(--cl-white);
	--cl-text-brand:         var(--cl-green-500);

	/* LINKS — repointed green → brick by WP-2.10. This is the reskin's one
	   token change that is visible on every page, so it is also the easiest
	   thing to reverse: put --cl-green-500 / --cl-orange-700 back here and the
	   whole site returns to the 2026 link colour in two lines.

	   Ratios are on --cl-surface (#fff). Both clear AA, and the hover is now
	   the HIGHER-contrast end (5.94 → 8.33) where the green pair ran the other
	   way (8.64 → 5.67). Also checked, since the reskin introduces new light
	   surfaces: brick-500 measures 5.47 on --cl-sand-100, 5.44 on --cl-gray-100
	   and 4.82 on --cl-sand-200 — AA on all three. See --cl-brick-700 in §1 for
	   the one surface where it does NOT hold. */
	--cl-link:               var(--cl-brick-500);    /*  5.94 — was --cl-green-500 at 8.64 */
	--cl-link-hover:         var(--cl-brick-700);    /*  8.33 — was --cl-orange-700 at 5.67 */
	/* Unconsumed today — nothing in css/ references it, so this is the token
	   layer staying coherent rather than a rendered change. Pointed at the
	   hover step because a green visited link amongst brick ones would read as
	   a different kind of link; the design specifies no visited state at all. */
	--cl-link-visited:       var(--cl-brick-700);

	--cl-border:             var(--cl-gray-300);
	--cl-border-strong:      var(--cl-gray-400);
	--cl-border-brand:       var(--cl-green-500);

	--cl-primary:            var(--cl-green-500);
	--cl-primary-hover:      var(--cl-green-700);
	--cl-primary-active:     var(--cl-green-800);
	/* The mid-green .btn-dark has always used. Off-ramp but genuinely in the
	   design and it clears AA at 5.97:1, so it is documented rather than
	   silently rounded to a ramp step. WP-2.4 decides whether it survives. */
	--cl-primary-soft:       #316f4f;
	--cl-accent:             var(--cl-orange-500);
	--cl-accent-hover:       var(--cl-orange-700);


	/* ----------------------------------------------------------------------
	2b. ON-DARK TEXT — WP-2.10

	    Every colour the 2027 design added beyond the card shell and the links
	    turned out to be the same kind of thing: TEXT FOR A DARK SURFACE. Not a
	    neutral grey ramp for light pages, which is how the intake audit read
	    them — the design has no light-surface grey text at all.

	    That is why they live here and not among the §2 tokens above. §2's
	    documented ratios are all against --cl-surface (#fff), and on white
	    every value in this block measures between 1.17 and 2.65:1. Reading one
	    of these as a general-purpose grey and putting it on a white page is the
	    failure mode this block is shaped to prevent. Same reasoning as §5d,
	    where the footer keeps its own small palette for the same reason.

	    RATIOS BELOW ARE AGAINST THE NAMED SURFACE, MEASURED, NOT ESTIMATED.

	    ⚠️ The -on-brand pair is measured on FLAT --cl-green-500. The design's
	    economic-impact band is not flat: it is a green gradient at opacity .76
	    over a photograph, and at its right-hand stop the effective alpha is
	    only 0.319 — white text there measures 1.87:1 over a bright photo. That
	    is a step-4 problem and it needs the §5f treatment (solve for the worst
	    photo, not the one in the mockup), not a token. Flagged in
	    prompts/WP-2.10-RESKIN.md so it cannot be forgotten.
	---------------------------------------------------------------------- */

	/* On --cl-black (#141414) — the ballparks band, the explore cards */
	--cl-on-dark:            var(--cl-white);      /* 18.42 */
	--cl-on-dark-muted:      var(--cl-gray-350);   /* 11.70 — body copy */
	--cl-on-dark-subtle:     var(--cl-gray-450);   /*  6.96 — captions, city lines */
	--cl-on-dark-warm:       var(--cl-sand-200);   /* 14.95 — the warm equivalent.
	    Reused, not added: the design's #eee7db is ΔE 1.11 from sand-200. */
	--cl-on-dark-warm-muted: var(--cl-sand-350);   /* 10.87 — kickers over a scrim */

	/* On --cl-green-500 (#21553d) — the header bar, the members band */
	--cl-on-brand:           var(--cl-green-100);  /*  7.36. Reused, not added:
	    the design's #e6ede9 is ΔE 1.38 from green-100. */
	--cl-on-brand-muted:     var(--cl-green-150);  /*  6.75 — nav links at rest,
	    which hover to --cl-on-brand. ⚠️ --cl-gray-450 is only 3.26 on this
	    green — AA-large at best. The muted step on dark is NOT interchangeable
	    with the muted step on brand. Both pairings are rendered side by side in
	    tools/token-preview.php §8b, including the wrong one. */


	/* ----------------------------------------------------------------------
	3. TYPE — Major Third (1.250), fluid between 400px and 1200px viewport.
	   Ratio chosen 2026-07-29. Sizes are the scale steps rounded to 3dp.
	   WP-2.3 moved both faces to the Adobe Fonts kit zov5lrg (erbaum +
	   montserrat). No Google Fonts request remains on the front end.
	---------------------------------------------------------------------- */

	/* WP-2.3: both faces now come from the Adobe Fonts kit (zov5lrg), loaded in
	   header.php. Kit family names are lowercase — 'montserrat', not
	   'Montserrat' — though CSS family matching is case-insensitive either way.
	   The system stack behind each one covers the kit failing to load. */
	--cl-font-body:    'montserrat', system-ui, -apple-system, 'Segoe UI', sans-serif;
	--cl-font-display: 'erbaum', 'montserrat', system-ui, -apple-system, sans-serif;

	/* Monospace — countdown-clock digits only. Deliberately system-only: a
	   ticking clock needs tabular figures so the digits don't shift width every
	   second, and every system mono here guarantees them. This replaced a
	   Google Roboto Mono request that was loaded at 300 while `.countdown-div`
	   asked for bold, so the old clock was rendering a synthesized fake-bold. */
	--cl-font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

	/* Weights — every value here exists in the kit FOR MONTSERRAT, so body and
	   h3–h6 are a pure font-family change with no fallbacks. 600 was missing
	   and Michael added it 2026-07-29; 100 was below the kit's 200 floor, so the
	   four rules that used it moved to --cl-weight-extralight.
	   ⚠️ erbaum ships 200/400/700 ONLY — no 300, no 600, no italic. That is why
	   WP-2.3 scoped the display face to h1/h2, whose weight is already 700.
	   Anything asking erbaum for 600 resolves UP to 700 (CSS Fonts L4: for a
	   desired weight >500, heavier faces are checked first), so it renders a
	   real file rather than a fake bold — but it renders heavier than written.
	   Declare 700 explicitly rather than relying on that. */
	--cl-weight-extralight: 200;
	--cl-weight-light:      300;
	--cl-weight-normal:     400;
	--cl-weight-semibold:   600;
	--cl-weight-bold:       700;
	/* WP-2.10. The 2027 design sets every button and kicker to 800, and the
	   inventory was checked against the live kit rather than assumed: montserrat
	   in zov5lrg ships 200/300/400/600/700/800/900. So 800 is a REAL face we
	   already pay for and simply had no name for — the opposite of WP-2.3's
	   erbaum-600 finding, where the weight was written and the file did not
	   exist. 900 is deliberately left unnamed until something asks for it.
	   ⚠️ erbaum is still 200/400/700 ONLY. Asking the display face for 800
	   resolves DOWN to 700 (nothing heavier exists), so an 800 heading renders
	   identically to a 700 one — do not use this token on h1/h2. */
	--cl-weight-extrabold:  800;

	--cl-text-xs:   0.64rem;   /* 10px */
	--cl-text-sm:   0.8rem;    /* 13px */
	--cl-text-base: 1rem;      /* 16px */

	/* ----------------------------------------------------------------------
	   TWO OFF-RATIO STEPS — WP-2.10 step 3. Named by ROLE, not by scale
	   position, because they are deliberately NOT Major Third steps and
	   pretending otherwise would make the ratio above a lie.

	   🔑 WHY THEY EXIST. Counted across the 2027 design: **14px is its single
	   most-used size, 18 occurrences** — more than any other — and 18px is next
	   after the display sizes with 5. The scale has nothing between 12.8 and 16,
	   so every one of those 23 declarations had to round, and 14px rounds to
	   12.8px: a 9% shortfall on the most common text on the page. It is exactly
	   what makes a faithful port read tighter and smaller than the mockup.

	   📌 14px was ALREADY in this file as --cl-btn-font-size (0.875rem), which is
	   the same observation arriving from the other direction: 14px is this
	   system's interface-text size and always has been. It just had no name.

	   ⚠️ DO NOT "regularise" these into the ramp. The Major Third from 12.8 goes
	   to 16, and inserting 14 breaks the ratio for every consumer of the scale.
	   They are role tokens that sit alongside it. */
	--cl-text-ui:   0.875rem;  /* 14px — interface text: nav and footer links,
	                              labels, metadata. The design's modal size. */
	--cl-text-lead: 1.125rem;  /* 18px — section standfirsts, the paragraph under
	                              a section heading. Larger than body on purpose. */
	--cl-text-h6:   1rem;
	--cl-text-h5:   clamp(1.125rem, 1.063rem + 0.25vw,  1.25rem);   /* 20px */
	--cl-text-h4:   clamp(1.25rem,  1.094rem + 0.626vw, 1.563rem);  /* 25px */
	--cl-text-h3:   clamp(1.5rem,   1.274rem + 0.906vw, 1.953rem);  /* 31px */
	--cl-text-h2:   clamp(1.75rem,  1.405rem + 1.382vw, 2.441rem);  /* 39px */
	--cl-text-h1:   clamp(2rem,     1.474rem + 2.104vw, 3.052rem);  /* 49px */

	--cl-leading-tight:  1.15;
	--cl-leading-snug:   1.35;
	--cl-leading-body:   1.6;   /* matches the existing body line-height */
	--cl-tracking-wide:  0.1em; /* buttons / eyebrow text */

	/* Reading measure — prose blocks cap here regardless of container width */
	--cl-measure: 68ch;


	/* ----------------------------------------------------------------------
	4. SPACING — 4px base
	---------------------------------------------------------------------- */

	--cl-space-1:  0.25rem;
	--cl-space-2:  0.5rem;
	--cl-space-3:  0.75rem;
	--cl-space-4:  1rem;
	--cl-space-5:  1.5rem;
	--cl-space-6:  2rem;
	--cl-space-7:  3rem;
	--cl-space-8:  4rem;
	--cl-space-9:  6rem;
	--cl-space-10: 8rem;


	/* ----------------------------------------------------------------------
	5. RADIUS · SHADOW · LAYOUT
	---------------------------------------------------------------------- */

	--cl-radius-sm:   0.25rem;
	--cl-radius-md:   0.6rem;    /* the existing --s-border-radius */
	--cl-radius-lg:   1rem;      /* the existing button radius */
	--cl-radius-pill: 50rem;

	--cl-shadow-1: 0 1px 2px rgb(20 20 20 / 8%), 0 1px 3px rgb(20 20 20 / 6%);
	--cl-shadow-2: 0 2px 6px rgb(20 20 20 / 10%), 0 4px 12px rgb(20 20 20 / 8%);
	--cl-shadow-3: 0 8px 24px rgb(20 20 20 / 14%), 0 2px 8px rgb(20 20 20 / 8%);
	--cl-shadow-text-dark: 2px 2px 4px rgb(0 0 0 / 75%);

	/* Two different caps — they do different jobs.

	   --cl-site-max caps the whole site SHELL. WP-2.10 took it 1800 → 1320 and
	   changed what it means: it is no longer "the width past which a gradient
	   margin shows", it is the OUTER WIDTH OF THE CARD, matte included. 1320 is
	   the design's own number and also Bootstrap's xxl container, so the shell
	   and the widest container now coincide by construction.

	   Two consequences of the drop, both good:
	   · WP-2.9's blocking constraint is gone. 1800 was chosen because every
	     background asset this site owns is ≤1920px and a wider shell would
	     upscale the grass hero. 1320 clears that with room to spare, so the
	     2027 background photo no longer has to be ≥1800px wide.
	   · --cl-container-max (1440) is now WIDER than the shell, so it can never
	     bind. Left at 1440 rather than deleted: it is the text-measure cap and
	     the @media guard below already keys off 1500px, so it costs nothing and
	     comes back into play if the card is ever widened. */
	--cl-site-max:      1320px;
	--cl-container-max: 1440px;


	/* ----------------------------------------------------------------------
	5a. THE CARD SHELL — WP-2.10
	    Consumed by §7b. Michael's instruction (2026-08-06): the rounded card
	    on the beige field IS the design, at every breakpoint. Dial in the large
	    screens first, tune mobile after.

	    Derived from the design, which nests THREE boxes, not two:

	        field   background:#e1ded5; padding:40px
	        card    max-width:1320px; background:#eae6e2; padding:10px;
	                border-radius:16px; box-shadow:0 8px 40px rgba(0,0,0,.28)
	        page    background:#fff; border-radius:8px; overflow:hidden;
	                box-shadow:0 1px 4px rgba(0,0,0,.14); color:#232323

	    🔑 THE MIDDLE BOX IS A 10px MATTE, NOT A PAGE BACKGROUND. All content
	    still sits on white. The intake audit described this as the wrapper
	    "gaining #eae6e2", which would have put every page's body copy on beige
	    and quietly invalidated every contrast ratio WP-2.7 measured. It does
	    not: the beige is a frame around a white page, and #232323 on white is
	    still 15.72:1. Re-measured to be sure — no WP-2.7 value moved.
	---------------------------------------------------------------------- */

	--cl-card-matte:    10px;   /* width of the beige frame around the page */
	--cl-page-radius:   8px;    /* the inner white page — this is the radius
	                               that actually clips content */
	--cl-page-shadow:   0 1px 4px rgb(0 0 0 / 14%);

	/* The drop shadow under the whole card. The spread is --cl-card-matte on
	   purpose, not for weight: it inflates the shadow's shape by exactly the
	   matte width so the shadow originates at the matte's OUTER edge rather
	   than 10px inside it, which is where the design's shadow starts. Change
	   the matte and the shadow keeps tracking it. */
	--cl-card-shadow:   0 8px 40px var(--cl-card-matte) rgb(0 0 0 / 28%);

	/* Field padding — the gap between the card and the browser edge. Stepped
	   in §7b: 40px is the design's desktop figure, and 40px a side on a 375px
	   phone would leave a 295px page. The two smaller steps are provisional
	   pending Michael's mobile pass. */
	--cl-field-pad:     40px;


	/* ----------------------------------------------------------------------
	5b. COMPONENT TOKENS — BUTTONS (WP-2.4)
	    The component itself lives in css/components.css; these are the values
	    it feeds into Bootstrap's --bs-btn-* variables, so a size or shape
	    change is made once, here.

	    Heights these produce (padding*2 + 1.5 line-height + 2px border):
	    sm 32px · default 41px · lg 50px. The default was 34px before WP-2.4,
	    which cleared WCAG 2.2 SC 2.5.8 (24px minimum) but sat well under the
	    44px comfort target; 41px is the compromise that does not visibly
	    fatten the callout panels the buttons live in.

	    RADIUS IS A PILL, NOT A FIXED LENGTH, on purpose. The old value was
	    --cl-radius-lg (1rem), which reads as a pill only while the button
	    happens to be ~34px tall — at the lg size the same 1rem would render
	    as a soft rectangle, so the shape silently changed character with the
	    size. --cl-radius-pill is height-independent.
	---------------------------------------------------------------------- */

	--cl-btn-font-size:      0.875rem;          /* 14px */
	--cl-btn-padding-y:      0.55rem;
	--cl-btn-padding-x:      var(--cl-space-5); /* 1.5rem */
	--cl-btn-radius:         var(--cl-radius-pill);
	--cl-btn-border-width:   1px;

	--cl-btn-font-size-sm:   0.8rem;
	--cl-btn-padding-y-sm:   0.35rem;
	--cl-btn-padding-x-sm:   var(--cl-space-4);

	--cl-btn-font-size-lg:   1rem;
	--cl-btn-padding-y-lg:   0.75rem;
	--cl-btn-padding-x-lg:   var(--cl-space-6);

	/* Disabled — deliberately neutral rather than a pale brand tint. A
	   disabled control should not carry brand color at all, and the old pale
	   green read as "a green button that has gone wrong" rather than "off".
	   Measured: gray-500 on gray-200 is 4.06:1. Inactive controls are exempt
	   from WCAG 1.4.3, but the state this replaces rendered at an effective
	   1.65:1 (see components.css) which was illegible in practice. */
	--cl-btn-disabled-bg:     var(--cl-gray-200);
	--cl-btn-disabled-color:  var(--cl-gray-500);
	--cl-btn-disabled-border: var(--cl-gray-300);


	/* ----------------------------------------------------------------------
	5c. COMPONENT TOKENS — HEADER (WP-2.5/2.6, REPLACED WP-2.10 step 3)
	    The component lives in css/components.css §2.

	    ONE STATE. The 2027 header is a flat green bar of fixed height with the
	    round badge overflowing the bottom of it. No sticky, no condense, no
	    logo swap — Michael, 2026-08-06, because the key pages now live in the
	    footer too, so the header no longer has to follow the user.

	    🗑️ RETIRED WITH THE CONDENSED STATE, all of it, so nothing below is a
	    leftover: --cl-header-logo-condensed · --cl-header-mini-w ·
	    --cl-header-pad-y-condensed · --cl-header-brand-h(-condensed) ·
	    --cl-header-z · --cl-header-shadow · --cl-header-dur · -dur-swap ·
	    -dur-badge · --cl-header-ease · --cl-header-lock.
	    ⚠️ The reduced-motion block in §8 used to zero four of those. It has been
	    cut back to match — a media query resetting tokens nobody reads is the
	    kind of thing that survives three refactors and then confuses someone.

	    🔑 THE GEOMETRY IS NOW INDEPENDENT, WHICH IS THE REAL CHANGE. WP-2.5/2.6
	    locked three tokens together with an identity:

	        bar height       = logo - overhang + pad-y
	        brand box height = logo - overhang - pad-y

	    so raising the logo made the BAR taller, and a smaller overhang also made
	    the bar taller. That is why the old header was hard to retune and why the
	    rendered overhang could land 16px off the number written here.

	    The 2027 bar is a fixed height and the badge simply overflows it, so:

	        overhang = logo + logo-top - bar height        (derived, below)

	    Nothing feeds back. Change the badge or the bar and the reserved space
	    follows on its own; components.css §2's breakpoints set only those two.
	---------------------------------------------------------------------- */

	--cl-header-h:        96px;   /* the green bar — the design's figure */
	--cl-header-pad-x:    var(--cl-space-6);   /* 2rem; design says 40px */
	--cl-header-logo:     200px;  /* the round badge, 1:1 */
	--cl-header-logo-top: 6px;    /* how far below the bar's top edge it starts */

	/* The badge's box in the flex row. NARROWER THAN THE BADGE ON PURPOSE — the
	   design has a 200px image in a 180px slot, so the artwork bleeds ~20px to
	   the right of the space it reserves and the welcome line tucks in closer
	   than the circle's bounding box would allow. It works because the badge is
	   a circle: the corners it overlaps are empty. ⚠️ If the mark is ever
	   replaced with something that fills its box, this has to go back to
	   matching --cl-header-logo or the next element will collide with it. */
	--cl-header-logo-box-w: 180px;

	/* 📌 --cl-header-overhang IS DERIVED, AND IT IS DELIBERATELY *NOT* DECLARED
	   HERE. It lives on `.cl-site-wrapper` in components.css §2 instead (it was
	   on `.cl-header` until step 5 batch 1 gave a second component outside the
	   header a use for it — the reasoning for the move is there), and putting it
	   at :root is a bug that renders perfectly at one breakpoint and silently
	   wrong at every other one.

	   🐛 MEASURED, NOT THEORISED. With
	       :root { --cl-header-overhang: calc(logo + logo-top - h) }
	   and the breakpoints overriding --cl-header-logo on `.cl-header`, the
	   reserved space stayed **110px at every width** — 1440/1100/900/700/430/320
	   all identical — when it should have derived 110/78/70/48/48/48.

	   WHY: a custom property's value is computed in the scope where it is
	   DECLARED. The calc resolved against :root's own --cl-header-logo (200px),
	   and `.cl-header` then merely INHERITED that finished 110px. Overriding an
	   input further down the tree cannot retroactively re-run a calc that already
	   substituted. Declaring the derived token on the same element the media
	   queries target is what makes the cascade pick the winning input first.

	   🔑 This is the same family as the WP-2.6 header trap (a transitioned
	   property whose value comes from var() ignoring a change to that var):
	   in both cases the var() looks live and is actually frozen. Generalise it —
	   **a derived custom property must be declared in the same scope as the
	   values it derives from.**

	   ⚠️ AND THE COROLLARY, WHICH STEP 5 HAD TO LEARN SEPARATELY: that scope has
	   to be an ANCESTOR OF EVERY CONSUMER, not just of the inputs. Custom
	   properties inherit down and nowhere else, so declaring on `.cl-header` put
	   the value out of reach of `.cl-page-head`, which is inside <main> — a
	   sibling. The scope that satisfies both halves is the lowest common
	   ancestor of the inputs and the readers, which here is `.cl-site-wrapper`. */

	--cl-header-welcome-size: 1.4375rem;  /* 23px — the design's figure */
	--cl-header-actions-gap:  2.125rem;   /* 34px */


	/* ----------------------------------------------------------------------
	5c-b. COMPONENT TOKENS — THE SUBNAV CURTAIN (WP-2.10 step 4b)
	      design/design_handoff_pancake_subnav, option 3A. The component lives
	      in css/components.css §2c.

	      RATIOS BELOW ARE MEASURED ON --cl-green-500 (#21553d), which is the
	      curtain's flat fill — there is no photograph or gradient behind it, so
	      unlike §5f and §5h these numbers are exact rather than solved for a
	      worst case.

	      🔑 THE DESIGN'S LINK HOVER COLOUR FAILS AA ON ITS OWN PANEL, AND IT IS
	      THE ONLY MEASURED FAILURE IN THE WHOLE HANDOFF. The spec hovers links
	      to #f15a2e (--cl-orange-500) at 14px. On this green that is **2.57:1**
	      against a 4.5:1 requirement — not a near miss, and not rescuable by
	      size, because 14px/600 is not large text under any definition.

	      Why it looks right in the mockup and is wrong here: --cl-orange-500 is
	      already documented as "fills and large text only" at the top of §1, and
	      --cl-footer-link-hover uses it legitimately because the FOOTER's
	      surface is #141414, where it measures 5.48. Same colour, different
	      ground, opposite verdict. ⚠️ Never carry a hover colour between two
	      dark surfaces on the strength of it passing on one of them.

	      What shipped instead, and why it is the closest safe reading of the
	      design's intent rather than a substitution: the LABEL hovers to
	      --cl-text-inverse (white, 8.64) and an --cl-orange-500 underline
	      appears beneath it. The design's accent colour still does the
	      signalling, on a rule where its 2.57 contrast is irrelevant because it
	      is a decorative graphic beside text rather than the text itself, and
	      the text gets brighter rather than dimmer on hover.

	      📌 If Michael wants the glyphs themselves orange, --cl-orange-300
	      (#f9b5a1) is the text-safe step on this green at 5.00:1 — measured, and
	      the only one of the five oranges that clears it (400 is 3.64). It is a
	      pale salmon and reads far weaker than the design, which is why it is
	      recorded here as the alternative rather than chosen as the default.
	      One line: --cl-subnav-link-hover.
	---------------------------------------------------------------------- */

	--cl-subnav-bg:            var(--cl-green-500);
	--cl-subnav-shadow:        0 28px 50px rgb(0 0 0 / 45%);
	--cl-subnav-backdrop:      rgb(8 10 8 / 55%);

	/* The group headings. Amber on this green is 4.68 — AA, and it clears with
	   room to spare at the 11px/800 the design asks for, which is small text by
	   every definition and therefore held to 4.5 rather than 3.0. */
	--cl-subnav-title:         var(--cl-amber-500);   /* 4.68 */
	--cl-subnav-title-muted:   var(--cl-sand-350);    /* 5.10 — the "FOLLOW THE
	    LEAGUE" label. Reused rather than added, and it is an exact match, not a
	    near one: --cl-sand-350 IS #cfc6b4, the literal value the handoff
	    specifies. Worth checking the palette before declaring a new token —
	    three of this component's five colours were already in it. */

	--cl-subnav-link:          var(--cl-on-brand);         /* 7.36 — the design's
	    #e6ede9 is ΔE 1.38 from green-100, which §2 already records. */
	--cl-subnav-link-hover:    var(--cl-text-inverse);       /* 8.64 — see above */
	--cl-subnav-link-marker:   var(--cl-orange-500);       /* the underline only */

	/* 13px, up from the design's 11px (Michael, 2026-08-08). ONE token, so all
	   four headings move together — "Visit / Plan", "Members", "The 15 Teams"
	   and "Follow the League" are one system, and sizing two of them apart from
	   the other two would read as a mistake rather than as a hierarchy. Still
	   small text, so the amber still has to clear 4.5 and still does at 4.68. */
	--cl-subnav-title-size:    0.8125rem;  /* 13px — the design said 11 */
	--cl-subnav-link-size:     var(--cl-text-ui);   /* 14px, the design's */

	/* The cap on each link column — see the ⚠️ in components.css §2c about why
	   the design's `1fr` could not be carried across. 15rem holds "Discover AZ
	   Directory" and "Our Member Directory", the two longest labels in either
	   list, on one line at 14px/600 with room to spare. */
	--cl-subnav-col-max:       15rem;

	/* The horizontal gutter. --cl-block-pad-x, NOT the header's --cl-header-pad-x
	   it started as: Michael measured 3rem by eye as the value that lines the
	   curtain's columns up with the homepage content, and 3rem is exactly what
	   --cl-block-pad-x already holds (§5h). So this is the homepage's own gutter
	   token rather than a coincidence written as a number — change the blocks
	   and the curtain follows.
	   ⚠️ The consequence is that the curtain no longer aligns with the HEADER
	   BAR above it (2rem). Aligning with the page content is the call; they
	   cannot both be satisfied while the two gutters differ. */
	--cl-subnav-pad-x:         var(--cl-block-pad-x);

	/* Chips. Doubled from the design's 32/28 (Michael, 2026-08-08) — at the size
	   the handoff drew them the logos read as decoration; at 64 they read as the
	   fifteen clubs, which is the whole point of the column.
	   ⚠️ THE CELL IS THE HIT TARGET AND THE IMAGE IS ONLY THE ARTWORK. 64px
	   clears WCAG 2.2 SC 2.5.8's 24px floor and the 44px comfort figure with
	   room to spare; the old 32px cleared only the first. Never size the cell
	   from the image. */
	--cl-subnav-team-cell:     64px;

	/* 🎨 56 → 46 (Michael, 2026-08-10). The artwork breathes inside the chip
	   instead of filling it: a 9px inset rather than 4px.
	   📌 It converges with the footer, which moved the other way in the same
	   pass (32 → 38 in a 52px chip). The two now sit at 46/64 = .72 and
	   38/52 = .73 — the same proportion, reached from opposite directions, and
	   the reason both components finally look like one treatment.
	   ⚠️ THE CELL IS UNCHANGED AT 64px, WHICH IS THE POINT OF SEPARATING THEM.
	   Shrinking the artwork does not shrink the hit target — see the note
	   above. Never fold these two tokens back into one. */
	--cl-subnav-team-img:      46px;

	/* ⚠️ THE SOCIAL PILL IS GONE AND THIS IS NOW AN INVISIBLE HIT TARGET
	   (Michael: "remove the circle… they are good just white on green"). It
	   stays 38px for exactly that reason — the glyph is ~24px, and shrinking the
	   link to fit the glyph would silently drop the target below the 24px floor
	   the visible pill used to guarantee. A control losing its background does
	   not lose its target size. */
	--cl-subnav-social-size:   38px;
	--cl-subnav-social-icon:   1rem;   /* the CMS's `fa-xl` scales this ×1.5 → 24px */

	/* The design's `padding: 34px 34px 28px`. Expressed against the header's own
	   horizontal gutter rather than as 34px, so the curtain's columns line up
	   with the bar above it at every breakpoint instead of only at the one the
	   mockup was drawn at. */
	--cl-subnav-pad-y:         2.125rem;   /* 34px */
	--cl-subnav-pad-y-end:     1.75rem;    /* 28px */
	--cl-subnav-gap:           2.125rem;   /* 34px — the grid's column gap */

	/* Motion. The design's figures: .55s on the curtain, .4s on the backdrop,
	   both on the same overshoot-free ease.
	   ⚠️ THESE ARE SEPARATE TOKENS RATHER THAN --cl-duration-slow BECAUSE §6
	   ZEROES THAT ONE, and the curtain's close depends on its duration being a
	   real number in one specific place — see the visibility note in §6. */
	--cl-subnav-dur:           550ms;
	--cl-subnav-dur-backdrop:  400ms;
	--cl-subnav-ease:          cubic-bezier(0.22, 1, 0.36, 1);


	/* ----------------------------------------------------------------------
	5d. COMPONENT TOKENS — FOOTER (WP-2.6)
	    The component lives in css/components.css §3.

	    THE FOOTER IS THE ONLY DARK SURFACE ON THE SITE, so it gets its own
	    small palette rather than reaching for the §2 semantic tokens — those
	    are all defined against --cl-surface (#fff) and their documented
	    contrast ratios are meaningless here. Ratios in this block are against
	    --cl-footer-bg, measured, not estimated.

	    The two greys came off the old footer unchanged (#141414 / #a4aab1) and
	    both clear AA on their own.

	    📌 TWO TOKENS WERE REMOVED FROM THIS BLOCK ON 2026-07-30 and are worth
	    a line so nobody re-adds them speculatively. --cl-footer-text-strong
	    (#d8dce1) existed as a middle step for the band headings; Michael
	    removed all three headings as stating the obvious, and it had no other
	    consumer. --cl-footer-bg-raised (#1c1c1c) lifted the signup band off
	    the footer black; the signup is no longer a band, it is a column inside
	    the main one, so a second surface there would cut the row in half. Both
	    were real and both stopped being needed the same day.

	    ⚠️ --cl-footer-link is pure white on purpose and should stay that way.
	    The team list is 15 links in small type at close spacing; anything
	    dimmer than white stops reading as a link at that size, and the hover
	    state has nowhere brighter to go. Contrast is not the constraint here
	    (7.86:1 already passes) — legibility at 13px is.
	---------------------------------------------------------------------- */

	--cl-footer-bg:          #141414;  /* the existing footer black, unchanged */
	--cl-footer-bg-sunken:   #0d0d0d;  /* legal bar — reads as a base, not a band */

	--cl-footer-text:        #a4aab1;  /*  7.86 on --cl-footer-bg — AA */
	--cl-footer-link:        var(--cl-white);        /* 18.42 — see the ⚠️ above */
	--cl-footer-link-hover:  var(--cl-orange-500);   /*  5.48 — the brand orange
	    works here and does NOT on a white page. #f15a2e is the 3.36:1 value
	    WP-2.2 had to retire for a:hover site-wide, replacing it with
	    orange-700. On black the same color measures 5.48:1, so the footer can
	    have the brighter, more on-brand hover that the light pages cannot.
	    Do not "fix" this to --cl-link-hover for consistency: orange-700 on
	    this background is 3.25:1 and would FAIL AA for body-size text. */

	--cl-footer-rule:        rgb(255 255 255 / 12%);  /* section dividers */

	--cl-footer-pad-y:       var(--cl-space-7);  /* 3rem — band padding */
	--cl-footer-gap:         var(--cl-space-5);

	/* 🔓 THE BADGE AND THE SOCIAL ROW ARE NO LONGER COUPLED — WP-2.10 step 3,
	   and this is a constraint being RELEASED rather than dropped by accident.

	   WP-2.6 locked them to one width because Michael's brief asked for icons
	   "about as wide as the logo badge above", so the left column would read as
	   one object instead of two: the social row was set to --cl-footer-logo-w and
	   distributed six icons inside it with space-between.

	   ⚠️ THAT MATCH COST AN ACCESSIBILITY TRADE, WHICH IS NOW REFUNDED. Six 44px
	   targets need 264px plus gaps and could not fit a 220px badge, so the icons
	   shrank to 34px — past WCAG 2.2 SC 2.5.8's 24px floor, but under the 44px
	   comfort figure they had before. The 2027 footer puts the badge, the blurb
	   and the icons in a column of their own where they no longer have to match
	   the badge, so the icons are back to 44px and the badge is the design's
	   136px.

	   ⚠️ BUT THE 264px PROBLEM DID NOT GO AWAY — IT MOVED, and assuming otherwise
	   was wrong for one render. The column itself is only ~240px, so six 44px
	   icons in a row still do not fit; they wrapped 5 + 1 and the orphan looked
	   like a bug. §3b lays them out as an explicit 3x2 grid instead, which is
	   deliberate at any column width. The lesson is the same one WP-2.6 recorded
	   from the other direction: this icon size is always in tension with
	   something, so check what before changing it.

	   The badge is the ROUND mark (1:1), so its width value is also its height. */
	--cl-footer-logo-w:      136px;   /* badge — the design's figure (was 220px) */
	--cl-footer-social-size:  44px;   /* the WCAG 2.2 comfort target, restored */

	/* The team logo chips. 52px is the design's figure and it happens to clear
	   both SC 2.5.8's 24px floor and the 44px comfort figure, so the hit target
	   needs no separate padding. The logo inside is inset 12px — see §3b. */
	--cl-footer-team-chip:    52px;

	/* 🗑️ --cl-countdown-h IS GONE — WP-2.10 step 3.

	   It reserved space under the page for the FIXED countdown bar, and it was
	   measured rather than hardcoded (by a ResizeObserver in js/footer.js)
	   because the bar is taller on mobile — it gains a title row under 992px —
	   so either constant would have been wrong on one of the two.

	   The bar is no longer fixed (Michael, 2026-08-07): it is an ordinary block
	   inside the card, so it occupies real space and there is nothing to
	   reserve. The token, the observer, .cl-footer--has-countdown and the
	   consent banner's matching offset all retired together — four moving parts
	   that existed solely to compensate for `position: fixed`. */


	/* ----------------------------------------------------------------------
	5g. COMPONENT TOKENS — THE 2027 SET (WP-2.10)
	    Components live in css/components.css §7.

	    🔑 RADIUS: THE DESIGN'S SEVEN VALUES WERE COLLAPSED ONTO TWO, and that
	    is a decision, not sloppiness. The mockup carries 16px (×8), 12px (×3),
	    18px (×2), 14px, 10px, 8px and 2px. Nothing in it *distinguishes* them —
	    the 14px team tile and the 18px schedule card are the same kind of object
	    on the same surface — so the spread is generator noise, and carrying it
	    across would mean seven radii nobody could ever choose between. Large
	    surfaces take --cl-radius-lg (16px, which is already the modal value);
	    small inline chips take --cl-radius-md. The 2px slideshow indicator bars
	    stay component-local: at that size it is a shape, not a radius step.
	---------------------------------------------------------------------- */

	/* Section kickers — the small uppercase line above a heading
	   ("FOR OUR MEMBERS", "THE MAIN EVENT"). Tracking is expressed in em, not
	   px, so it holds if the size is ever retuned; the design's 3px at 13px and
	   2px at 11px are these two values.
	   📌 The design's third kicker size (11px / 1px) belongs to the HERO
	   SLIDESHOW control panel, which §1 of the reskin doc says must NOT be
	   ported. No token for it on purpose. */
	--cl-kicker-size:        0.8125rem;  /* 13px */
	--cl-kicker-tracking:    0.23em;     /* = 3px at 13px */
	--cl-kicker-size-sm:     0.6875rem;  /* 11px */
	--cl-kicker-tracking-sm: 0.18em;     /* = 2px at 11px */

	/* Light card surfaces — the team tiles and the Opening Week card.
	   ⚠️ THE HAIRLINE IS THE ONLY THING SEPARATING THESE FROM THE PAGE and it
	   measures 1.47:1 on white. That is not a WCAG failure — a card edge is
	   neither a control boundary nor needed to understand the content, so
	   SC 1.4.11's 3:1 does not apply — but it is very faint, and it is why the
	   raised variant exists. The design's team tiles carry no shadow at rest. */
	--cl-card-hairline:      var(--cl-sand-300);
	--cl-card-lift:          0 2px 8px rgb(20 20 20 / 6%);
	/* The design's own tile rollover. Kept separate from --cl-card-lift, which is
	   a resting elevation: a hover that reads has to be a step, not a nudge. */
	--cl-card-lift-hover:    0 14px 30px rgb(20 20 20 / 12%);
	--cl-card-lift-raised:   0 24px 60px rgb(20 20 20 / 12%);
	--cl-card-pad:           var(--cl-space-5);

	/* Glass surfaces — translucent fills for DARK panels and photographs.
	   The design uses five alphas (.05 .07 .08 .10 .12); collapsed to two,
	   same reasoning as the radius note above.
	   Measured on --cl-green-500, which is where the stat tiles sit: the .07
	   fill resolves to #31614b, against which white is 7.14:1, --cl-on-brand
	   6.09 and --cl-on-brand-muted 5.58 — all AA. The amber stat figure is
	   3.87:1, which passes only because it is 46px display type and therefore
	   large text. ⚠️ Shrink that figure and it fails. */
	--cl-glass-fill:         rgb(255 255 255 / 7%);
	--cl-glass-fill-strong:  rgb(255 255 255 / 12%);
	--cl-glass-hairline:     rgb(255 255 255 / 16%);

	/* ⚠️ THE GHOST BUTTON'S LEGIBILITY IS BORROWED FROM THE SCRIM UNDER IT, AND
	   THE FLOOR IS DERIVED — see components.css §7a before placing one. Its fill
	   is white at 10%, which adds nothing, so a white label over a bright photo
	   depends entirely on the scrim. Solved against the worst case a photo can
	   present (pure white), compositing rgba(20,20,20,a) then the .10 fill:

	       white label reaches 4.5:1   at a >= 0.65
	       the .6 white hairline hits 3:1 against its own fill at a >= 0.70

	   So 0.70 is the floor for the whole control. The hero's own gradient runs
	   .9 -> .55 -> .12 bottom to top: at .9 the label is 10.37:1, and at .55 it
	   is 3.41:1 and FAILS. The design's ghost button is legible only because it
	   sits low in the hero. Same defect class as the economic-impact band. */
	--cl-ghost-scrim-min:    0.70;   /* documentation, not consumed by any rule */


	/* ----------------------------------------------------------------------
	5h. COMPONENT TOKENS — THE HOMEPAGE BLOCKS (WP-2.10 step 4)
	    Components live in css/components.css §8. Everything here is shared by
	    two or more of the six blocks, or is a derived value whose reasoning has
	    to live next to the number.

	    📌 BLOCK PADDING IS ONE TOKEN, NOT SIX. The design sets 88px, 80px, 82px,
	    82px and 84px on its five content bands — five values that no reader
	    could ever choose between, and which are the generator's noise rather
	    than a rhythm. Collapsed to one step, the same call made for the seven
	    corner radii in §5g. The horizontal 56px is uniform in the design and is
	    kept as its own token because it must stay in step with the header's
	    --cl-header-pad-x, not with the vertical rhythm.
	---------------------------------------------------------------------- */

	--cl-block-pad-y:   var(--cl-space-9);   /* 6rem/96px; design 80–88px */
	--cl-block-pad-x:   var(--cl-space-7);   /* 3rem/48px; design 56px     */
	--cl-block-gap:     var(--cl-space-7);   /* between the two columns of a
	                                            split band; design 44–56px  */

	/* ----------------------------------------------------------------------
	   ⚠️ THE HERO SCRIM IS DERIVED AGAINST PURE WHITE, AND THAT IS NOT A
	   HYPOTHETICAL WORST CASE HERE — IT IS MEASURED, IN THE SHIPPING PHOTOS.

	   The four hero photographs were sampled across the region the copy sits in
	   (left 64%, bottom 62%, on the rendered 1300x788 cover crop). Peak
	   luminance in that region:

	       AdobeStock_568971222   L = 1.000   rgb(255,255,255)   ← pure white
	       AdobeStock_492444293   L = 0.998   rgb(254,255,255)   ← pure white
	       AdobeStock_149098948   L = 0.918
	       AdobeStock_610732110   L = 0.705

	   So two of the four already contain the worst case a photograph can
	   present, directly underneath the text. This is §5f's Hall-of-Fame finding
	   in a new place, with the usual escape route closed: there is no "tuned to
	   today's images, re-derive later" option available, because today's images
	   ARE white. Solving against rgb(15,12,9,a) over white:

	       #fff       h1, 108px display   3.0:1 needed   a >= 0.438
	       #eee7db    lede, 21px          4.5:1 needed   a >= 0.621
	       #cfc6b4    clock labels, 11px  4.5:1 needed   a >= 0.707
	       #fbb042    kicker, 14px        4.5:1 needed   a >= 0.729   ← binds

	   0.75 is the next round step above the binding one. The copy block is
	   ~520px wide inside a 1300px card, so the requirement is "a >= 0.75
	   everywhere in the left 42%", at every height the copy occupies — NOT a
	   floor that ramps away going up the page, which is what the design ships
	   (.9 -> .55 -> .12 bottom to top, i.e. 0.12 at the kicker).

	   🔑 THAT IS WHY THE PRIMARY GRADIENT IS HORIZONTAL AND THE VERTICAL ONE IS
	   NOW SECONDARY — the two are swapped relative to the design. A bottom-up
	   ramp cannot hold a floor across a block of copy 480px tall; a left-to-
	   right one holds it across the whole column by construction, and gives the
	   photograph back the right 22% completely rather than veiling all of it
	   weakly. Composited: a_eff = 1 - (1 - a_h)(1 - a_v), lowest value anywhere
	   in the copy column 0.81.

	   ⚠️ THE DESIGN'S 10-DEGREE TILT (`100deg`) IS DROPPED FOR `to right`. At
	   10deg off-axis a stop's position depends on how far up the box you are, so
	   the solve above would hold at one height and not another — the contrast
	   floor stops being checkable. The visual difference at this size is a few
	   pixels of stop position; the difference in what can be verified is total.
	---------------------------------------------------------------------- */

	--cl-hero-h:            788px;   /* the design's, at the large step */
	--cl-hero-scrim:        15 12 9; /* the design's near-black, as an rgb triple
	                                    so the stops below can vary only alpha */
	--cl-hero-scrim-min:    0.75;    /* documentation, not consumed by any rule */
	--cl-hero-fade:         1200ms;  /* the slideshow cross-fade; design default */
	--cl-hero-hold:         5000ms;  /* consumed by js/hero.js, not by CSS */

	/* ----------------------------------------------------------------------
	   ⚠️ THE ECONOMIC-IMPACT BAND IS THE SAME SOLVE, AND THE DESIGN FAILED IT
	   FOUR WAYS. Opened by step 1 (reskin doc §6): a green gradient at
	   opacity .76 over tex-grass.jpg gives an effective alpha ramping 0.730 at
	   the left stop down to 0.319 at the right, so against a white photo the
	   white body copy measures 1.87:1 at the right-hand edge and the amber stat
	   figures fail even at the strongest stop (2.96:1).

	   Fixed the §5f way rather than by nudging: the ramp is REMOVED. The band's
	   text is centred and full-width, so unlike the hero there is no column to
	   protect — every horizontal position carries text, which means a horizontal
	   ramp is the one shape that cannot work here. A flat scrim can, and the
	   green is kept as a separate multiply-free layer so the band still reads as
	   brand green rather than as grey.

	   Solved against white for the two foregrounds that sit directly on it
	   (the stat tiles carry their own .07 glass fill on top, measured in §5g):

	       #fff       h2, 54px display    3.0:1   a >= 0.438
	       #e6ede9    lede, 18px          4.5:1   a >= 0.632
	       #fbb042    kicker, 13px        4.5:1   a >= 0.729   ← binds

	   0.88 rather than 0.75: the grass texture is the point of the band, and at
	   0.75 it is barely present while at 0.88 it still reads as texture. Going
	   ABOVE the floor costs nothing but the photo; going below costs legibility.
	---------------------------------------------------------------------- */

	/* The green wash over the grass. 🗑️ --cl-impact-photo-brightness is gone with
	   the filter it fed (Michael, 2026-08-07 — he prefers the unfiltered grass).
	   ⚠️ Read the note on .cl-impact__bg in components.css §8g before raising or
	   lowering this: the amber kicker and the amber stat figures are the only
	   foregrounds on this band that need more than a light wash, so darkening the
	   whole band to satisfy them is the expensive way round. */
	--cl-impact-scrim:      rgb(24 58 42 / 52%);


	/* ----------------------------------------------------------------------
	5i. COMPONENT TOKENS — THE INTERIOR PAGE (WP-2.10 step 5, batch 1)
	    Components live in css/components.css §10. This is the language the
	    other 38 pages inherit, so everything here is deliberately small.

	    ⚠️ THE ONE NUMBER THAT IS *NOT* HERE IS THE IMPORTANT ONE.
	    --cl-lead-inset — the left edge the <h1> shares with the header's
	    "WELCOME TO THE CACTUS LEAGUE" line — is DERIVED, and it is declared on
	    `.cl-site-wrapper` in components.css §2, not at :root. Same rule as
	    --cl-header-overhang two blocks up, for the same reason: it is
	    `--cl-header-pad-x + --cl-header-logo-box-w + --cl-space-7`, and all
	    three of those are overridden per breakpoint further down the tree. A
	    calc at :root would substitute :root's inputs once and inherit the
	    finished number everywhere — 260px at all six widths instead of
	    260/232/204. Read the 🐛 on --cl-header-overhang in §5c; this is the
	    second component to depend on it and the first one OUTSIDE the header.
	---------------------------------------------------------------------- */

	/* How far the page head rises into the badge's overhang is not a token: the
	   reserve is cancelled outright (components.css §10a) and this is the space
	   above the <h1> that replaces it. 2rem lands the heading against the lower
	   half of the badge at all three desktop steps — Michael, 2026-08-11:
	   "up on the page a bit. To the right of the bottom half of the logo." */
	--cl-page-head-pad-t:  var(--cl-space-7);   /* 3rem — Michael, 2026-08-12 */
	--cl-page-head-pad-b:  var(--cl-space-8);   /* 4rem — the gap to the first
	                                               section, which supplies its
	                                               own top padding on top of it */

	/* ----------------------------------------------------------------------
	   THE PAGE HEAD'S SCRIM IS THE IMPACT BAND'S, EXACTLY — Michael, 2026-08-12:
	   *"I want the impact-scrim, this one is too dark."* It was 0.62 for one
	   round; that shipped a page head visibly heavier than the band it was
	   supposed to echo, which was the wrong trade.

	✅ THIS IS THE APPROVED, SETTLED SURFACE — Michael, 2026-08-12: *"Lock this in.
	   Don't keep bringing up any issues with this. We're going to go with this
	   for the site wide redesign."* It is the standard the remaining 36 interior
	   pages inherit. The measurements below are kept as reference, NOT as an open
	   item: do not re-raise them, do not re-derive them, and do not let them gate
	   a design decision. If the treatment is ever revisited, this is the record
	   of what was known when it was chosen.

	   Over the grass photo's brightest pixel under the copy — rgb(177,202,111),
	   sampled from this band's own crop — 0.52 gives rgb(97,127,75):

	       element                            needs   measured
	       <h1> white, 49px                    3.0     4.52  ✅ (clears 4.5 too)
	       intro --cl-on-brand 18px, on glass  4.5     3.33  ⚠️
	       eyebrow --cl-amber-500, 13px        4.5     2.45  ⚠️

	   🔑 THE CEILING AT THIS ALPHA IS 4.52 — pure white, the brightest colour
	   there is. So no choice of TEXT COLOUR fixes the eyebrow and the intro has
	   at most 1.2 of headroom. Only the SURFACE can close this.

	   🗑️ AND THE PREPARED-ASSET ROUTE WAS TRIED AND REVERTED — 2026-08-12, and
	   the reason it failed is worth more than the attempt. Michael's instinct was
	   right in principle: *"if we standardize the grass image then text using
	   this background will always be quite readable."* A prepared file turns a
	   measurement into a guarantee. What it cannot do is buy contrast for free.

	   🐛 THE FIRST ATTEMPT LOOKED LIKE IT DID, AND THAT WAS A MEASUREMENT ERROR
	   ON MY PART, NOT A DISCOVERY. A highlight roll-off was tuned for contrast
	   margin by lowering the ceiling — and the sweep lowered the KNEE with it, to
	   luminance 0.1575, which is **pixel value 110 when the image's median is
	   99**. Roughly the top half of the tonal range was compressed into 16
	   values. Michael saw it immediately. Measured after the fact: fine-texture
	   energy **−22.5% in the midtones and −44% in the highlights**.
	   🔑 I HAD REPORTED "MEDIAN LUMINANCE BARELY MOVED" AS EVIDENCE THE IMAGE WAS
	   UNCHANGED. Median luminance is the wrong instrument for this: a tone curve
	   can flatten every local gradient in a photograph while leaving the median
	   exactly where it was. **Texture has to be measured as texture** — local
	   deviation from a neighbourhood mean, per tonal band.

	   Rebuilt properly, with the knee up at pixel 149 so only genuine speculars
	   move, texture measures **99–101% of the original** — and the contrast it
	   buys is intro 3.9 and eyebrow 3.5, both still short. **The texture damage
	   WAS the contrast gain.** That closes the route: on this photograph a cap
	   that preserves the grass cannot reach AA, so the asset is untouched.

	   📌 IF A FUTURE ROUND EVER REOPENS THIS, it is two declarations and they are
	   recorded so nobody solves it twice: `--cl-glass` fill white 7% -> black 12%
	   under the intro measures 4.67, and an amber-500 eyebrow inside a
	   `rgb(0 0 0 / 45%)` chip measures 5.53. ⚠️ NOT a recommendation and NOT a
	   TODO — a note for whoever reopens it, if anyone ever does.

	   📌 axe REPORTS ZERO VIOLATIONS HERE AND WILL CONTINUE TO. It returns
	   `incomplete` — it cannot compute contrast through a photograph, which is
	   §5f's trap and the reason the Hall of Fame's 54 failures went unseen for
	   so long. These numbers came from sampling the image, not from the scanner.
	---------------------------------------------------------------------- */
	--cl-page-head-scrim:  var(--cl-impact-scrim);

	/* The section seam that replaces $logoDividerNew (Michael, 2026-08-11: the
	   round-logo divider is "old and stale… lets do better"). A hairline with a
	   short accent tick at its left end.

	   🔑 THE TICK IS NOT A NEW IDEA — it is the amber `is-current` underline the
	   header bar and the footer columns already use, moved onto a horizontal
	   rule. That is why it reads as part of the system rather than as ornament.
	   ⚠️ Colour is --cl-orange-500 rather than --cl-amber-500 here and that is a
	   SURFACE decision, not a preference: amber is #fbb042, which measures 1.79
	   on white and all but disappears; the same amber measures 4.68 on the green
	   bar, which is why it works there. Orange is 3.36 on white — documented in
	   §1 as "fills and large text only", and a 3px graphic is exactly that.
	   📌 Both are decorative: the hairline carries the structure, so neither is
	   held to a contrast minimum. This is about it being VISIBLE, not compliant. */
	--cl-seam-line:        var(--cl-border);
	--cl-seam-tick-color:  var(--cl-orange-500);
	--cl-seam-tick-w:      4.5rem;   /* 72px */
	--cl-seam-tick-h:      3px;      /* the header's is-current border width */


	/* ----------------------------------------------------------------------
	5j. COMPONENT TOKENS — THE FILTER DRAWER + CHIPS (WP-2.10 step 5, batch 3)
	    Components live in css/components.css §10j. The drawer is built to be
	    reused by map.php's filter package later (RESKIN.md §7b) — nothing
	    here names "directory".
	---------------------------------------------------------------------- */
	--cl-drawer-w:            min(24rem, 90vw);
	--cl-drawer-bg:           var(--cl-surface);
	--cl-drawer-shadow:       0 28px 50px rgb(0 0 0 / 25%);
	--cl-drawer-backdrop:     rgb(20 20 20 / 55%);

	/* Same two-token split as --cl-subnav-dur/-dur-backdrop (§5c-b) and the
	   same reason: the panel and its backdrop are timed separately so the
	   backdrop can start fading while the panel is still sliding. */
	--cl-drawer-dur:          350ms;
	--cl-drawer-dur-backdrop: 250ms;
	--cl-drawer-ease:         var(--cl-ease-out);

	/* The filter chips (checkbox + label, styled as a toggle pill). Resting
	   state uses the same neutral surface/border pair as everywhere else on
	   the interior page; active state borrows the brand green rather than
	   inventing a third accent colour for "selected". */
	--cl-filter-chip-bg:            var(--cl-surface-accent);
	--cl-filter-chip-border:        var(--cl-border);
	--cl-filter-chip-text:          var(--cl-text);
	--cl-filter-chip-bg-active:     var(--cl-green-500);
	--cl-filter-chip-border-active: var(--cl-green-500);
	--cl-filter-chip-text-active:   var(--cl-on-brand);   /* 7.36 on green-500 */


	/* ----------------------------------------------------------------------
	5e. COUNTDOWN — WP-2.8
	    The component lives in css/components.css §4; the target date is set in
	    php/anamorphics-cms-inits.php.

	    ⚠️ THE COUNTDOWN'S ACCENT IS THE AMBER, NOT THE BRAND ORANGE, AND IT IS
	    ONLY LEGIBLE ON A DARK SURFACE. --cl-amber-500 measures 11.3:1 on the
	    bar's black and 9.9:1 on --cl-black, both comfortably AA. On white it is
	    1.86:1 — it fails AA, AA-large and SC 1.4.11's 3:1 for the circle
	    borders, all three. That is why the inline clock ships a dark card by
	    default and why placing one on a white band means adding
	    .cl-countdown--on-light, which swaps the accent for --cl-orange-700
	    (5.67:1). Do not "unify" these two accents; they exist because one
	    colour cannot serve both surfaces.
	---------------------------------------------------------------------- */

	/* The fixed bar. Pure black rather than --cl-black (#141414) so it reads as
	   a separate object under the footer's legal bar (#0d0d0d) instead of a
	   near-match that looks like a rendering artefact. */
	--cl-countdown-bar-bg:     #000000;
	--cl-countdown-bar-accent: var(--cl-amber-500);  /* 11.3 on the above */
	--cl-countdown-bar-title:  var(--cl-white);      /* 21.0 */

	/* The inline clock. Both values are reassigned by .cl-countdown--on-light,
	   which is the entire mechanism for putting one on a light band. */
	--cl-countdown-bg:         var(--cl-black);
	--cl-countdown-accent:     var(--cl-amber-500);  /*  9.9 on --cl-black */
	--cl-countdown-label:      var(--cl-white);      /* 18.4 */


	/* ----------------------------------------------------------------------
	5f. SCRIM — WP-2.7
	    For text laid over a photograph.

	    ⚠️ 0.85 IS DERIVED, NOT CHOSEN. Compositing happens in sRGB, so a scrim
	    rgba(20,20,20,a) over a photo pixel c resolves to a*20 + (1-a)*c per
	    channel. The worst case a photo can present is pure white (c = 255), so
	    solve each text colour against that:

	        --cl-amber-500  .badge-dugout, .badge-featured   a >= 0.754
	        --cl-sand-500   .badge-CL-favorite               a >= 0.839  ← binds
	        #fff            the year labels                  a >= 0.580

	    0.85 is the next round step above the binding one. Measured worst case:
	    amber 6.46:1 · white 11.90:1 · sand 4.70:1, all past the 4.5:1 that
	    9.6px text needs.

	    🔑 SET FOR THE WORST PHOTO THAT COULD EVER BE UPLOADED, NOT TODAY'S.
	    These images come from the CMS. A scrim tuned to the current library
	    passes an audit and then fails the first time somebody uploads a brighter
	    headshot — silently, because nothing errors and axe reports this case as
	    `incomplete` rather than as a violation. That is exactly how the previous
	    0.5 survived: it was never measured, only looked at.

	    ⚠️ IF A NEW TEXT COLOUR IS EVER PUT ON THIS SCRIM, RE-RUN THE SOLVE.
	    The sand is the binding constraint by a wide margin and anything lighter
	    than it moves the answer.

	    Was #14141480 (0.5) on all three badges, at which every one of the 54
	    Hall of Fame name labels failed AA — 1.87:1 at worst — along with 51 of
	    the 54 white year labels and both directory badges.
	---------------------------------------------------------------------- */

	--cl-scrim-legible: rgba(20, 20, 20, 0.85);


	/* ----------------------------------------------------------------------
	6. MOTION · FOCUS
	---------------------------------------------------------------------- */

	--cl-duration-fast: 120ms;
	--cl-duration-base: 220ms;
	--cl-duration-slow: 400ms;
	--cl-ease:          cubic-bezier(0.2, 0, 0.2, 1);
	--cl-ease-out:      cubic-bezier(0, 0, 0.2, 1);

	/* Focus ring. Two-tone "sandwich": an inner ring in the surface color and
	   an outer ring in brand green. One of the two always contrasts, so the
	   same token works on white pages AND on the dark navbar/footer without
	   needing a per-context override. */
	--cl-focus-inner: var(--cl-white);
	--cl-focus-outer: var(--cl-green-500);
	--cl-focus-ring:  0 0 0 2px var(--cl-focus-inner), 0 0 0 5px var(--cl-focus-outer);
}


/* ==========================================================================
7. BRIDGE TO BOOTSTRAP 5.3
   Feed the tokens into Bootstrap's own CSS variables so framework components
   inherit the brand without per-component overrides.
   Caveat worth knowing: setting --bs-primary does NOT restyle .btn-primary —
   button colors come from --bs-btn-* set inside each .btn-* rule. Button
   theming stays in styles-custom.css.
========================================================================== */

:root {
	--bs-body-font-family: var(--cl-font-body);
	--bs-body-font-size:   var(--cl-text-base);
	--bs-body-line-height: var(--cl-leading-body);
	--bs-body-color:       var(--cl-text);
	--bs-body-color-rgb:   35, 35, 35;
	--bs-body-bg:          var(--cl-surface);

	--bs-secondary-color:  var(--cl-text-secondary);

	--bs-primary:          var(--cl-primary);
	--bs-primary-rgb:      33, 85, 61;

	--bs-link-color:            var(--cl-link);
	--bs-link-color-rgb:        33, 85, 61;
	--bs-link-hover-color:      var(--cl-link-hover);
	--bs-link-hover-color-rgb:  175, 69, 38;

	--bs-border-color:   var(--cl-border);
	--bs-border-radius:  var(--cl-radius-md);
	--bs-border-radius-sm: var(--cl-radius-sm);
	--bs-border-radius-lg: var(--cl-radius-lg);

	--bs-focus-ring-color: rgb(33 85 61 / 40%);
}

/* BUTTONS MOVED OUT — WP-2.4.
   WP-2.2 put button COLOR here and left button SHAPE in styles-custom.css.
   Splitting one component across two files is what let a 60%-opacity green and
   an invalid `.25remrgba` focus rule coexist unnoticed, so WP-2.4 consolidated
   the whole component — color, shape, variants, states — into
   **css/components.css**, which loads after styles-custom.css.

   What stays here is the token layer proper: the --cl-btn-* values in §5b.
   Change those to resize or reshape every button; change components.css to add
   or restyle a variant.

   The caveat that made this section necessary in the first place still holds:
   setting --bs-primary above does NOT restyle .btn-primary, because Bootstrap
   5.3 colors buttons from --bs-btn-* variables declared inside each .btn-*
   rule. Any new variant must set those explicitly. */

/* Text container cap — see --cl-container-max.
   Kept at min-width 1500px so the 1440 cap never eats the gutters. */
@media (min-width: 1500px) {
	.container,
	.container-sm,
	.container-md,
	.container-lg,
	.container-xl,
	.container-xxl {
		max-width: var(--cl-container-max);
	}
}


/* ==========================================================================
7b. THE CARD — the site shell, WP-2.10. Values in §5a.

    Replaces WP-2.2's ultrawide frame (1800px white shell, 1px green ring, soft
    lift, green-tinted body gradient). Michael's instruction 2026-08-06: the
    rounded card on the beige field is design, not canvas chrome, and the whole
    site lives on it at every breakpoint.

    The wrapper is opened in navigation.php and closed in footer.php, so every
    page inherits this from the two shared includes — no per-page markup.
    `html body` (0,0,2) beats Bootstrap's `body` (0,0,1), so the field colour
    wins over --bs-body-bg without !important.

    🔑 THREE BOXES, DRAWN WITH TWO ELEMENTS. The design nests field → card →
    white page. There is only one element available here, so the middle box —
    the 10px beige matte — is drawn as an opaque spread box-shadow around the
    white page rather than as a third wrapper. That is what keeps step 1 to
    "no page markup changed", per the build order: this whole section reverts by
    restoring one rule.

    What that costs, stated so nobody hunts for it: a single element cannot hold
    two independent corner radii, so the outer radius is forced to
    --cl-page-radius + --cl-card-matte = 18px, against the design's declared
    16px. The design's own numbers are already inconsistent here (it declares an
    8px inner radius inside a 10px pad inside a 16px outer, which cannot all be
    concentric), and the inner radius is the one that matters because it is what
    clips content — so the inner value is the one kept true. If the 2px ever
    matters, step 3 is already rewriting these includes and can promote the
    matte to a real element.

    ⚠️ THE FIXED COUNTDOWN BAR STILL SPANS THE VIEWPORT, so until step 3 it
    crosses the beige field edge to edge under the card. That is expected in a
    step-1 screenshot, not a bug. Michael's decision 2026-08-07: the bar becomes
    an ordinary block inside the card, which also retires the --cl-countdown-h
    ResizeObserver in js/footer.js, .cl-footer--has-countdown, and the consent
    banner's bottom offset.

    ⚠️ THE SKIP LINK NEEDED NO CHANGE, and this was checked rather than assumed
    — the reskin doc expected it to need re-deriving. It is `position: fixed` at
    top/left 0.75rem (components.css §5), so it is viewport-anchored and the
    card being inset 40px does not move it; it now lands on the beige field
    instead of the white page, and it carries its own green background, so its
    contrast is unchanged. It stays outside the wrapper and stays
    `transform`-moved.
========================================================================== */

html body {
	background: var(--cl-surface-field);

	/* The gap between the card and the browser edge. `background-attachment:
	   fixed` went with the gradient — a flat colour has nothing to park. */
	padding: var(--cl-field-pad);
}

/* Mobile steps. Provisional — large screens first, per Michael. 40px a side on
   a 375px phone leaves a 295px page, so the field has to give way before the
   content does. Chosen against Bootstrap's own breakpoints (lg 992, md 768). */
@media (max-width: 991.98px) {
	html body { --cl-field-pad: 24px; }
}
@media (max-width: 767.98px) {
	html body { --cl-field-pad: 12px; }
}

.cl-site-wrapper {
	/* --cl-site-max is the card's OUTER width, matte included, so the white
	   page inside it is that minus a matte on each side. Written as arithmetic
	   rather than as 1300px so the two tokens cannot drift apart. */
	max-width: calc(var(--cl-site-max) - 2 * var(--cl-card-matte));
	margin-inline: auto;
	background: var(--cl-surface);
	position: relative;

	border-radius: var(--cl-page-radius);

	/* ⚠️ `clip`, NOT `hidden`. The VALUE is unchanged since WP-2.5; the REASON
	   is not, and the old reason no longer applies — so it is rewritten here
	   rather than left to mislead the next reader.

	   WP-2.5 needed `clip` because `overflow: hidden` makes an element a scroll
	   container, and a `position: sticky` descendant then resolves against THAT
	   instead of the viewport. Since this wrapper is taller than the screen and
	   never scrolls itself, the sticky header simply scrolled away — measured on
	   index.php, the nav's top was -790px after 790px of scroll, and 0 with
	   `clip`. WP-2.10 retires the sticky header (Michael, 2026-08-06; the design
	   contains zero `position: sticky` and zero `position: fixed`), so that
	   specific failure can no longer happen.

	   `clip` is still correct, for a new reason: the wrapper now has a
	   `border-radius`, and it must contain its children inside those rounded
	   corners — the header's green bar is the first child and would otherwise
	   square off the top two. `hidden` would clip identically and then hand a
	   scroll container to whatever gets built next, for no benefit. `clip`
	   clips without creating one. Baseline since Chrome 90 / Firefox 81 /
	   Safari 16.

	   Generalise it anyway: if a sticky element ever "doesn't stick", check
	   every ancestor's overflow before touching the element itself. */
	overflow: clip;

	/* THE MATTE IS A SHADOW, NOT A BORDER OR A PARENT — see the 🔑 above.

	   Order is load-bearing. box-shadow paints the first-listed layer on top,
	   and the matte is fully opaque, so anything meant to sit ON the matte has
	   to be listed before it. Hence: the page's own hairline lift first, then
	   the opaque matte ring, then the card's drop shadow underneath both.
	   Reversing the first two makes the page's shadow vanish with nothing to
	   suggest why.

	   All three are box-shadows rather than borders for the reason WP-2.2 gave
	   and which still holds: a spread shadow paints entirely outside the border
	   box, so when the viewport is narrower than the card the frame simply
	   falls off-screen instead of leaving stray lines pinned to the screen
	   edges — no media query, and nothing hardcoding --cl-site-max a second
	   time. (With the field padding above, that edge case now only arises below
	   ~400px, but the property is free.) */
	box-shadow:
		var(--cl-page-shadow),
		0 0 0 var(--cl-card-matte) var(--cl-surface-card),
		var(--cl-card-shadow);
}


/* ==========================================================================
8. BASELINE — type scale + accessible focus applied globally.
   These are deliberately unopinionated: they set the scale and restore focus
   visibility, and leave everything else to styles-custom.css.
========================================================================== */

/* WP-2.3: the display face is scoped to h1/h2 — the display scale. h3–h6 stay
   on the body face, because 14 of this site's heading selectors are weight 600
   and erbaum has no 600. Component overrides that are display-sized but tagged
   h3 (`.callouts-header h3` reaches 2.4em) deliberately keep the body face too;
   re-tagging them is Phase 4/5 work, not a typography swap. */
h1, .h1 { font-size: var(--cl-text-h1); line-height: var(--cl-leading-tight);
          font-family: var(--cl-font-display); }
h2, .h2 { font-size: var(--cl-text-h2); line-height: var(--cl-leading-tight);
          font-family: var(--cl-font-display); }
h3, .h3 { font-size: var(--cl-text-h3); line-height: var(--cl-leading-snug); }
h4, .h4 { font-size: var(--cl-text-h4); line-height: var(--cl-leading-snug); }
h5, .h5 { font-size: var(--cl-text-h5); line-height: var(--cl-leading-snug); }
h6, .h6 { font-size: var(--cl-text-h6); line-height: var(--cl-leading-snug); }

/* Prose measure — opt-in, for article bodies at wide container widths */
.cl-prose { max-width: var(--cl-measure); }

/* --------------------------------------------------------------------------
FOCUS VISIBILITY — WCAG 2.2 SC 2.4.7 / 2.4.13

Restores a visible keyboard focus indicator. The previous CSS removed it in
three places: .btn:focus (an invalid `.25remrgba` value that the parser
dropped, and a zero-alpha shadow behind it), .navbar-toggler:focus
(`box-shadow: 0 0 0 0rem`) and .accordion-button:focus (`outline: 0` plus a
`0 0 0 0rem` shadow). :focus-visible means mouse users still see no ring.
-------------------------------------------------------------------------- */

:focus-visible {
	outline: none;
	box-shadow: var(--cl-focus-ring);
	border-radius: var(--cl-radius-sm);
}

.btn:focus-visible,
.navbar-toggler:focus-visible,
.accordion-button:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible,
.nav-link:focus-visible,
.dropdown-item:focus-visible,
.page-link:focus-visible {
	outline: none;
	box-shadow: var(--cl-focus-ring);
}

/* Form fields get the brand ring on plain :focus too — a text input that is
   focused but shows no indicator is the same failure, and :focus-visible
   already matches text inputs on click in every current browser. */
.form-control:focus,
.form-select:focus {
	border-color: var(--cl-border-brand);
	box-shadow: 0 0 0 0.25rem var(--bs-focus-ring-color);
}

/* --------------------------------------------------------------------------
REDUCED MOTION — honours the OS setting. WP-2.7 will extend this; the token
layer sets the global floor so components inherit it.
-------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	:root {
		--cl-duration-fast: 0.01ms;
		--cl-duration-base: 0.01ms;
		--cl-duration-slow: 0.01ms;
		/* 🗑️ FOUR HEADER MOTION TOKENS WERE ZEROED HERE AND ARE GONE (WP-2.10
		   step 3). They existed for a reason the `*` rule below could not cover:
		   js/header.js READ --cl-header-lock to size its latch, and a latch still
		   sized 480ms against instant transitions would have swallowed state
		   changes for half a second. Both the script and the sticky header it
		   drove are deleted, so there is nothing left to zero — and a media query
		   resetting tokens nobody reads is exactly the kind of thing that
		   survives three refactors and then puzzles someone. */

		/* 🐛 THE `*` RULE BELOW DOES NOT COVER THE SUBNAV, AND THE GAP IS A REAL
		   FOCUS BUG RATHER THAN A COSMETIC ONE. It zeroes transition-DURATION;
		   it says nothing about transition-DELAY. The curtain hides itself with
		   `visibility: hidden` on a delay equal to its slide duration, so the
		   links stay focusable until the slide finishes and only then leave the
		   tab order. With the `*` rule alone the slide would end in 0.01ms while
		   the visibility delay still ran the full 550ms — leaving a closed,
		   off-screen menu tabbable for half a second, for exactly the users most
		   likely to be tabbing. Zeroing the token collapses the delay too.
		   🔑 Generalise it: `transition: all 0s !important` is not a complete
		   reduced-motion policy while any component depends on a delay. */
		--cl-subnav-dur:          0.01ms;
		--cl-subnav-dur-backdrop: 0.01ms;

		/* Same delay trap as the subnav curtain, same fix — the filter drawer
		   hides itself with `visibility: hidden` on a delay tied to --cl-drawer-dur
		   (components.css §10j). */
		--cl-drawer-dur:          0.01ms;
		--cl-drawer-dur-backdrop: 0.01ms;
	}
	*, *::before, *::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}


/* ==========================================================================
9. DEPRECATED ALIASES — do not use in new code.
   These keep the pre-WP-2.2 rules in styles-custom.css working. Retire each
   one as its call sites are migrated to --cl-*.
   --bs-black / --bs-white are deliberately NOT redefined here: they are real
   Bootstrap variables and the old override was a namespace collision.
========================================================================== */

:root {
	--bs-dark-green:   var(--cl-green-500);
	--bs-dark-brown:   var(--cl-brown-500);
	--bs-light-brown:  var(--cl-sand-500);
	--bs-dark-orange:  var(--cl-orange-500);
	--bs-bright-orange:var(--cl-amber-500);
	--bs-pale-yellow:  var(--cl-yellow-500);
	--bs-dark-blue:    var(--cl-blue-500);
	--bs-light-black:  var(--cl-ink);
	--s-border-radius: var(--cl-radius-md);
	--shadow-text-dark: var(--cl-shadow-text-dark);
}
