/* ==========================================================================
COMPONENT LAYER — Cactus League 2027
==========================================================================

Opened by WP-2.4 (buttons). This is where Phase 2's components live — buttons
now, header/nav (WP-2.5), footer (WP-2.6) and the countdown (WP-2.8) next.

LOAD ORDER MATTERS AND IS DELIBERATE (header.php):

    bootstrap.min.css  →  tokens.css  →  styles-custom.css  →  components.css

Last means a component can override a legacy rule in styles-custom.css without
!important. It also means it can override tokens.css §8's global :focus-visible
rule — which is load-bearing, see the border-radius note under §1 below.

RULES FOR THIS FILE
  1. Reference --cl-* tokens, never raw hex. If a value is missing, add it to
     tokens.css rather than hardcoding it here.
  2. Express anything Bootstrap can express as a --bs-* variable AS one, so the
     framework's own modifiers (.btn-sm, .btn-lg, .disabled) keep working.
     Declaring the final property directly is what broke .btn-sm/.btn-lg before
     WP-2.4; see §1.
  3. Never suppress a focus indicator. tokens.css §8 restores :focus-visible
     across the site after it had been removed in three places; nothing here
     may set `outline: 0`, `outline: none` or a zero-alpha/zero-spread
     box-shadow on :focus or :focus-visible.
========================================================================== */


/* ==========================================================================
1. BUTTONS — WP-2.4
==========================================================================

Three variants and two context modifiers:

    .btn-cl-primary     solid brand green    the default action
    .btn-cl-secondary   green outline        the second choice on a light page
    .btn-cl-accent      solid orange         conversion CTAs (tickets, buy, join)

    .btn-cl-on-light    modifier: solid variant sitting on a white surface
    .btn-cl-on-dark     modifier: outline variant sitting on a dark panel

.btn-primary is an alias of .btn-cl-primary so the twelve existing call sites
keep rendering exactly as before — WP-2.4 changes no markup.

WHY THE DEFAULT ASSUMES A COLORED PANEL. All twelve .btn-primary call sites put
the button inside .bg-green / .bg-brown / .bg-alt or over a photograph, and
eleven of them are `d-grid` so the button is full width. That is why the resting
border is WHITE: a green button on a dark green panel disappears into it, and
the hairline is the only thing keeping the control findable. .btn-cl-on-light
swaps that hairline for a tone-matched dark one where the surface is white.

TWO BUGS THIS FIXES, both measured on the running site 2026-07-29:

 🐛 .btn-sm AND .btn-lg WERE INERT. A `.btn-primary` measured 34px / 5.6px
    padding / 13.76px type with .btn-sm, with .btn-lg, and with neither — three
    identical results — while a stock .btn-success went 38px → 48px correctly.
    Cause: styles-custom.css set `padding` and `font-size` as direct properties,
    which beat Bootstrap's `.btn { padding: var(--bs-btn-padding-y) ... }` no
    matter what .btn-lg puts in those variables. Everything below goes through
    the variables instead, which is what makes the size scale real.

 🐛 THE DISABLED STATE RENDERED AT 1.65:1. tokens.css shipped white text on
    --cl-green-300 (2.25:1 on its own), and Bootstrap then composites
    `opacity: var(--bs-btn-disabled-opacity)` = .65 on top of it, landing at an
    effective 1.65:1. Not a WCAG 1.4.3 violation — inactive controls are exempt
    — but illegible in practice, and a good example of a value that looks fine
    in the file and is not what reaches the screen. Now opacity is pinned to 1
    with explicit neutral colors at 4.06:1.

⚠️ BORDER-RADIUS IS DECLARED DIRECTLY AS WELL AS THROUGH THE VARIABLE, and that
   is not redundant. tokens.css §8 has a global
   `:focus-visible { border-radius: var(--cl-radius-sm) }`. It and Bootstrap's
   own `.btn { border-radius: var(--bs-btn-border-radius) }` are both
   specificity (0,1,0), and tokens.css loads after bootstrap.min.css — so
   :focus-visible wins, and a pill whose radius came ONLY from the variable
   would square off the instant it received keyboard focus. Measured with an
   `<input class="btn">` (text inputs match :focus-visible even on programmatic
   focus): 9.6px → 4px for a variable-only radius, unchanged for a directly
   declared one. The direct declaration in this later file is what holds the
   shape. Keep both lines.
-------------------------------------------------------------------------- */

/* ---- 1a. Shape — shared by every variant ---- */

.btn {
	--bs-btn-padding-y:     var(--cl-btn-padding-y);
	--bs-btn-padding-x:     var(--cl-btn-padding-x);
	--bs-btn-font-size:     var(--cl-btn-font-size);
	--bs-btn-font-weight:   var(--cl-weight-semibold);
	--bs-btn-border-width:  var(--cl-btn-border-width);
	--bs-btn-border-radius: var(--cl-btn-radius);

	/* See the ⚠️ note above — this line is what survives :focus-visible. */
	border-radius: var(--cl-btn-radius);

	letter-spacing: var(--cl-tracking-wide);
	text-transform: uppercase;

	/* Enumerated rather than `transition: all`, so a future transform or
	   box-shadow change cannot accidentally animate the focus ring. */
	transition:
		background-color var(--cl-duration-fast) var(--cl-ease),
		border-color     var(--cl-duration-fast) var(--cl-ease),
		color            var(--cl-duration-fast) var(--cl-ease);
}

/* Size modifiers. These must come AFTER .btn: they are the same specificity
   (0,1,0), so it is source order that lets them win. Bootstrap's own px values
   are replaced with token values so the scale stays on-brand. */
.btn-sm {
	--bs-btn-padding-y: var(--cl-btn-padding-y-sm);
	--bs-btn-padding-x: var(--cl-btn-padding-x-sm);
	--bs-btn-font-size: var(--cl-btn-font-size-sm);
}
.btn-lg {
	--bs-btn-padding-y: var(--cl-btn-padding-y-lg);
	--bs-btn-padding-x: var(--cl-btn-padding-x-lg);
	--bs-btn-font-size: var(--cl-btn-font-size-lg);
}

/* Disabled — one neutral treatment for every variant. Pinning the opacity to 1
   is the point: Bootstrap's .65 default is what turned the old pale-green
   disabled state into an effective 1.65:1. */
.btn {
	--bs-btn-disabled-opacity:      1;
	--bs-btn-disabled-color:        var(--cl-btn-disabled-color);
	--bs-btn-disabled-bg:           var(--cl-btn-disabled-bg);
	--bs-btn-disabled-border-color: var(--cl-btn-disabled-border);
}


/* ---- 1b. PRIMARY — solid brand green. The default action. ----
   Contrast, white label on the fill: rest 8.64:1 · hover 11.19 · active 12.75.
   Was rgb(49 111 79 / 60%) before WP-2.2, which resolved to #83a995 over white
   and gave 2.60:1 — least readable at rest, legible only on hover. */

.btn-cl-primary,
.btn-primary {
	--bs-btn-color:               var(--cl-text-inverse);
	--bs-btn-bg:                  var(--cl-primary);
	--bs-btn-border-color:        var(--cl-white);
	--bs-btn-hover-color:         var(--cl-text-inverse);
	--bs-btn-hover-bg:            var(--cl-primary-hover);
	--bs-btn-hover-border-color:  var(--cl-white);
	--bs-btn-active-color:        var(--cl-text-inverse);
	--bs-btn-active-bg:           var(--cl-primary-active);
	--bs-btn-active-border-color: var(--cl-primary-active);
}


/* ---- 1c. SECONDARY — green outline, fills on hover. ----
   The variant this site genuinely lacked: every button on it was the primary.
   Contrast: green-500 label on white 8.64:1, and the same green as the border
   is 8.64:1 against white — far past the 3:1 that WCAG 2.2 SC 1.4.11 asks of a
   control boundary. Hover flips to the solid primary fill, so a secondary and a
   primary side by side resolve to the same shape and weight on interaction. */

.btn-cl-secondary {
	--bs-btn-color:               var(--cl-primary);
	--bs-btn-bg:                  transparent;
	--bs-btn-border-color:        var(--cl-primary);
	--bs-btn-hover-color:         var(--cl-text-inverse);
	--bs-btn-hover-bg:            var(--cl-primary);
	--bs-btn-hover-border-color:  var(--cl-primary);
	--bs-btn-active-color:        var(--cl-text-inverse);
	--bs-btn-active-bg:           var(--cl-primary-active);
	--bs-btn-active-border-color: var(--cl-primary-active);
}


/* ---- 1d. ACCENT — solid orange. Conversion CTAs only. ----
   Tickets, buy, join, subscribe — the actions Phase 3's schedule page and
   WP-2.6's newsletter signup need and that no current variant expresses.

   ⚠️ NOT the brand orange. --cl-orange-500 (#f15a2e) gives a white label
   3.36:1, which is fine for a large graphic fill but fails AA for button text,
   which is normal-size text. --cl-orange-700 (#af4526) carries the same accent
   role at 5.67:1 · hover 7.50 · active 10.43. Use orange-500 for fills and
   large display text, never for a control label.

   Use sparingly. An accent that appears on every button is not an accent —
   one per view, on the action the page exists to get. */

.btn-cl-accent {
	--bs-btn-color:               var(--cl-text-inverse);
	--bs-btn-bg:                  var(--cl-orange-700);
	--bs-btn-border-color:        var(--cl-white);
	--bs-btn-hover-color:         var(--cl-text-inverse);
	--bs-btn-hover-bg:            var(--cl-orange-800);
	--bs-btn-hover-border-color:  var(--cl-white);
	--bs-btn-active-color:        var(--cl-text-inverse);
	--bs-btn-active-bg:           var(--cl-orange-900);
	--bs-btn-active-border-color: var(--cl-orange-900);
}


/* ---- 1e. CONTEXT MODIFIERS ----
   Specificity (0,2,0) beats the (0,1,0) variant rules, so these compose with
   any variant and need no !important. */

/* Solid variant on a white / light surface: swap the white hairline for a
   tone-matched dark one. The white border is invisible rather than harmful on
   white — verified on directory.php, where .clear-filters-btn has rendered a
   white border on a light gradient for years without anyone noticing — so this
   is an upgrade for new work, not a fix applied retroactively to old markup. */
.btn-cl-on-light.btn-cl-primary,
.btn-cl-on-light.btn-primary {
	--bs-btn-border-color:       var(--cl-primary-active);
	--bs-btn-hover-border-color: var(--cl-primary-active);
}
.btn-cl-on-light.btn-cl-accent {
	--bs-btn-border-color:       var(--cl-orange-900);
	--bs-btn-hover-border-color: var(--cl-orange-900);
}

/* Outline variant on a dark panel or photograph: white label and hairline,
   inverting to a white fill with a green label on hover. White on the
   .bg-green panel measures 16.29:1 and on .bg-brown 15.51:1. */
.btn-cl-on-dark.btn-cl-secondary {
	--bs-btn-color:               var(--cl-text-inverse);
	--bs-btn-border-color:        var(--cl-white);
	--bs-btn-hover-color:         var(--cl-primary);
	--bs-btn-hover-bg:            var(--cl-white);
	--bs-btn-hover-border-color:  var(--cl-white);
	--bs-btn-active-color:        var(--cl-primary);
	--bs-btn-active-bg:           var(--cl-gray-200);
	--bs-btn-active-border-color: var(--cl-gray-200);
}


/* ---- 1f. LEGACY ----

   .btn-dark is DEPRECATED. Its name has never matched its color — it renders
   the mid-green #316f4f (--cl-primary-soft, white label 5.97:1), not a dark
   button, which is exactly the kind of misnamed class that gets copied into new
   markup by someone reading the class list rather than the page.

   One call site: schedule-custom.php:200, the "Display Custom Schedule" submit.
   Deliberately NOT migrated here. That button is its page's primary action, so
   the honest replacement is .btn-cl-primary.btn-cl-on-light rather than the
   outline variant — a visible change to a page that SCHEDULE-SPEC.md already
   has 301-ing to /schedule.php in Phase 3. Retiring the class and the page
   together is one change instead of two. Colors are unchanged from WP-2.2. */

.btn-dark {
	--bs-btn-color:               var(--cl-text-inverse);
	--bs-btn-bg:                  var(--cl-primary-soft);
	--bs-btn-border-color:        var(--cl-black);
	--bs-btn-hover-color:         var(--cl-text-inverse);
	--bs-btn-hover-bg:            var(--cl-ink);
	--bs-btn-hover-border-color:  var(--cl-black);
	--bs-btn-active-color:        var(--cl-text-inverse);
	--bs-btn-active-bg:           var(--cl-black);
	--bs-btn-active-border-color: var(--cl-black);
}

/* .storeCTA is a hand-rolled button on a <div>, not a .btn — five store pages
   build it from CMS content (Items.info8), so the markup cannot become an <a>
   without knowing what that field holds. It therefore cannot inherit any of the
   above, and WP-2.2 tokenized it in place. What it CAN do is consume the same
   --cl-btn-* tokens, so it stops drifting away from the real buttons every time
   the button shape changes. Folding it into a genuine variant needs the CMS
   field audited first — logged for Phase 4's store work.

   SHAPE TOKENS ONLY — deliberately no border. styles-custom.css sets
   `border-color: var(--cl-white)` with no border-style, so this element has
   never actually painted a border, and on the store-membership pages it sits on
   a WHITE background where the site's usual white hairline would be invisible
   anyway. Adding one would be a design change to CMS-driven markup, which is
   what the audit above is for. It is the natural first user of
   .btn-cl-on-light once it becomes a real button. */
.storeCTA {
	font-size:     var(--cl-btn-font-size);
	padding:       var(--cl-btn-padding-y) var(--cl-btn-padding-x);
	border-radius: var(--cl-btn-radius);
}


/* ==========================================================================
2. HEADER + NAV — WP-2.10 step 3 (replaces WP-2.5 / WP-2.6)
==========================================================================

ONE STATE, one bar. Tokens in tokens.css §5c.

🔑 WHAT WAS DELETED HERE, AND WHY IT IS A DELETION RATHER THAN A REFACTOR.
Michael, 2026-08-06: no more sticky header — the key pages now live in the
footer too, so it no longer has to follow the user. The design agrees, and that
was verified by grep rather than by eye: zero `position: sticky` and zero
`position: fixed` in the whole file. That removed, in one go:

  · `position: sticky` and the condense-on-scroll state machine (.is-condensed)
  · the TWO scroll sentinels 240px apart, and the whole reason they existed —
    a single trigger point oscillated, because condensing removed the header's
    own height from the document, scroll anchoring pulled the page back across
    that threshold, and the state flipped forever
  · the stacked full/minimal logo cross-fade, and with it the only reference
    anywhere to img/Cactus-League-Simple.svg
  · .is-resizing, which guarded a real Chrome bug: a transitioned property whose
    value comes from var() does not pick up a change to that var, so every
    breakpoint step left half the header sized for the breakpoint it had left
  · js/header.js — deleted, ~200 lines
  · --cl-header-dur / -swap / -dur-badge / -lock, and --cl-header-brand-h

⚠️ ~520 LINES WENT. If something in the header looks unfinished, check whether
it was a property of the condensed state before assuming it regressed — most of
the old complexity was two states reconciling, and there is now only one.

⚠️ THE OVERHANG IS THE ONE PIECE OF THE OLD ARITHMETIC THAT SURVIVES, and it
survives inverted. WP-2.5/2.6 sized the BAR around the badge, so a smaller
overhang made the bar taller (the badge's bottom edge is always pad-y + logo).
The 2027 bar is a fixed 96px and the badge simply overflows it, so the two are
independent now and the identity that made the old header hard to retune is
gone. Read the ⚠️ on --cl-header-overhang in tokens.css §5c before changing it.
========================================================================== */

/* THE HEADER RESERVES THE OVERHANG, and that is the whole reason this rule
   exists rather than the badge just hanging.

   In the design the 200px badge sits in a 96px bar and overlaps the hero
   photograph below it. We cannot assume a hero: 58 of the 59 pages open with
   ordinary content, and an unreserved overhang would lay the badge across their
   <h1>. So the header pushes the page down by exactly the amount the badge
   overflows, and nothing is ever covered.

   Step 4's homepage may pull its hero up into this space with a negative margin
   to get the design's exact overlap. That is a per-page decision and it belongs
   there, not here — the shared include must be safe on every page by default. */
/* 🔑 THE HEADER'S GEOMETRY IS DECLARED ON THE WRAPPER, NOT ON THE HEADER, AND
   THAT MOVED IN STEP 5 BATCH 1 FOR ONE REASON: A SECOND COMPONENT NOW NEEDS IT.

   The rule it obeys has not changed and is still tokens.css §5c's 🐛 — a custom
   property is computed in the scope where it is DECLARED, so a calc at :root
   substitutes :root's inputs once and every descendant inherits that finished
   number. Declared at :root, --cl-header-overhang measured 110px at all six
   breakpoints instead of 110/78/70/48. The fix was to declare it on the same
   element the media queries target.

   What changed is WHICH element that is. Custom properties inherit DOWN, and
   `.cl-page-head` (§10a) is inside <main> — a SIBLING of <header>, so nothing
   declared on `.cl-header` is visible to it. The page head needs two of these
   numbers: --cl-lead-inset for its left edge and --cl-header-overhang for its
   minimum height. Re-deriving either one over there would mean duplicating five
   tokens that get overridden at four breakpoints — the exact "four numbers kept
   in sync by hand" §8a rejected when the homepage hero faced this from the
   other side. Declared here, on the nearest ancestor BOTH subtrees share, each
   number exists once and every breakpoint step re-runs both calcs for free.

   ⚠️ SO THE RESPONSIVE STEPS BELOW TARGET `.cl-site-wrapper`, NOT `.cl-header`.
   Moving any of --cl-header-pad-x / -logo / -logo-box-w / -logo-top / -h back
   onto `.cl-header` re-freezes both calcs at the :root values and breaks the
   page head silently — it renders correctly at one width and wrong at five.
   Everything still READS the tokens from wherever it sits; only the declaration
   moved up one level. */
.cl-site-wrapper {
	--cl-header-overhang:
		calc(var(--cl-header-logo) + var(--cl-header-logo-top) - var(--cl-header-h));

	/* The left edge of "WELCOME TO THE CACTUS LEAGUE" — the bar's inline
	   padding, plus the slot the badge occupies in the flex row, plus
	   `.cl-header__lead`'s gap. Not measured off the rendered welcome line and
	   not a copied pixel value: it is the same three terms the flex row itself
	   resolves, so the two edges cannot drift.
	   ⚠️ --cl-header-logo-box-w, NOT --cl-header-logo. The badge is 200px of
	   artwork in a 180px slot and deliberately overhangs it to the right (see
	   tokens.css §5c), so the artwork's right edge lands 28–32px SHORT of this
	   inset at every desktop step — checked at 1440/1100/900, not assumed. That
	   clearance is what lets the page head sit beside the badge without the
	   badge ever crossing into text. */
	--cl-lead-inset:
		calc(var(--cl-header-pad-x) + var(--cl-header-logo-box-w) + var(--cl-space-7));
}

.cl-header {
	margin-bottom: var(--cl-header-overhang);

	/* The subnav curtain positions from this element's top edge (§2c), so the
	   header has to be its containing block. ⚠️ NO z-index HERE. Giving the
	   header one would open a stacking context around both the bar and the
	   curtain, and the curtain would then be trapped inside it — unable to
	   paint below the bar, which is the entire illusion. The bar's own
	   `z-index: 30` and the curtain's 20 are siblings in the ROOT stacking
	   context, and they have to stay that way. */
	position: relative;
}

/* 🗑️ THE `.navbar` NEUTRALISERS ARE GONE (step 4b), AND SO IS THE CLASS.
   Step 3 kept `.navbar` on this element and cancelled its flex layout and
   `--bs-navbar-padding-y` here, because the menu below was a Bootstrap collapse
   whose `.navbar-nav .nav-link` children read `--bs-navbar-*` variables that
   only exist on `.navbar`. Step 4b replaced the collapse with the curtain
   (§2c), so nothing in this subtree reads those variables and both the class
   and the four declarations cancelling it are dead. The `display: block` stays
   — the bar and the curtain are stacked, and it is now this element's own
   layout rather than an override of somebody else's.

   ⚠️ `z-index: 30` IS THE COMPONENT, NOT DECORATION. It is what puts the bar
   above the curtain (20) and the backdrop (10) so the panel appears to emerge
   from behind it. Both other values are meaningless without this one. */
.cl-header__bar {
	display: block;
	padding: 0;
	background: var(--cl-surface-brand);
	position: relative;
	z-index: 30;
}

/* The bar proper: a fixed-height row. Overflow is deliberately NOT hidden —
   the badge has to escape downward. */
.cl-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--cl-space-4);
	height: var(--cl-header-h);
	padding-inline: var(--cl-header-pad-x);
}

.cl-header__lead {
	display: flex;
	align-items: center;

	/* 3rem, up from 1.5rem (Michael, 2026-08-08 — the welcome line sat too close
	   to the badge). ⚠️ THE GAP IS NOT THE WHOLE GAP, which is why the fix
	   needed doubling rather than nudging: the badge is 200px of artwork inside
	   a 180px box (--cl-header-logo-box-w), so it already overhangs its own slot
	   by 20px and eats a third of the old spacing before the flex gap is
	   measured at all. */
	gap: var(--cl-space-7);

	/* The badge is absolutely positioned inside this, so the flex row needs a
	   reserved slot for it — otherwise the welcome line slides under the logo. */
	min-width: 0;
}

/* ---- The badge ----
   A fixed-size box the artwork overflows, rather than a sized image: the box is
   what the flex row measures, and the overflow is what produces the overhang.
   `height` matches the bar so the badge is vertically anchored to it at every
   breakpoint without a second variable to keep in sync. */
.cl-header__brand {
	position: relative;
	display: block;
	flex: 0 0 auto;
	width: var(--cl-header-logo-box-w);
	height: var(--cl-header-h);

	/* .navbar-brand used to supply this; the class is gone from the markup. */
	padding: 0;
	margin: 0;
}

.cl-header__logo {
	position: absolute;
	top: var(--cl-header-logo-top);
	left: 0;
	width: var(--cl-header-logo);
	height: var(--cl-header-logo);

	/* Above the bar's own stacking context so the overflowing part paints over
	   whatever the page puts below the header. */
	z-index: 40;

	/* The design's lift. It is what separates a dark badge from the dark green
	   bar behind its top half. */
	filter: drop-shadow(0 10px 22px rgb(0 0 0 / 40%));
}

/* ---- The welcome line ----
   Reinstated 2026-08-07 on Michael's instruction; WP-2.5 had dropped the
   welcome statement and WP-2.6 the tagline. White on --cl-green-500 measures
   8.64:1.

   ⚠️ NOT A HEADING, and the markup must stay a <p>. Every page already has its
   own <h1>; a heading in a shared include would give all 59 pages a second one,
   and WP-2.7 spent real time unpicking exactly that pattern elsewhere. */
.cl-header__welcome {
	margin: 0;
	font-family: var(--cl-font-display);
	font-weight: var(--cl-weight-bold);
	font-size: var(--cl-header-welcome-size);
	letter-spacing: 0.02em;
	text-transform: uppercase;
	line-height: var(--cl-leading-tight);
	color: var(--cl-text-inverse);

	/* ⚠️ `color` must be declared here, not inherited. styles-custom.css has
	   `:is(body, p, a) { color: var(--cl-text) }` — an element selector that
	   matches every <p> on the site — so a <p> in a dark band inherits nothing
	   and renders near-black on the green. That is the identical bug WP-2.6
	   found in the footer, where the signup paragraph rendered #232323 on
	   #141414 at 1.06:1. It is not inheritance failing; it is a rule that
	   matches, and inheritance only fills where nothing does. */
}

/* ---- The two inline links ---- */
.cl-header__actions {
	display: flex;
	align-items: center;
	gap: var(--cl-header-actions-gap);
	flex: 0 0 auto;
}

.cl-header__link {
	font-family: var(--cl-font-body);
	font-weight: var(--cl-weight-bold);
	/* --cl-text-ui (14px), not --cl-text-sm. ⚠️ THE TRACKING BELOW DEPENDS ON IT:
	   0.107em is the design's 1.5px expressed at 14px, so rendering at 12.8px
	   silently gave 1.37px and the comment was quietly wrong. An em tracking and
	   a rounded font-size are only consistent if the size is the one the em was
	   derived from — check both together, always. See tokens.css §3. */
	font-size: var(--cl-text-ui);
	letter-spacing: 0.107em;      /* the design's 1.5px at 14px */
	text-transform: uppercase;
	text-decoration: none;
	white-space: nowrap;

	/* --cl-on-brand-muted at rest, white on hover — 6.75:1 and 8.64:1 on the
	   bar. Both clear AA; the muted step is the design's, and it is a real
	   token rather than an opacity so the ratio is knowable. */
	color: var(--cl-on-brand-muted);

	/* The underline is a transparent border at rest, not a border that appears:
	   a border toggling between none and 3px would shift the text 3px upward on
	   hover, and the reserved space is what keeps the row still.

	   🐛 THE PADDING IS SYMMETRICAL AND THAT IS WHAT CENTRES THE LABELS AGAINST
	   THE PANCAKE (Michael, 2026-08-08: "lower Schedule and Stadium Map so they
	   are vertically centred with the pancake menu"). The row is
	   `align-items: center`, which centres each item's BORDER BOX — and with 7px
	   of padding-plus-border below the text and nothing above it, the box was
	   centred while the text inside it sat 3.5px high. The trigger next to it
	   has no such asymmetry, so the two read as misaligned.
	   🔑 `align-items: center` centres boxes, not glyphs. Any item carrying
	   one-sided padding is off-centre by half of it, and the fix belongs on the
	   item rather than on the row. The 7px is `padding-bottom + border-width`,
	   written as a calc so the two cannot drift apart.
	   ⚠️ This also answers "don't move them up when they are active": the border
	   is 3px in BOTH states (transparent → amber), so the box never changes size
	   and nothing shifts. Verified by measuring both states, not by reading. */
	padding-bottom: 4px;
	padding-top: calc(4px + 3px);
	border-bottom: 3px solid transparent;

	transition: color var(--cl-duration-fast) var(--cl-ease),
	            border-color var(--cl-duration-fast) var(--cl-ease);
}

.cl-header__link:hover,
.cl-header__link:focus-visible {
	color: var(--cl-text-inverse);
}

/* Current page. The amber is the design's marker; `aria-current="page"` in the
   markup is the half of this that reaches a screen reader, because colour alone
   fails WCAG 1.4.1. Amber on the green bar is 4.68:1 — AA. */
.cl-header__link.is-current {
	color: var(--cl-text-inverse);
	border-bottom-color: var(--cl-amber-500);
}

/* Below the md breakpoint the bar is badge + trigger only. The two links move
   into the slide-down panel, where they already exist as full nav items — so
   this hides duplicates rather than removing access to anything.
   The welcome line goes too: 23px of uppercase erbaum plus a badge does not fit
   a phone. Decorative brand furniture, so leaving the accessibility tree with
   it is correct — nothing depends on reaching it, unlike a skip link. */
@media (max-width: 767.98px) {
	.cl-header__welcome,
	.cl-header__link {
		display: none;
	}
}

/* ---- The responsive scale ----
   Large screens first, per Michael. Three steps, and each one moves the badge
   and the bar together — unlike the old header, where they were locked by an
   identity and could not be tuned independently.

   The badge is allowed to stay large relative to the bar as the viewport
   narrows, because it is the brand and the bar is only its backdrop. What
   shrinks first is the padding. */
@media (max-width: 1199.98px) {
	.cl-site-wrapper {
		--cl-header-logo: 168px;
		--cl-header-logo-box-w: 152px;
		--cl-header-welcome-size: 1.125rem;   /* 18px */
		--cl-header-actions-gap: var(--cl-space-5);
	}
}

@media (max-width: 991.98px) {
	.cl-site-wrapper {
		--cl-header-h: 84px;
		--cl-header-logo: 148px;
		--cl-header-logo-box-w: 132px;
		--cl-header-pad-x: var(--cl-space-5);
		--cl-header-welcome-size: 1rem;       /* 16px */
	}
}

/* 🔑 THE MOBILE BADGE IS 230px — Michael, 2026-08-08. He supplied it as three
   properties on .cl-header__logo (`top: 10px; width: 270px; height: 230px`) and
   flagged that it might not be the right way to achieve it. It is not, and both
   reasons are worth writing down.

   ONE · 270 × 230 IS NOT A SIZE, IT IS A 230px BADGE PLUS DEAD SPACE. The
   artwork is `viewBox="0 0 182 182"` — square — and carries no
   preserveAspectRatio, so the default `xMidYMid meet` applies and an SVG in a
   non-square <img> box LETTERBOXES rather than stretching. In a 270×230 box the
   circle renders 230 × 230, centred, with ~20px of transparent margin each
   side. So the visible badge is 230, the 270 is padding, and nothing was
   distorted — which is why it looked right. Checked in the file, not inferred
   from the render.

   TWO · SETTING IT ON THE ELEMENT WOULD HAVE BROKEN THE OVERHANG SILENTLY, and
   this is the trap tokens.css §5c exists to prevent. `--cl-header-overhang` is
   DERIVED — `logo + logo-top - header-h` — and .cl-header reserves it as
   margin-bottom so the badge never lands on a page's <h1>. Hard-coding the size
   on .cl-header__logo leaves the token at 116px, so the header would reserve
   48px while the badge actually hung 168px: a 120px overlap onto real content,
   on all 58 non-homepage pages, at mobile only, with nothing erroring. Set as
   tokens, the reserve re-derives itself.

   ⚠️ WHAT THAT RESERVE NOW COSTS, because it is large and it is the honest
   consequence rather than a bug: 230 + 10 - 72 = **168px** of blank space below
   the bar on every mobile page (it was 48px). The curtain pays it too, as
   badge-clearing padding. If that reads as too much white space, the lever is
   the badge size — they are the same number by construction. */
@media (max-width: 767.98px) {
	.cl-site-wrapper {
		--cl-header-h: 72px;
		--cl-header-logo: 230px;
		/* ~0.9 × the badge, matching the ratio the three larger steps use. The
		   artwork overflows this slot deliberately — the box is what the flex
		   row measures, the overflow is what produces the overhang. */
		--cl-header-logo-box-w: 207px;
		--cl-header-logo-top: 10px;
		--cl-header-pad-x: var(--cl-space-4);
	}
}

/* 🐛 THE 230px BADGE COLLIDES WITH THE PANCAKE AT 320px — measured, and it is
   the one place Michael's figure does not survive. His phone is ~390px wide,
   where the badge clears the trigger by 43px; at 320 (iPhone SE, and still the
   narrowest width this project tests) the badge's right edge crosses the
   trigger's left edge by **12px** and the two overlap. Nothing overflows the
   card, so no overflow check catches it — the collision is entirely inside the
   bar.
   📌 Below 375px the badge steps back to 180px. That is a 118px reserve instead
   of 168px, so the smallest phones also carry less blank space — the two follow
   each other by construction, as §5c intends. */
@media (max-width: 374.98px) {
	.cl-site-wrapper {
		--cl-header-logo: 180px;
		--cl-header-logo-box-w: 162px;
	}
}

/* The overhang is derived, not declared per step — see tokens.css §5c. Every
   breakpoint above changes only the badge and the bar, and the reserved space
   follows on its own. That is the whole point of expressing it as a calc: the
   old header had three geometry tokens locked by an identity, and getting any
   one of them wrong put the rendered overhang 16px off the number in the file.
   📌 --cl-lead-inset rides on the same machinery as of step 5 — the steps above
   change --cl-header-pad-x and --cl-header-logo-box-w, and the page head's left
   edge follows without a number being written twice. Measured after the move,
   at the same six widths §5c uses: overhang 110/110/78/70/168/118 and inset
   260/260/232/204 — identical to the values before it. */


/* --------------------------------------------------------------------------
2c. THE SUBNAV CURTAIN — .cl-subnav  ·  WP-2.10 step 4b

design/design_handoff_pancake_subnav, option 3A ("slide-down curtain"). Tokens
in tokens.css §5c-b. Behaviour in js/subnav.js.

🔑 THIS IS A REPLACEMENT, NOT A RESTYLE, AND THE REASON IS BEHAVIOURAL RATHER
THAN VISUAL. WP-2.5's menu was a Bootstrap `.collapse`, which animates its own
HEIGHT between 0 and its scroll height. A height animation occupies layout: the
page below is pushed down as the menu opens and springs back as it closes. The
design's curtain does the opposite — it OVERLAYS the page from behind the bar
and the page does not move at all. No easing, duration or transform on a
collapse can produce that, because the difference is whether the element is in
flow. So the collapse went, and with it:

  · `.navbar` / `.navbar-collapse` / `.navbar-nav` / `.nav-link` in the header,
    and the four `--bs-navbar-*` neutralisers §2 carried for them
  · data-bs-toggle / data-bs-target on the trigger — js/subnav.js owns it now
  · the two-copy team grid ($teamsNAVUI + $teamsNAVUIlg), which put all fifteen
    team links in the DOM TWICE on every page and showed one copy per breakpoint
    with .d-none. One grid, and CSS changes its track count.

⚠️ THE THREE z-index VALUES ARE ONE MECHANISM AND MUST BE READ TOGETHER:

    bar       30    .cl-header__bar          — paints over everything
    curtain   20    .cl-subnav               — slides out from under the bar
    backdrop  10    .cl-subnav__backdrop     — dims the page below

  and the enabling condition is that NONE of their ancestors opens a stacking
  context. `.cl-header` is `position: relative` with `z-index: auto` for exactly
  that reason (see §2 above). Give the header a z-index — which looks like
  tidying — and the curtain becomes trapped inside it, forced to paint above the
  bar, and the illusion inverts: the panel slides down OVER the header instead
  of out from behind it.

🔑 THE BADGE STILL WINS THE OVERLAP, AND THE CURTAIN STILL PADS ITSELF CLEAR.
Moving from a collapse to an absolutely positioned panel does not retire step
3's finding, it only changes where the padding goes. `.cl-header`'s
`margin-bottom` reserves the badge's ~110px overhang against the PAGE BELOW; it
reserves nothing against the header's own descendants. The badge lives inside
the z-index-30 bar, so it paints over a z-index-20 sibling no matter what the
curtain does. Hence the top padding below. The cost — an open menu carrying the
overhang's worth of green above its columns — is the same cost WP-2.6 accepted,
and it matches Michael's decision that opening the menu must NOT retract the
badge.
-------------------------------------------------------------------------- */

.cl-subnav {
	position: absolute;
	left: 0;
	right: 0;

	/* Flush under the bar. `--cl-header-h` and not a literal, so the three
	   breakpoints that move the bar move the curtain with it — the design's
	   `top: 72px` is only correct at the width it was drawn at. */
	top: var(--cl-header-h);
	z-index: 20;

	background: var(--cl-subnav-bg);
	box-shadow: var(--cl-subnav-shadow);

	/* ⚠️ THE CLOSED PANEL IS NOT CLIPPED BY ITS PARENT — IT IS CLIPPED BY THE
	   CARD, and that is worth stating because the handoff says otherwise
	   ("the panel's parent must be overflow: hidden"). In the prototype the
	   parent is a 580px demo frame. Here the parent is `.cl-header`, which
	   cannot clip: the badge has to escape it downward. What actually contains
	   the off-screen panel is `.cl-site-wrapper`, already `overflow: clip` for
	   its rounded corners (tokens.css §7b) — so a -104% translate takes the
	   curtain above the card's top edge and out of sight for free. Adding
	   `overflow: hidden` to the header to satisfy the spec literally would
	   decapitate the badge. */
	transform: translateY(-104%);

	/* 🔑 `visibility`, NOT `display` OR A CLIPPED transform ALONE — this is the
	   half of the component that keyboard users depend on and nobody sees.
	   A closed curtain is off-screen but still rendered, so without this its
	   ~30 links stay in the tab order: a keyboard user tabbing off the trigger
	   would walk the entire closed menu, focus vanishing off-canvas item by
	   item, with nothing on screen to explain it. `visibility: hidden` removes
	   the subtree from the tab order and the accessibility tree while leaving
	   the transform transitionable, which `display: none` would not.

	   The delay is what makes it work in both directions: opening, visibility
	   flips immediately (0s) so the slide is visible; closing, it waits the
	   full slide duration so the panel does not disappear mid-move.
	   ⚠️ See the 🐛 in tokens.css §6 — that delay is why this component needs
	   its own reduced-motion handling. */
	visibility: hidden;
	transition:
		transform  var(--cl-subnav-dur) var(--cl-subnav-ease),
		visibility 0s linear var(--cl-subnav-dur);

	/* The curtain scrolls itself rather than growing past the fold. At 320px it
	   is roughly 900px tall — 15 links, 15 team chips and 6 social pills stacked
	   — and a panel taller than the viewport with the page scroll locked behind
	   it would simply have unreachable content at the bottom.
	   `dvh` with a `vh` fallback: on mobile Safari `100vh` is the LARGE viewport,
	   so the last row would sit behind the URL bar exactly when the list is
	   longest. */
	max-height: calc(100vh  - var(--cl-header-h));
	max-height: calc(100dvh - var(--cl-header-h));
	overflow-y: auto;

	/* Keep an inertial scroll inside the panel from chaining to the document
	   once it hits the end. The scroll lock in js/subnav.js already stops the
	   page moving; this stops the rubber-banding that makes it feel like it
	   nearly did. */
	overscroll-behavior: contain;
}

.cl-subnav.is-open {
	transform: translateY(0);
	visibility: visible;
	transition:
		transform  var(--cl-subnav-dur) var(--cl-subnav-ease),
		visibility 0s;
}

.cl-subnav__inner {
	/* Matched to the HOMEPAGE CONTENT gutter, not the header bar's — Michael,
	   2026-08-08. See the ⚠️ on --cl-subnav-pad-x: the curtain can align with
	   the page below it or with the bar above it, not both, and the page won. */
	padding-inline: var(--cl-subnav-pad-x);

	/* The badge clearance — see the 🔑 in the section head. Derived from the
	   same token the header reserves, so all three breakpoints follow. */
	padding-top: calc(var(--cl-header-overhang) + var(--cl-subnav-pad-y));
	padding-bottom: var(--cl-subnav-pad-y-end);
}

/* ---- The three-column top row ----
   The two link columns are EQUAL here, not weighted 2:1 as the collapse was.
   That split existed to stop a nine-item visitors list towering over a
   six-item members list inside a Bootstrap row; the curtain's two lists are
   eight and seven, so they balance on their own.

   ⚠️ THE DESIGN'S `1fr 1fr auto` IS RIGHT AT THE WIDTH IT WAS DRAWN AT AND
   WRONG AT OURS, and this is the second time in this package that a proportion
   has had to be re-derived rather than copied. The prototype's panel is 560px
   wide, which after gutters and the 196px team column leaves the two link
   tracks about 114px each — tight, and the lists wrap. Our card is up to
   1800px, where the identical rule gives each track ~400px to hold 150px of
   text: two thin lists stranded at opposite ends of a green field, with the
   whole layout reading as though something failed to load.

   `1fr` means "share what is left", and what is left is the thing that changed.
   So the tracks are capped at a readable measure and the SLACK goes into the
   space before the team grid, which is pinned right. At 560px this computes to
   almost exactly the design's layout; at 1800px it stays a menu instead of
   becoming a poster. */
.cl-subnav__grid {
	display: grid;
	grid-template-columns:
		minmax(0, var(--cl-subnav-col-max))
		minmax(0, var(--cl-subnav-col-max))
		1fr;
	gap: var(--cl-subnav-gap);
}

/* Pinned to the right edge, which is where the design puts it and where the
   trigger that opened the menu is. */
.cl-subnav__col--teams {
	justify-self: end;

	/* Five 32px chips plus four 11px gaps is 204px; the design says 196 and
	   relies on the chips shrinking. Sized off the tokens so the two cannot
	   drift — change the chip size and the column follows. */
	min-width: calc(5 * var(--cl-subnav-team-cell) + 4 * 11px);
}

/* ---- Group headings ----
   Real <h2>s labelling real <nav> landmarks, matching the footer's columns
   (§3). ⚠️ They therefore override tokens.css §8's h2 rules — font-size AND
   font-family, because §8 puts h2 on the display face and these are 11px
   montserrat. Both have to be answered explicitly; a heading at a non-heading
   size means every §8 property is in play. Same finding §3 records. */
.cl-subnav__title {
	margin: 0 0 var(--cl-space-3);
	font-family: var(--cl-font-body);
	font-weight: var(--cl-weight-extrabold);
	font-size: var(--cl-subnav-title-size);
	letter-spacing: 0.182em;        /* the design's 2px at 11px */
	text-transform: uppercase;
	line-height: var(--cl-leading-tight);
	color: var(--cl-subnav-title);
}

.cl-subnav__title--muted {
	color: var(--cl-subnav-title-muted);
	margin-bottom: 0;
}

/* 🐛 `li { margin-bottom: 1rem }` IS A BARE ELEMENT SELECTOR IN
   styles-custom.css (line ~118) AND IT REACHES EVERY LIST IN THIS COMPONENT.
   Michael spotted it by eye and it was most of what made the first pass look
   wrong: the link lists set `gap: .5rem`, and gap does not REPLACE margin, it
   ADDS to it — so the rendered spacing was 24px against the 8px in the file,
   and the team grid carried a phantom 16px between its rows for the same
   reason. Zeroed for the whole curtain rather than per list, because all three
   lists here own their spacing through `gap`.
   🔑 The family to watch: a global element selector plus a modern layout
   property that looks like it supersedes it. Nothing errors, and the number in
   the stylesheet is simply not the number on screen. */
.cl-subnav li {
	margin-bottom: 0;
}

/* ---- The link lists ---- */
.cl-subnav__links {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--cl-space-2);
}

.cl-subnav__links a {
	display: inline-block;
	width: fit-content;
	font-family: var(--cl-font-body);
	font-weight: var(--cl-weight-semibold);
	font-size: var(--cl-subnav-link-size);
	line-height: var(--cl-leading-snug);
	text-decoration: none;

	/* ⚠️ DECLARED, NOT INHERITED. styles-custom.css carries
	   `:is(body, p, a) { color: var(--cl-text) }` — an element selector matching
	   every <a> on the site — so a link on a dark panel inherits nothing and
	   renders near-black on the green. Identical to the trap §2's welcome line
	   and §3's footer paragraph both hit. Inheritance is not failing; a rule is
	   matching, and inheritance only fills where nothing does. */
	color: var(--cl-subnav-link);

	/* The underline is a transparent border at rest, not one that appears on
	   hover: a border toggling between none and 2px shifts the label 2px and
	   the whole column jitters as the pointer crosses it. Same technique as
	   .cl-header__link. */
	padding-bottom: 3px;
	border-bottom: 2px solid transparent;

	/* Not `transition: all` — see §1. */
	transition: color        var(--cl-duration-fast) var(--cl-ease),
	            border-color var(--cl-duration-fast) var(--cl-ease);
}

/* ⚠️ THE DESIGN HOVERS THESE TO #f15a2e AND THAT MEASURES 2.57:1 ON THIS GREEN.
   It is the handoff's only measured accessibility failure and it is not a near
   miss — 14px/600 is small text, so the bar is 4.5. The full derivation, and
   the --cl-orange-300 alternative if Michael wants the glyphs themselves
   orange, are in tokens.css §5c-b. What ships: the label goes WHITE (8.64) and
   the design's orange arrives as the underline, where its contrast is a
   non-text 3:1 question rather than a text 4.5:1 one. */
.cl-subnav__links a:hover,
.cl-subnav__links a:focus-visible {
	color: var(--cl-subnav-link-hover);
	border-bottom-color: var(--cl-subnav-link-marker);
}

/* Current page. `aria-current="page"` in the markup is the half of this that
   reaches a screen reader — colour alone fails WCAG 1.4.1. Amber, matching
   .cl-header__link.is-current, so "you are here" looks the same in the bar and
   in the menu. */
.cl-subnav__links a.is-current {
	color: var(--cl-subnav-title);
	border-bottom-color: var(--cl-subnav-title);
}

/* ---- The team chips ----
   A grid and not `columns`: fifteen fixed-size cells want a fixed track count,
   and column flow would order them down-then-across, so the alphabetical list
   would read in the wrong direction. Same reasoning as the footer's grid. */
.cl-subnav__teams {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(5, var(--cl-subnav-team-cell));
	gap: 11px;
}

/* 🐛 THE CHIP IS FILLED WHITE AND THE DESIGN LEAVES IT TRANSPARENT — this is the
   one place the curtain departs visibly from the handoff, and it is a measured
   failure rather than a preference.

   The design floats the fifteen logos directly on the green. Rendered with the
   REAL CMS artwork instead of the mockup's, eight of them are somewhere between
   faint and invisible. Measured per logo by rasterising each SVG and taking the
   contrast of its single BEST opaque pixel against --cl-green-500 — a generous
   upper bound, not an average, and even so:

       Royals 1.11 · Athletics 1.16 · Dodgers 1.21 · Rangers 1.43
       Padres 1.77 · Guardians 1.83 · Reds 2.43 · Giants 2.74

   against the 3:1 WCAG 1.4.11 asks of a meaningful non-text graphic. The
   Royals' and the Athletics' marks are dark-on-dark and simply are not there.

   🔑 THE FOOTER ALREADY FOUND THIS AND ALREADY SOLVED IT — see .cl-footer__team
   in §3, whose comment reads "dark artwork on #141414 all but disappears". Same
   fifteen files, same problem one component over, same fix. That is the
   argument for the white chip over any of the alternatives: the site is now
   consistent about how a CMS team logo is presented on a dark surface, rather
   than solving it twice two different ways.

   ⚠️ THE SHAPE IS STILL THE DESIGN'S — the handoff's rounded square, so the
   curtain reads as the drawing did in silhouette even though the fill changed.
   The departure is the fill only.
   📌 The footer used to be the counter-example here ("the footer uses a
   circle"). It does not any more: Michael took this treatment as the settled
   one on 2026-08-10 and .cl-footer__team now carries the same --cl-radius-md.
   The two components agree on shape and fill; they still differ on the artwork
   inset, and the note there says why.

   📌 The real fix is upstream and is not available to CSS: several of these
   logos are single-colour marks in the team's dark colour, and a light-on-dark
   variant per club would let the chip go transparent as designed. That is a
   CMS/asset job — TODO-CMS-2.10. */
.cl-subnav__team {
	display: flex;
	align-items: center;
	justify-content: center;
	width:  var(--cl-subnav-team-cell);
	height: var(--cl-subnav-team-cell);
	/* Design says 8px; --cl-radius-md is 9.6 and --cl-radius-sm is 4. Reaching
	   for the nearer existing step rather than declaring a third — the same
	   collapse tokens.css §4 already applied to the design's five radii. */
	border-radius: var(--cl-radius-md);
	background-color: var(--cl-white);
	transition: box-shadow var(--cl-duration-fast) var(--cl-ease),
	            transform  var(--cl-duration-fast) var(--cl-ease);
}

/* 46px of artwork in the 64px cell — a 9px inset. Both figures are tokens and
   both are deliberately separate; see the ⚠️ on --cl-subnav-team-img in
   tokens.css §5c-b for why the cell must never be sized from the image.

   ⚠️ THE OLD FIGURE IN THIS COMMENT WAS 28-IN-32, AND IT IS WORTH KNOWING WHY
   IT IS GONE TWICE OVER. That was the handoff's own size, carried verbatim; the
   chips were doubled to 64/56 on 2026-08-08 because at the drawn size the logos
   read as decoration rather than as the fifteen clubs, and the artwork came
   down again to 46 on 2026-08-10. So neither number here has been the design's
   since the day after it landed. 🔑 The proportion is now the thing being held,
   not the pixel — .cl-footer__team img carries the other half of that and the
   note there explains the relationship. */
.cl-subnav__team img {
	width:  var(--cl-subnav-team-img);
	height: var(--cl-subnav-team-img);
	object-fit: contain;
}

/* The design's hover was `background: rgba(255,255,255,.14)` — a lightening of
   a transparent chip, which says nothing once the chip is already white. The
   feedback moves to a lift and a ring instead, matching what the footer chips
   and §7c's tiles already do on hover. */
.cl-subnav__team:hover,
.cl-subnav__team:focus-visible {
	transform: translateY(-2px);
	box-shadow: 0 0 0 2px var(--cl-glass-hairline);
}

/* The team page you are on. An amber ring — the same "you are here" colour the
   bar and the link lists use, so the signal is one colour site-wide.
   `aria-current="page"` in the markup carries the same fact to a screen reader;
   colour alone fails WCAG 1.4.1. */
.cl-subnav__team.is-active {
	box-shadow: 0 0 0 2px var(--cl-amber-500);
}

/* ---- The social row ---- */
.cl-subnav__social-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--cl-space-4);
	flex-wrap: wrap;
	margin-top: var(--cl-space-5);
	padding-top: var(--cl-space-4);
	border-top: 1px solid var(--cl-glass-hairline);
}

.cl-subnav__social {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: var(--cl-space-2);
}

/* ⚠️ SIX ICONS, NOT THE DESIGN'S FIVE — LinkedIn is a live CMS record. The row
   is `flex-wrap` for that reason: the count is data, not layout, and a fixed
   track count would break the day a seventh is published.

   🗑️ THE PILL IS GONE — Michael, 2026-08-08: "remove the circle around the
   social media icons, they are good just white on green." The design drew a
   38px `rgba(255,255,255,.1)` circle behind each one.

   ⚠️ THE BOX STAYS 38px THOUGH THE CIRCLE DOES NOT, and that is the point worth
   not undoing. It is now an invisible hit target: the glyph is ~24px, so
   shrinking the link to the glyph would take the target under WCAG 2.2
   SC 2.5.8's 24px floor — a regression with nothing on screen to show for it.
   Removing a control's background does not remove its target. */
.cl-subnav__social-link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width:  var(--cl-subnav-social-size);
	height: var(--cl-subnav-social-size);
	font-size: var(--cl-subnav-social-icon);
	color: var(--cl-text-inverse);   /* declared — same <a> trap as above */
	text-decoration: none;
	transition: color var(--cl-duration-fast) var(--cl-ease);
}

/* 🐛 THE HOVER IS AMBER, NOT THE DESIGN'S ORANGE, AND THE PILL IS WHY.
   While the icon sat ON an orange fill, white-on-orange measured 3.37 and
   cleared WCAG 1.4.11's 3:1 for a non-text graphic. With the fill gone the
   orange becomes the GLYPH's own colour against the green — which is the same
   2.57:1 that disqualified it for the link labels. Same colour, same panel,
   and it passed in one arrangement and fails in the other purely because of
   what it was painted on.
   --cl-amber-500 is 4.68 here and is already the curtain's accent, so the
   hover stays warm and clearly legible. */
.cl-subnav__social-link:hover,
.cl-subnav__social-link:focus-visible {
	color: var(--cl-amber-500);
}

/* 📌 THE CMS SHIPS THE *SQUARE* FONT AWESOME VARIANTS for Facebook, Instagram,
   YouTube and LinkedIn (`fa-square-facebook` and friends, in Items.info3),
   while X and TikTok are bare glyphs. This mattered less behind a pill and
   matters MORE now that the pill is gone: four of the six read as filled tiles
   and two as line marks, sitting side by side. It is a content value, not a
   style one, so the fix is four CMS records (drop `-square`) rather than a
   CSS override — flagged for Michael. `fa-fw fa-xl` in the same field is what
   sizes them, against --cl-subnav-social-icon on the link. */

/* ---- The backdrop ----
   A <button>, so the click target is keyboard-reachable rather than a div with
   `cursor: pointer` — see the note in navigation.php. Appearance is reset
   because it is a control being used as a surface. */
.cl-subnav__backdrop {
	position: absolute;
	left: 0;
	right: 0;
	top: var(--cl-header-h);
	bottom: 0;
	z-index: 10;

	/* It is a sibling of .cl-header inside .cl-site-wrapper, so `top` is
	   measured from the CARD's top edge, not the header's. Those coincide —
	   the header is the wrapper's first child — which is the only reason a bare
	   `--cl-header-h` is correct here. ⚠️ Put anything above the header inside
	   the wrapper and this silently starts in the wrong place. */

	appearance: none;
	padding: 0;
	border: 0;
	background: var(--cl-subnav-backdrop);
	cursor: pointer;

	opacity: 0;
	visibility: hidden;
	transition: opacity    var(--cl-subnav-dur-backdrop) var(--cl-ease),
	            visibility 0s linear var(--cl-subnav-dur-backdrop);
}

.cl-subnav__backdrop.is-open {
	opacity: 1;
	visibility: visible;
	transition: opacity    var(--cl-subnav-dur-backdrop) var(--cl-ease),
	            visibility 0s;
}

/* ---- Responsive ----
   The handoff demonstrates desktop only and says to follow the site's own
   breakpoints. One step, at the same 768px the bar already uses to drop its two
   inline links into this menu — so the width at which the curtain becomes the
   ONLY route to Schedule and Stadium Map is the width at which it reflows. */
/* Michael's three rows, 2026-08-08: "row 1: Visit Plan + Members · row 2: team
   icons full width (1 line of icons, 2 if they get too small) · row 3: social".

   ⚠️ THE TWO LINK COLUMNS STAY SIDE BY SIDE ALL THE WAY DOWN — the earlier pass
   collapsed them to one column below 576px and that was wrong. Fifteen links
   stacked single-file push the team grid and the social row off the bottom of a
   phone, so the menu opens showing only half of itself. Two columns of short
   labels fit 320px comfortably and keep all three rows in view. */
@media (max-width: 991.98px) {
	.cl-subnav__grid {
		grid-template-columns: 1fr 1fr;
	}

	/* Row 2: the teams span the full width instead of being a third peer. */
	.cl-subnav__col--teams {
		grid-column: 1 / -1;
		justify-self: stretch;
		min-width: 0;
	}

	/* 🔑 FLUID CELLS, NOT FIXED ONES — this is what makes "1 line, or 2 if they
	   get too small" a rule rather than a guess. The track count is declared and
	   the CELL SIZE follows from the width available, so the chips shrink to fit
	   the line before the line ever breaks. A fixed 64px cell with `auto-fit`
	   would do the opposite: it would keep the size and break the row at an
	   arbitrary count that changes with every device width.
	   15 across at this step gives ~37px a chip at 768px. */
	.cl-subnav__teams {
		grid-template-columns: repeat(15, 1fr);
		gap: var(--cl-space-2);
	}
	.cl-subnav__team {
		width: 100%;
		height: auto;
		aspect-ratio: 1;
	}
	.cl-subnav__team img {
		width: 87.5%;      /* the design's 56-in-64 ratio, held as the cell flexes */
		height: 87.5%;
	}
}

/* 🐛 TWO LINES OF EIGHT BELOW **768px**, NOT BELOW 576px — the first pass put
   this boundary at 576 and it landed the chips at exactly **23px** there, one
   pixel under WCAG 2.2 SC 2.5.8's 24px target floor. Found by measuring the
   rendered chip at every breakpoint rather than by checking the ones that
   looked risky; 576 was the width that felt safe because it is where the layout
   was already changing.

   🔑 The boundary belongs where the SIZE stops working, not where the previous
   breakpoint happened to sit. Fifteen across needs ~690px of viewport before a
   chip reaches a recognisable ~32px, so one line starts at 768 and everything
   below it gets two rows: 53px a chip at 576, 30px at 414, 27px at 320 — all
   clear of the floor, all recognisable as a club badge.

   📌 8 and not 5: eight is the fewest columns that still fits fifteen logos in
   TWO rows, and two rows is the most a phone menu can spend here without
   pushing the social row under the fold. */
@media (max-width: 767.98px) {
	.cl-subnav__teams {
		grid-template-columns: repeat(8, 1fr);
	}
}

@media (max-width: 575.98px) {
	/* The label above six icons reads as a caption on a phone, not as a row. */
	.cl-subnav__social-row {
		flex-direction: column;
		align-items: flex-start;
		gap: var(--cl-space-3);
	}
}


/* --------------------------------------------------------------------------
2d. THE MENU TRIGGER — .cl-burger  ·  WP-2.6

Replaces "MAIN MENU" + Bootstrap's .navbar-toggler-icon. The words are gone at
Michael's call — a three-line trigger is universally understood and the label
was the single most cluttered thing in the bar. The words are NOT gone from the
accessibility tree: the button keeps aria-label="Main menu", so a screen reader
still hears exactly what it used to, and Bootstrap keeps aria-expanded honest.

THE MOTION. Three equal bars at rest (Michael's call — an earlier pass had them
at full/68%/84% and he wanted them even). Opening, the outer two converge and
cross into a symmetrical X while the middle one scales out of existence, and the
whole glyph rotates a half turn as it goes.

  · The half-turn is what makes it feel like one gesture instead of two bars
    pivoting. It also means open and close are not the same animation played
    backwards — it keeps rotating the same direction, so the thing never looks
    like it is rewinding.
  · The two phases are staggered, and the order REVERSES between states, which
    is the detail that sells it: opening, the bars fold first and the rotation
    carries them home (bar delay 0, box delay 90ms); closing, the box unwinds
    first and the bars spring back into stripes underneath it (box delay 0,
    bar delay 110ms). Both orderings fall out of putting the delays on the
    state selectors rather than on the base rule.
  · The ease overshoots slightly (the 1.35 on the outgoing control point). On a
    ~380ms move that is a hint of springiness, not a bounce.

EVERYTHING ANIMATES ON `transform` ALONE — no width, no height, no
background-position. The middle bar's exit is scaleX(0), not a width change, so
the glyph resolves into the X without ever touching layout. That is why this
stays smooth on a scrolling sticky header, which was the non-negotiable for the
whole round.

State comes from Bootstrap's own aria-expanded on the button, not from a class
we maintain: one source of truth, and it cannot drift out of sync with the
panel. -------------------------------------------------------------------- */

/* Sized down 10% at Michael's request (was 52px / 26 / 8). --burger-bar stays
   at 2px rather than following to 1.8: hairlines that land off the pixel grid
   render soft, and the bars are the one part of this whose crispness is the
   whole point. */
.cl-burger {
	--burger-w:     23px;   /* bar length at scaleX(1) */
	--burger-bar:   2px;    /* bar thickness — deliberately NOT scaled, see above */
	--burger-gap:   7px;    /* centre-to-centre; also the fold distance */
	--burger-dur:   380ms;
	--burger-ease:  cubic-bezier(0.68, -0.35, 0.27, 1.35);

	display: inline-flex;
	align-items: center;
	justify-content: center;

	/* 2027 CHROME — WP-2.10 step 3. The design's rounded square with a glass
	   fill, replacing WP-2.6's transparent circle that only appeared on hover.
	   The MOTION below is untouched: Michael's instruction was that the
	   hamburger keeps doing exactly what it does today, so only the container
	   changed. 46px is the design's figure and it still clears WCAG 2.2
	   SC 2.5.8's 24px floor and the 44px comfort target.

	   ⚠️ The fill is visible AT REST now, which is the point — the old one was
	   invisible until hover, so on a photograph-free flat bar there was nothing
	   to indicate a control until you found it with a pointer. A keyboard user
	   got the focus ring and a touch user got nothing at all. */
	width: 46px;
	height: 46px;
	padding: 0;
	border-radius: var(--cl-radius-md);
	background-color: var(--cl-glass-fill-strong);
	border: 1px solid var(--cl-glass-hairline);
	transition: background-color var(--cl-duration-base) var(--cl-ease);
}
.cl-burger:hover { background-color: rgb(255 255 255 / 20%); }

/* The rotating group. Height is two gaps so the three bars sit at 0 / gap /
   2*gap with the box's centre exactly on the middle bar — the X is only
   symmetrical if the rotation centre and the fold target are the same point. */
.cl-burger__box {
	position: relative;
	display: block;
	width: var(--burger-w);
	height: calc(var(--burger-gap) * 2 + var(--burger-bar));
	transition: transform var(--burger-dur) var(--burger-ease) 0ms;
}

.cl-burger__bar {
	position: absolute;
	left: 0;
	width: 100%;
	height: var(--burger-bar);
	border-radius: var(--burger-bar);
	background-color: #fff;
	transform-origin: 50% 50%;
	transition:
		transform var(--burger-dur) var(--burger-ease) 110ms,
		opacity   calc(var(--burger-dur) / 2) linear 110ms;
}
/* All three the same length. The hover rule that used to extend the short ones
   went with them — there is nothing left for it to reveal. */
.cl-burger__bar:nth-child(1) { top: 0;                           }
.cl-burger__bar:nth-child(2) { top: var(--burger-gap);           }
.cl-burger__bar:nth-child(3) { top: calc(var(--burger-gap) * 2); }

/* Open: fold into the X, spin the group. Delays swap — see the header note. */
.cl-burger[aria-expanded="true"] .cl-burger__box {
	transform: rotate(180deg);
	transition-delay: 90ms;
}
.cl-burger[aria-expanded="true"] .cl-burger__bar { transition-delay: 0ms; }
.cl-burger[aria-expanded="true"] .cl-burger__bar:nth-child(1) {
	transform: translateY(var(--burger-gap)) rotate(45deg);
}
.cl-burger[aria-expanded="true"] .cl-burger__bar:nth-child(2) {
	transform: scaleX(0);
	opacity: 0;
}
.cl-burger[aria-expanded="true"] .cl-burger__bar:nth-child(3) {
	transform: translateY(calc(var(--burger-gap) * -1)) rotate(-45deg);
}


/* ==========================================================================
3. FOOTER — WP-2.6
==========================================================================

Four bands, top to bottom:

    .cl-footer__signup    newsletter — the only thing here asking for anything
    .cl-footer__main      brand + social + the 15 team links
    .cl-footer__stadiums  the existing stadium logo strip, unchanged in spirit
    .cl-footer__legal     copyright · cookie settings · credit

Tokens live in tokens.css §5d. That block is where the colors and their
measured contrast ratios are — this file is layout.

WHY THE NEWSLETTER IS A DISCLOSURE AND NOT A FORM AT REST. The brief asks for
signup that is easy but "doesn't need to be too loud". Constant Contact's
widget injects its own markup and styling, so a form rendered at rest would put
a third-party control we cannot fully restyle on all 43 pages, permanently, in
a band that is supposed to be quiet. A button that reveals it costs one click,
keeps the resting state on-brand — which is the state visible ~99% of the time
— and, because js/footer.js only injects the Constant Contact script on that
first open, means no third-party request happens on any page view where nobody
intends to sign up. The design constraint and the privacy/performance
constraint happen to want the same thing here.

⚠️ THE REVEAL IS A BOOTSTRAP .collapse, SO DO NOT PUT PADDING ON
   .cl-footer__newsletter. WP-2.5 lost most of a session to this in the header:
   Bootstrap animates a collapse element's `height` between 0 and its scroll
   height, and on `box-sizing: border-box` padding SURVIVES `height: 0`. The
   panel then sits permanently open by its own padding and the close animation
   ends at the wrong height. Spacing goes on the inner wrapper.

   📌 THIS IS NOW THE ONLY BOOTSTRAP COLLAPSE LEFT IN THE FRONT END. The rule
   used to point at .cl-header__panel-inner as its twin; step 4b replaced the
   header's collapse with the subnav curtain (§2c), so the header no longer
   demonstrates the trap and this newsletter reveal is the last place it can
   bite. The reasoning is spelled out above rather than delegated for that
   reason — there is nothing left to cross-reference.
-------------------------------------------------------------------------- */

.cl-footer {
	background-color: var(--cl-footer-bg);
	color: var(--cl-footer-text);
}

/* 🐛 EVERY <p> IN THE FOOTER RENDERS NEAR-BLACK WITHOUT THIS, AND THE CAUSE IS
   NOT OBVIOUS FROM THE FOOTER'S OWN CSS. styles-custom.css carries
   `:is(body, p, a) { color: var(--cl-text) }` — a DIRECT declaration on every
   paragraph on the site. Inheritance only fills in where no rule matches, so
   .cl-footer's light `color` reaches the container and every child EXCEPT the
   ones that rule names. The signup paragraph therefore came out #232323 on
   #141414: measured 1.06:1, invisible.

   Scoped to `p` generally rather than to the one class that hit it, because
   the next paragraph anyone adds down here would land in the same hole with
   nothing to suggest why. `inherit` rather than a token so it keeps tracking
   whatever surface it is sitting on.
   📌 That last point used to have a concrete reason — the stadium logo strip
   was a WHITE band inside the dark footer, so a token would have been wrong
   there. That band is gone (WP-2.10 step 3) and the whole footer is dark now,
   so `inherit` is currently equivalent to naming the token. Kept anyway: it
   costs nothing and it is still the right answer the moment a light band
   reappears down here. */
.cl-footer p { color: inherit; }

/* One link treatment for the whole footer. The old rules named each region
   separately (.footer-text a, .policy-text a, .footer-nav a) and then gave all
   three the same declaration, including a hover that changed nothing. */
.cl-footer a {
	color: var(--cl-footer-link);
	text-decoration: none;
	transition: color var(--cl-duration-fast) var(--cl-ease);
}
/* NO UNDERLINE ON HOVER (Michael, 2026-07-30) — colour carries the state.
   ⚠️ That is safe HERE and would not be everywhere: WCAG 2.2 SC 1.4.1 only
   forbids colour as the ONLY means of conveying information, and these links
   are not embedded in prose — they are discrete items in lists and a legal
   bar, so their linkness comes from position, not from a cue inside a
   sentence. Do not copy this rule onto body copy, where an underline is the
   only thing separating a link from the words around it. */
.cl-footer a:hover,
.cl-footer a:focus-visible {
	color: var(--cl-footer-link-hover);
}


/* ---- 3a. SIGNUP ----
   Sits in the right column of the main band's second row (it used to be its
   own full-width band above everything). No background of its own any more:
   it is part of the black section now, so a raised surface here would cut the
   row in half exactly where the layout wants it to read as one. */

/* Button first, then the copy, both packed left (Michael, 2026-07-30 — was
   copy-left / button-pushed-right by space-between). The button leads in the
   MARKUP as well as visually, so DOM order and reading order agree and there
   is no CSS `order` making the two disagree. The block keeps
   aria-labelledby="cl-newsletter-title", so a screen reader still announces
   what the button is for even though the heading now follows it. */
.cl-footer__signup-row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: flex-start;
	gap: var(--cl-footer-gap);
}

/* Caps the sentence at a readable measure without a magic pixel width.
   flex-grow is 0 now: with the row packed left there is nothing to push
   against, so letting the copy grow would just stretch it back out. */
.cl-footer__signup-copy { flex: 0 1 22rem; max-width: 46ch; }

/* Stops the button squashing when the copy wants the space. */
.cl-footer__signup-action { flex: 0 0 auto; }

.cl-footer__signup-title {
	font-family: var(--cl-font-display);
	font-size: var(--cl-text-h4);
	font-weight: var(--cl-weight-bold);
	line-height: var(--cl-leading-tight);
	color: var(--cl-white);
	margin: 0 0 var(--cl-space-2);
}

.cl-footer__signup-text {
	font-size: var(--cl-text-ui);
	line-height: var(--cl-leading-body);
	margin: 0;
}

/* .cl-footer__newsletter is the collapse element and deliberately carries NO
   rule of its own — see the ⚠️ in the block comment above. All of its spacing
   is here, on the inner wrapper, where the height animation can measure it. */
.cl-footer__newsletter-inner { padding-top: var(--cl-space-6); }

/* Constant Contact injects into .ctct-inline-form. What we own outright is the
   box it sits in: a light card, because the CC form is built for light
   backgrounds and its labels would otherwise render dark-on-dark. */
.cl-footer__newsletter .ctct-inline-form {
	background-color: var(--cl-surface);
	border-radius: var(--cl-radius-md);
	padding: var(--cl-space-5);
	color: var(--cl-text);
}
.cl-footer__newsletter .ctct-inline-form a { color: var(--cl-link); }


/* ---- 3a-i. THE CONSTANT CONTACT FORM, BROUGHT ONTO BRAND ----
   Michael, 2026-08-10: "Can we style that hideous CC form in the footer at
   least a little bit to match our brand?"

   ⚠️ THIS REVERSES A POSITION THIS FILE HELD UNTIL NOW. The comment above used
   to continue "we do not restyle its internals — that is a fight against markup
   we do not control and it breaks silently whenever they ship a change." That
   caution was right about the risk and wrong about the size of it, and what
   makes it safe is the FAILURE MODE, not confidence that CC will hold still:
   every declaration below is a colour, a font or a radius on an element CC has
   already placed. Nothing here hides, moves, sizes or positions anything. So if
   CC renames a class tomorrow the matching rule simply stops applying and that
   piece reverts to CC's own styling inside our white card — visually a step
   backwards, never a broken form. ⚠️ KEEP IT THAT WAY. The moment a rule here
   starts doing layout (display, position, float, width on their containers) the
   failure mode changes from "looks like CC again" to "unusable", and that is
   the version of this the old comment was rightly afraid of.

   🔑 SPECIFICITY IS THE WHOLE TRICK, AND IT IS NOT THE STYLESHEET YOU EXPECT.
   CC's widget stylesheet is not what has to be beaten. At runtime the widget
   ALSO injects a <style> block built from the form's design settings in the CC
   dashboard, and its selectors read

       .ctct-form-embed.form_0 .ctct-form-custom .ctct-form-button

   — specificity (0,4,0), injected into the document after ours. So a natural
   `.cl-footer__newsletter .ctct-form-custom .ctct-form-button` (0,3,0) loses,
   and a four-class version ties and STILL loses on source order. The prefix
   below is deliberately five classes so it wins outright on specificity and
   does not depend on load order. That is why it is written the long way and why
   there is no !important anywhere in it: nothing here needs one.
   ⚠️ `form_0` is a per-instance suffix — it is form_1 if a second CC form ever
   renders on a page. Never target it; the prefix below does not.

   📌 THE DURABLE FIX IS UPSTREAM AND IS MICHAEL'S TO MAKE. Those injected rules
   come from the form's design panel in the Constant Contact dashboard — button
   colour, fonts and field styling are all settings there. Anything set there
   arrives correct without CSS and survives their markup changes. This block is
   the version we can ship without waiting on it; if the dashboard is ever
   brought on-brand, most of what follows becomes redundant and can go. */

.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-custom .ctct-form-label,
.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-custom .ctct-form-element,
.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-custom .ctct-form-button {
	/* CC ships Helvetica Neue/Arial. One line puts the form on the site's face. */
	font-family: var(--cl-font-body);
}

/* Labels. CC's are 16px/700 #323232 — heavier and larger than any label on the
   rest of the site. Down to the interface size the footer already uses. */
.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-custom .ctct-form-label {
	font-size:   var(--cl-text-ui);
	font-weight: var(--cl-weight-semibold);
	color:       var(--cl-text);
}

/* Fields. CC's 3px radius becomes the site's --cl-radius-sm (4px).

   ⚠️ THE BORDER IS --cl-border-STRONG, AND THE OBVIOUS TOKEN WAS THE WRONG ONE.
   --cl-border (#d9d9d9) is what "a light field boundary" reaches for, and
   measured against the white card it is 1.41:1 — while CC's own stock #b0b6bb
   is 2.29:1. Taking the obvious token would have made the field boundary
   VISIBLY FAINTER than the vendor default we were replacing, in a change whose
   whole purpose was to improve this form. --cl-border-strong (#a4aab1) is
   2.29:1: the site's own token, and the exact contrast CC already held.

   📌 NEITHER FIGURE REACHES THE 3:1 SC 1.4.11 ASKS OF A CONTROL BOUNDARY, and
   that is a site-wide question rather than this block's to answer. The site's
   own inputs are worse: nothing overrides `.form-control` border-color at rest,
   so they inherit Bootstrap's #dee2e6 at ~1.28:1 on white. Fixing it here alone
   would leave the third-party form more accessible than every first-party form
   on the site and hide the real finding. Logged for the step 7 gate; the fix is
   one border token in tokens.css §3, applied to both at once. */
.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-custom .ctct-form-element {
	border-radius: var(--cl-radius-sm);
	border-color:  var(--cl-border-strong);   /* 2.29 — see above */
	color:         var(--cl-text);
	font-size:     var(--cl-text-ui);
}

/* ⚠️ THE FOCUS RING IS THE ONE RULE HERE THAT IS NOT COSMETIC. tokens.css §8
   gives every focusable element --cl-focus-ring at (0,1,0) via :focus-visible —
   which CC's own (0,4,0) field rules would otherwise be free to paint over.
   WP-2.7 exists because this site had removed its focus indicators in three
   places; a third-party form quietly doing it again in the footer is the same
   defect arriving through a different door. Declared at the same five-class
   weight as everything else so it cannot be. */
.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-custom .ctct-form-element:focus,
.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-custom .ctct-form-element:focus-visible {
	outline:      none;
	box-shadow:   var(--cl-focus-ring);

	/* Green on focus, matching `.form-control:focus` in tokens.css §8 — so a
	   focused CC field and a focused site field resolve to the same thing.
	   (This is why the rest state above could not also be --cl-border-strong's
	   neighbour: the focus state has to be distinguishable from it.) */
	border-color: var(--cl-border-brand);
}

/* The submit. CC's default is its own corporate blue (#0078c1) at 16px/400 with
   a 3px radius — the single most off-brand thing in the footer, and the reason
   Michael called the form hideous.

   🔑 IT TAKES THE ACCENT VARIANT, NOT THE PRIMARY, AND §1d SAYS WHY: the accent
   was written for "tickets, buy, join, subscribe — the actions Phase 3's
   schedule page and WP-2.6's newsletter signup need". This IS that button; it
   simply could not use the class because CC renders the element.
   ⚠️ --cl-orange-700, NOT the brand orange-500. §1d again: orange-500 gives a
   white label 3.36:1 and fails AA for button text. 700 is 5.67:1.
   📌 Shape and typography are copied from §1a rather than inherited, because
   .btn is not on this element and cannot be put there. If the button scale in
   tokens.css §6 ever moves, this is a place that has to be moved with it —
   it is reading the same tokens, so in practice it follows on its own. */
.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-custom .ctct-form-button {
	background-color: var(--cl-orange-700);
	border-color:     var(--cl-orange-700);
	color:            var(--cl-text-inverse);
	border-radius:    var(--cl-btn-radius);
	font-size:        var(--cl-btn-font-size);
	font-weight:      var(--cl-weight-semibold);
	letter-spacing:   var(--cl-tracking-wide);
	text-transform:   uppercase;
	transition:       background-color var(--cl-duration-fast) var(--cl-ease),
	                  border-color     var(--cl-duration-fast) var(--cl-ease);
}

.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-custom .ctct-form-button:hover {
	background-color: var(--cl-orange-800);   /* 7.50 */
	border-color:     var(--cl-orange-800);
	color:            var(--cl-text-inverse);
}

.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-custom .ctct-form-button:active {
	background-color: var(--cl-orange-900);   /* 10.43 */
	border-color:     var(--cl-orange-900);
	color:            var(--cl-text-inverse);
}

/* The GDPR/permission line and CC's own footer link. 11px is below anything
   else on the site; --cl-text-sm is 13px and --cl-text-secondary is 4.71:1 on
   white. Left as small print, made readable small print.
   ⚠️ NOT touching .ctct-form-errorMessage or .ctct-form-error. An error needs to
   look like an error, CC's red already does, and re-colouring validation state
   is the one thing in here that could actually cost a subscriber. */
.cl-footer__newsletter .ctct-inline-form .ctct-form-embed .ctct-form-defaults .ctct-gdpr-text {
	font-family: var(--cl-font-body);
	font-size:   var(--cl-text-sm);
	color:       var(--cl-text-secondary);
}


/* ---- 3b. MAIN BAND — WP-2.10 step 3 ----

   Four columns: brand + blurb + social | Visit / Plan | Members | team logos.
   Replaces WP-2.6's 2x2 (badge | team list / social | newsletter).

   The social icons moved INTO column one rather than keeping a row of their own:
   they are part of the brand block and the design has no row for them.
   📌 That retires the reason --cl-footer-logo-w and --cl-footer-social-size were
   locked to the same value. WP-2.6 tied them together so the badge and the icon
   row read as one column; the icons now sit under a 136px badge in a column of
   their own, so the constraint is gone — and with it the accessibility trade it
   forced, where six 44px targets could not fit 220px and shrank to 34px. They
   are back to a comfortable size. See tokens.css §5d. */

.cl-footer__main {
	padding-block: var(--cl-footer-pad-y) 0;
}

/* Explicit fractions, from the design. Not `repeat(4, 1fr)`: the columns hold
   very different content — a badge, two link lists of different lengths, and a
   5-wide logo grid — and equal tracks left the team grid wrapping to 4 across
   while column two had dead space. */
/* 🔑 THE FOOTER'S REAL PLACEMENT BUG WAS THE INSET, NOT THE COLUMNS. The grid
   ratios below already matched the design exactly, yet the footer looked
   misaligned against the page — because Bootstrap's `.container` inside these
   sections insets the content only ~12px from the card edge, while every
   content band above it is inset --cl-block-pad-x (48px). The columns were the
   right shape in the wrong place, which is why comparing ratios found nothing.

   Neutralising `.container` inside the footer puts the brand column, the link
   columns and the legal row on the same left edge as the hero copy, the
   section headings and the countdown bar. The design's own figure is a uniform
   56px; 48 is this system's step and is already logged in the deferred-polish
   table with the other band paddings — matching the page matters more here than
   matching the mockup by 8px. */
.cl-footer .container {
	max-width:      none;
	padding-inline: var(--cl-block-pad-x);
}

.cl-footer__grid {
	display: grid;
	grid-template-columns: 1.15fr 1fr 1.1fr 1.2fr;
	gap: var(--cl-space-6);
	padding-bottom: var(--cl-space-7);
	border-bottom: 1px solid var(--cl-footer-rule);
}

@media (max-width: 1199.98px) {
	.cl-footer__grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 575.98px) {
	.cl-footer__grid { grid-template-columns: 1fr; }
}

/* ---- Column 1: brand ---- */

/* The badge is a link Home as of 2026-08-10 (Michael) — see footer.php for why
   its <img> alt went empty when the anchor went round it.

   inline-block, NOT the header brand's `display: block`: the margin-bottom that
   spaces the badge off the blurb lives on the IMG below, and an inline <a> would
   not contain it — the anchor's own box would collapse to the line box and the
   focus ring would draw around a sliver instead of around the badge. Wrapping an
   existing block child is exactly the case where the wrapper has to establish a
   box of its own. Width stays intrinsic so the anchor is the badge and not the
   full column: a 240px-wide invisible click target above the blurb is the usual
   way this goes wrong. */
.cl-footer__brand-link {
	display: inline-block;
}

/* Shaped to the artwork. The global :focus-visible in tokens.css §8 applies
   --cl-radius-sm, which would draw a nearly-square ring around a round badge —
   correct in contrast terms and visibly wrong. 50% matches the mark. */
.cl-footer__brand-link:focus-visible {
	border-radius: 50%;
}

/* The round badge. 1:1 artwork, so the width token also fixes its height. */
.cl-footer__brand-logo {
	width: var(--cl-footer-logo-w);
	max-width: 100%;
	height: auto;
	display: block;
	margin-bottom: var(--cl-space-4);
}

.cl-footer__blurb {
	max-width: 240px;
	margin: 0 0 var(--cl-space-5);
	font-size: var(--cl-text-ui);
	line-height: var(--cl-leading-body);

	/* ⚠️ DECLARED, NOT INHERITED, and this is the WP-2.6 bug that must not come
	   back. styles-custom.css carries `:is(body, p, a) { color: var(--cl-text) }`
	   — an element selector matching every <p> on the site — so .cl-footer's
	   light colour reaches the container and every child EXCEPT the paragraphs,
	   which render near-black on near-black. Measured then: 1.06:1.
	   .cl-footer p { color: inherit } (§3 head) is the general fix; this is
	   belt-and-braces on the one paragraph that would be worst to lose. */
	color: var(--cl-footer-text);
}

/* ---- Columns 2 and 3: link lists ---- */

.cl-footer__col-title {
	margin: 0 0 var(--cl-space-4);
	font-family: var(--cl-font-body);
	font-size: 0.75rem;                       /* 12px */
	font-weight: var(--cl-weight-extrabold);
	letter-spacing: 0.167em;                  /* = the design's 2px at 12px */
	text-transform: uppercase;
	color: var(--cl-footer-link);             /* white, 18.42:1 */

	/* ⚠️ A REAL <h2>, SIZED DOWN — not a <div> that looks like a heading.
	   These label two navigation groups that the <nav> elements point at with
	   aria-labelledby, so they have to be real headings for that reference to
	   mean anything. The design uses a plain <div>, which would leave both
	   footer navs unnamed and indistinguishable to a screen reader.

	   ⚠️ It therefore overrides tokens.css §8's h2 rules — font-size AND
	   font-family, because §8 puts h2 on the display face. Both are declared
	   here deliberately: WP-2.7's hardest-won lesson is that changing a
	   heading's LEVEL silently restyles it, and the inverse holds too — using a
	   heading at a non-heading size means every §8 property has to be answered
	   explicitly or the element renders at 39px erbaum. */
}

/* Michael, 2026-08-07: "These are footer links. They don't need to be so loud."
   Weight to normal and the row gap right down — the design's own 11px gap was
   tuned for a heavier weight, and at 400 the list reads as a block rather than
   as separate rows if it stays that open. */
.cl-footer__links {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 0.1rem;
	font-size: var(--cl-text-ui);
	font-weight: var(--cl-weight-normal);
}

/* ⚠️ These are NOT --cl-footer-link (white). The ⚠️ on that token says it must
   stay white, but read why: it was pinned for the WP-2.6 TEAM LIST — 15 links
   in small type at close spacing, where anything dimmer stopped reading as a
   link. That list is now a logo grid, so the constraint no longer applies here,
   and the design's dimmer treatment is available. --cl-footer-text measures
   7.86:1 on the footer black; hover goes to the brighter orange at 5.48:1.
   ⚠️ Both are footer-scoped for a reason: that orange is 3.25:1 on white and
   would FAIL there. Do not unify it with --cl-link-hover. */
.cl-footer__links a {
	color: var(--cl-footer-text);
	text-decoration: none;
	width: fit-content;
	transition: color var(--cl-duration-fast) var(--cl-ease);
}
.cl-footer__links a:hover,
.cl-footer__links a:focus-visible {
	color: var(--cl-footer-link-hover);
}

/* Current page (Michael, 2026-08-10) — the state the bar and the curtain
   already carried, brought down to the footer columns so all three navigations
   answer "where am I" the same way.

   AMBER, and it is the third place the site says "you are here" in this one
   colour: .cl-header__link.is-current, .cl-subnav__links a.is-current, and now
   this. --cl-amber-500 measures 9.99:1 on the footer black — the figure
   .cl-footer__team.is-active already relies on for its ring.

   ⚠️ IT NEEDS THE WEIGHT AS WELL AS THE COLOUR, and this list is the reason.
   The siblings here sit at --cl-footer-text, a dim gray, and Michael's WP-2.10
   step 3 note deliberately set them at weight 400 with a 0.1rem gap so they
   read as a block. Inside a block that tight, a hue change alone is a weak
   signal. The markup carries aria-current="page", so nothing is colour-ONLY in
   the WCAG 1.4.1 sense either way — the weight is a legibility call, not a
   compliance one. */
.cl-footer__links a.is-current {
	color: var(--cl-amber-500);
	font-weight: var(--cl-weight-semibold);
}

/* ---- Social ---- */

/* ⚠️ A 3x2 GRID, NOT A WRAPPING ROW, AND THE FIRST ATTEMPT WAS WRONG.
   Releasing the badge/icon width coupling (see tokens.css §5d) let the icons go
   back to 44px — but the constraint MOVED rather than disappeared, which the
   first pass missed and the rendered page showed immediately: this column is
   about 240px wide and six 44px targets need 264px plus gaps, so they wrapped
   5 + 1 and the lone TikTok icon read as a mistake.

   An explicit 3x2 grid is deliberate at any column width, keeps the 44px
   comfort target, and cannot produce an orphan. `repeat(3, var(--size))` rather
   than `repeat(3, 1fr)` so the icons stay square and packed left instead of
   stretching to fill the column. */
/* 🔑 ONE LINE, AND FLEX RATHER THAN A FIXED GRID. WP-2.6 laid these out 3x2
   because they were locked to the footer badge's width in a ~240px column; that
   constraint left with them when they moved into the signup band, which is half
   the footer wide. `repeat(6, …)` would have worked at desktop and forced a
   horizontal overflow on a phone — flex-wrap keeps the single row wherever it
   fits and folds gracefully where it does not. */
.cl-footer__social {
	display:   flex;
	flex-wrap: wrap;
	gap:       var(--cl-space-2);
	justify-content: start;
}

.cl-footer__social a {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--cl-footer-social-size);
	height: var(--cl-footer-social-size);
	color: var(--cl-footer-link);
	text-decoration: none;
	transition: color var(--cl-duration-fast) var(--cl-ease);
}
.cl-footer__social a:hover,
.cl-footer__social a:focus-visible {
	color: var(--cl-footer-link-hover);
}

/* ---- Column 4: the team logo chips ----

   Michael, 2026-08-07: the design's circular logo chips replace WP-2.6's text
   list of 15 team names.

   🔑 EVERY CHIP IS NAMED BY ITS LOGO'S alt, WHICH THE DESIGN LEAVES EMPTY.
   Shipping it as designed would be 15 links with no accessible name — one of
   the exact defects WP-2.7 had to fix ("icon-only social links with no
   accessible name"). The image is the link's only content, so its alt IS the
   link's name; footer.php puts the full team name there.

   A grid, not `columns`: 15 fixed-size circles want a fixed track count, and
   column flow would order them down-then-across, so the alphabetical list would
   read in the wrong direction. */
.cl-footer__teams {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(5, 1fr);
	gap: 9px;
	justify-items: start;
}

@media (max-width: 1199.98px) {
	/* Two columns wide at this step, so the grid has room for more per row. */
	.cl-footer__teams { grid-template-columns: repeat(8, 1fr); }
}
@media (max-width: 767.98px) {
	.cl-footer__teams { grid-template-columns: repeat(5, 1fr); }
}

/* 🐛 THE 52px CHIP IS WHAT OVERFLOWED THE FOOTER AT 320px, AND IT DID IT
   THROUGH ITS GRANDPARENT. Five 52px chips plus four gaps have a min-content
   width of ~296px. A grid item's default `min-width: auto` refuses to shrink
   below min-content, so the single-column footer track was forced to 296px
   inside a 248px grid — and every sibling in that track (the brand block, the
   social row) was stretched to 296px with it and reported as the overflow.

   🔑 So the three elements the probe named were all symptoms, and none of them
   was too wide on its own. The same shape as the hero clock's `min-width: 300px`
   earlier in this step: when several siblings overflow by an identical amount,
   the cause is the track they share, not any of them.

   ⚠️ It was already sitting at exactly the page edge before step 4's footer
   inset — right edge 320 against a clientWidth of 320 — so an overflow check
   using `> clientWidth` rather than `>=` reported it clean. The inset moved it
   12px over and made it visible. */
@media (max-width: 575.98px) {
	.cl-footer__teams { --cl-footer-team-chip: 40px; }
}

.cl-footer__team {
	display: flex;
	align-items: center;
	justify-content: center;
	width: var(--cl-footer-team-chip);
	height: var(--cl-footer-team-chip);

	/* 🎨 A ROUNDED SQUARE, NOT A CIRCLE (Michael, 2026-08-10): "the visual
	   treatment of the team icons in the subnav is perfectly dialed in — do the
	   same for the footer." Same token as .cl-subnav__team, so the two places
	   the site presents a CMS team logo on a dark surface now agree on the
	   shape as well as on the white fill. See the note on the img inset below
	   for what this change retired. */
	border-radius: var(--cl-radius-md);

	/* White, because these are the same dark CMS logos the retired stadium strip
	   needed a light band for — see the note that used to be §3c. Dark artwork on
	   #141414 all but disappears, and the chip is what gives each one a surface
	   to sit on. 18.42:1 against the footer black, so the chip's own edge is
	   unmistakable without a border. */
	background: var(--cl-white);

	/* The chip is the hit target: 52px clears WCAG 2.2 SC 2.5.8's 24px floor and
	   also the 44px comfort figure, which the WP-2.6 social icons had to give up.
	   Nothing forces a trade here. */
	transition: transform var(--cl-duration-fast) var(--cl-ease),
	            box-shadow var(--cl-duration-fast) var(--cl-ease);
}

/* 📌 THE INSET IS A SIZE CHOICE, NOT A SHAPE CONSTRAINT, AND IT ONLY BECAME ONE
   WHEN THE CHIP STOPPED BEING A CIRCLE (both changes 2026-08-10, Michael).

   It used to be 20px and that figure was forced: `object-fit: contain` fits
   artwork to its BOX, and a circle's box is a square inscribed in it, so a mark
   filling its own square corner to corner (the Rangers' T, the Braves' A)
   crossed the circle's edge. 20px bought the diagonal clearance the worst
   artwork in the CMS needed. A rounded square has almost no diagonal to lose,
   so that pressure is gone and the number became free to choose. 14px is the
   choice: 38px of artwork in the 52px chip.

   🔑 THE FOOTER AND THE SUBNAV NOW AGREE ON PROPORTION AND STILL DIFFER ON
   PIXELS, WHICH IS THE CORRECT RELATIONSHIP BETWEEN THEM. 38/52 here and 46/64
   there are .73 and .72 — one treatment at two sizes, arrived at from opposite
   directions in the same pass (this grew, the curtain's shrank). ⚠️ Do not
   "unify" them into a shared token. The chips are different sizes on purpose —
   52px is a footer target, 64px is a menu item — so a shared inset would break
   the proportion that is the thing actually being matched. If one moves, move
   the other to keep the RATIO, not the pixel. */
.cl-footer__team img {
	width: calc(var(--cl-footer-team-chip) - 14px);
	height: calc(var(--cl-footer-team-chip) - 14px);
	object-fit: contain;
}

.cl-footer__team:hover {
	transform: translateY(-2px);
	box-shadow: 0 4px 12px rgb(0 0 0 / 45%);
}

/* You-are-here. A ring OUTSIDE the white chip, not inside it: amber on the
   footer black is 9.99:1, while the same amber on the white chip would be
   1.84:1 and invisible. The markup also carries aria-current="page", so the
   state is not colour-only (WCAG 1.4.1). */
.cl-footer__team.is-active {
	box-shadow: 0 0 0 3px var(--cl-amber-500);
}

/* ⚠️ `transition: none` for the same two reasons as .cl-card--tile in §7c — a
   focus ring must not fade in, and a transitioned box-shadow built from var()
   does not pick up a change to that var in Chrome. Listed after :hover so it
   wins on source order at equal specificity. */
.cl-footer__team:focus-visible {
	transition: none;
	box-shadow: var(--cl-focus-ring);
}

/* ---- The newsletter band ----
   Full width under the grid rather than a quarter-width column: it is the one
   thing down here that asks the visitor for something, and a form does not fit
   a footer column. */
.cl-footer__signup-band {
	padding-block: var(--cl-space-6);
}

/* 🎨 Two columns, top-aligned: social left, newsletter right (Michael,
   2026-08-07). The second column is also what finally constrains the Constant
   Contact form — its markup is injected by CC and cannot be fully restyled, so
   the only real control over it is the width of the box it renders into. It was
   spanning the whole footer; now it is half. */
.cl-footer__band-grid {
	display:     grid;
	gap:         var(--cl-block-gap);
	align-items: start;
}

@media (min-width: 768px) {
	.cl-footer__band-grid { grid-template-columns: 1fr 1fr; }
}

/* 🗑️ §3c, THE STADIUM LOGO STRIP, IS GONE — Michael, 2026-08-06. The 2027
   homepage carries a proper stadium section, so ten ballpark marks in the footer
   were the same content twice.

   🔑 WORTH KEEPING FROM ITS ~30 LINES OF REASONING, because the fact outlives
   the band: THE CMS STADIUM AND TEAM LOGOS ARE DARK ARTWORK DRAWN FOR A WHITE
   BACKGROUND. WP-2.6's first pass put the whole footer on --cl-footer-bg and all
   ten all but vanished — dark marks on #141414 — which is why the original
   footer was a dark/light/dark sandwich. That is exactly why the team chips
   above carry a white background rather than sitting bare on the dark surface.
   ⚠️ If these ever need to sit ON the dark surface, the fix is new artwork from
   the clubs, NOT `filter: invert()` — several carry a second colour that comes
   out wrong. */


/* ---- 3d. LEGAL BAR ---- */

.cl-footer__legal {
	background-color: var(--cl-footer-bg-sunken);
	padding-block: var(--cl-space-4);
	font-size: 0.75rem;              /* 12px — the design's figure, up from 10.24 */
	letter-spacing: 0.03rem;

	/* ⚠️ THE DESIGN'S OWN COLOUR HERE FAILS AA AND WAS NOT PORTED. It sets
	   #6f747a (= --cl-gray-500) on this bar, which measures **4.12:1** against
	   --cl-footer-bg-sunken — short of the 4.5:1 that 12px text needs. Not a
	   close call and not a judgement: it is a WCAG 1.4.3 failure, so porting it
	   verbatim would have put one back into a package whose whole predecessor
	   (WP-2.7) existed to remove them.

	   --cl-gray-450 is the fix and it keeps the design's INTENT rather than
	   overriding it. The intent is a dimmer tier than the footer's links
	   (--cl-footer-text, 8.30:1 here), and -450 is genuinely dimmer at
	   **7.35:1** while clearing AA with room to spare. Simply using
	   --cl-footer-text would have passed too, but it would have flattened the
	   hierarchy the design asked for.

	   📌 It is also worth noting the failure only exists because the design
	   RAISED this text to 12px. At the 10.24px it replaces, the old colour would
	   have needed even more contrast, not less — small text does not get a
	   discount. */
	color: var(--cl-gray-450);
}

/* 🗑️ .cl-footer--has-countdown REMOVED — WP-2.10 step 3.

   It reserved `--cl-space-4 + --cl-countdown-h` of padding under this bar,
   because the clock was position:fixed at the bottom of the viewport and at the
   end of the page it landed on top of the copyright and the Anamorphics credit.
   The height was measured by js/footer.js rather than hardcoded, since the bar
   gains a title row under 992px and is materially taller on a phone; and the
   whole thing hung off a modifier class so the off-season case reserved exactly
   zero rather than leaving 64px of dead space for ten months of the year.

   The bar is now an ordinary block after this one (Michael, 2026-08-07), so it
   occupies real space and reserves its own. Nothing to pad, nothing to measure,
   and no off-season special case — an absent include simply renders nothing.

   🔑 FOUR MOVING PARTS DIED WITH ONE POSITIONING CHANGE: this rule, the
   --cl-countdown-h token, a ResizeObserver, and two PHP-side class toggles
   (here and on the consent banner). Worth remembering before reaching for
   `position: fixed` again. */

.cl-footer__legal-row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--cl-space-2) var(--cl-space-4);
}

/* Inline separators between the legal links. Generated rather than authored so
   the list can gain a Privacy Policy / Terms entry without anyone having to
   remember to add or remove a middot — see the note in footer.php about those
   pages not existing yet. aria-hidden because a screen reader announcing
   "middot" between every link is noise. */
.cl-footer__legal-links {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--cl-space-2);
	margin: 0;
	padding: 0;
	list-style: none;
}
.cl-footer__legal-links li + li::before {
	content: "\00b7";
	margin-right: var(--cl-space-2);
	color: var(--cl-footer-text);
}

@media (max-width: 767.98px) {
	.cl-footer__legal-row    { justify-content: center; text-align: center; }
	.cl-footer__legal-links  { justify-content: center; }
}


/* ==========================================================================
4. COUNTDOWN — WP-2.8
==========================================================================

Two variants of one component:

    .cl-countdown--bar      fixed to the bottom of the viewport, every page,
                            included by footer.php
    .cl-countdown--inline   a block to drop into page content, any number of
                            them, on a dark card by default

Tokens live in tokens.css §5e — read the ⚠️ there before changing a colour.
The target date is php/anamorphics-cms-inits.php; the ticking is
js/countdown.js; the markup is asset-countdown-clock*.php.

WHY THIS IS HERE AND NOT IN THE INCLUDES. Both variants used to carry their
own <style> block, and the two blocks defined the SAME selectors with different
values — `.countdown-div` was a 150px bordered circle in one and an unstyled
box in the other. That was survivable only because the two were never on one
page. Placing them together, which is the point of WP-2.8, would have meant the
second include silently restyling the first. Two variants of one component
belong in one place, keyed by a modifier.

WHAT IS DELIBERATELY NOT SHARED: the digits' font-size, weight and letter-
spacing. The bar is 400 weight at 1.15em (tuned in WP-2.6 against the system
mono stack) and the inline circles are bold at 2em. They look like one family
because they are the same face and the same accent, not because they are the
same rule.
========================================================================== */

/* ---- Shared ------------------------------------------------------------ */

/* The mono stack is --cl-font-mono for the reason set out in tokens.css §3:
   real weights and guaranteed tabular figures. Tabular is what stops the
   whole row jittering sideways once a second as the digits change width. */
.cl-countdown__container {
	font-family: var(--cl-font-mono);
	font-style: normal;
	display: flex;
	justify-content: center;
	align-items: center;
	text-align: center;
}

.cl-countdown__display {
	display: flex;
	justify-content: center;
	align-items: center;
	text-align: center;
	min-width: 300px;
}


/* ---- Variant: the footer bar -------------------------------------------- */

/* ⚠️ RENAMED IN THE COMMENTS ONLY — the class is still .cl-countdown--bar, and
   deliberately: renaming it would touch two include files and the inline
   variant's sibling rules for no behavioural gain. But it is no longer THE
   FIXED BOTTOM BAR. WP-2.10 step 3 removed `.fixed-bottom` from the markup
   (Michael, 2026-08-07) and it is now the last ordinary block inside the card.

   ⚠️ ITS HEIGHT IS STILL NOT SET HERE, for a different reason than before.
   It used to be unset because js/footer.js measured this element into
   --cl-countdown-h and a declared height would have been wrong on one of the
   two breakpoints — the bar gains a mobile title row under 992px. That
   measurement is gone. The height stays unset now simply because the content
   should size it, which is what a static block does for free. Declaring one
   would clip the mobile title row.

   The white top border stays: it separates the bar from the footer's legal
   band (--cl-footer-bg-sunken #0d0d0d) directly above it, and pure black
   against near-black would otherwise read as a rendering artefact rather than
   as a separate object. Same reasoning as --cl-countdown-bar-bg being #000
   rather than --cl-black. */
/* 🎨 REBUILT to the design 2026-08-07 (WP-2.10 step 4). Was a single centred
   row of five cells with the title dressed as a digit; the design is a title
   pinned LEFT and the four units pinned RIGHT.
   ⚠️ The white top border is gone with it — the design has none, and against
   the footer's #0d0d0d legal bar directly above, a 1px white rule read as a
   seam rather than as a separator. The colour change alone does that job. */
.cl-countdown--bar {
	background-color: var(--cl-countdown-bar-bg);
	padding-block: var(--cl-space-4);
}

/* Title left, units right, at the SAME inset as every content band above —
   which is what makes the bar read as part of the page rather than as a strip
   bolted under it. */
.cl-countdown--bar .cl-countdown__container {
	display:         flex;
	flex-wrap:       wrap;
	align-items:     center;
	justify-content: space-between;
	gap:             var(--cl-space-3) var(--cl-space-5);
	padding-inline:  var(--cl-block-pad-x);
	text-align:      initial;   /* the base rule centres; this row does not */
}

.cl-countdown--bar .cl-countdown__display {
	display:     flex;
	align-items: baseline;
	gap:         var(--cl-space-5);
}

/* Each unit is value + label on one baseline, the design's arrangement. The old
   bar stacked nothing and simply ran them together with margins. */
.cl-countdown--bar .cl-countdown__unit {
	display:     inline-flex;
	align-items: baseline;
	/* ⚠️ --cl-space-1 (4px) renders as "196DAYS" — the design's 6px is the gap
	   between a figure and its unit, and at 4px against a 22px digit the two
	   read as one token. 8px is the scale's nearest step and is legible. */
	gap:         var(--cl-space-2);
}

@media (max-width: 767.98px) {
	/* Stacked and centred rather than space-between, which at this width leaves
	   the title and the units at opposite ends of a very wide gap. */
	.cl-countdown--bar .cl-countdown__container { justify-content: center; }
	.cl-countdown--bar .cl-countdown__display   { gap: var(--cl-space-4); }
}

/* WEIGHT AND SIZE SET IN WP-2.6 (Michael: "the orange bold font is too heavy,
   reduce the whole thing in size a little bit").

   ⚠️ THE WEIGHT IS 400, NOT AN INTERMEDIATE VALUE, AND THAT IS DELIBERATE.
   This is the system mono stack (--cl-font-mono), chosen in WP-2.3 precisely
   because it ships REAL weights — the previous Google Roboto Mono was loaded
   at 300 while this rule asked for bold, so the old clock rendered a
   synthesized fake-bold. Menlo / SF Mono / Consolas ship Regular and Bold and
   nothing between, so asking for 500 or 600 gets rounded anyway (CSS Fonts L4
   checks below 500 first for a desired 500) and invites a future reader to
   "fix" a value that never rendered. 400 is a real face and says what it means.

   Sizes came down about 20% in the same pass: value 1.4 → 1.15em, title
   1 → 0.85em, label 0.8em, mobile title 1.4 → 1.05rem. Letter-spacing dropped
   with them — 2px was tuned against the old bold and reads as gappy at 400. */
/* ⚠️ THE WEIGHT WAS 400 AND IS NOW 600, AND THE REASON THE OLD NOTE GAVE FOR
   400 NO LONGER APPLIES. It said an intermediate weight gets rounded anyway
   because the system mono stack ships Regular and Bold and nothing between —
   true, and the design asks for 600, which rounds UP to the Bold that stack
   really has. So this is a real face, not a synthesized one, and it is the
   design's value. (The original 400 was solving a different problem: WP-2.3's
   Google Roboto Mono was loaded at 300 and being faux-bolded.) */
.cl-countdown--bar .cl-countdown__value {
	font-size:   1.375rem;   /* 22px */
	font-weight: 600;
	color:       var(--cl-countdown-bar-accent);   /* 11.3 on the bar's black */

	/* Tabular figures so the row does not shuffle sideways once a second. */
	font-variant-numeric: tabular-nums;
}

/* ⚠️ NOT amber. The design gives the labels #a4aab1 against the amber digits,
   which is what stops the row reading as eight equally loud numbers — the
   figure is the information and the unit is the annotation. --cl-gray-400
   measures 9.06:1 on the bar's black, comfortably AA at 10px. */
.cl-countdown--bar .cl-countdown__label {
	font-size:      0.625rem;   /* 10px */
	font-weight:    var(--cl-weight-bold);
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color:          var(--cl-gray-400);
}

/* The title. erbaum rather than the mono stack — it is a label, not a readout,
   and the design sets it in the display face. */
.cl-countdown--bar .cl-countdown__title {
	margin:         0;
	font-family:    var(--cl-font-display);
	font-size:      1rem;       /* 16px */
	font-weight:    var(--cl-weight-bold);
	letter-spacing: 0.0625em;   /* = the design's 1px at 16px */
	text-transform: uppercase;
	color:          var(--cl-countdown-bar-title);   /* 21:1 on black */
}

/* 🗑️ .cl-countdown__mobile-title AND ITS WHITE STRIP ARE RETIRED (step 4).
   The strip existed only because the desktop title was `d-none d-lg-block`, so
   something else had to carry the label on a phone. The rebuilt bar shows the
   title at every width, which made the strip a second copy of the same words —
   and a white band under a black bar is not in the design at any size. */


/* ---- Variant: inline --------------------------------------------------- */

/* A dark card by default, and that is a contrast decision rather than a
   stylistic one — see the ⚠️ in tokens.css §5e. The circles are drawn in
   --cl-countdown-accent, which is legible on --cl-countdown-bg and is not
   legible on white; shipping the light treatment as the default would mean
   the easiest way to place this component is also the broken way. */
.cl-countdown--inline {
	background-color: var(--cl-countdown-bg);
	border-radius: var(--cl-radius-md);
	padding: var(--cl-space-5) var(--cl-space-3);
}

.cl-countdown--inline .cl-countdown__display {
	padding: 4px 24px;
}

.cl-countdown--inline .cl-countdown__unit {
	width: 150px;
	height: 150px;
	border-radius: 50%;
	border: 1px solid var(--cl-countdown-accent);
	color: var(--cl-countdown-accent);
	font-weight: bold;
	letter-spacing: 2px;
	aspect-ratio: 1 / 1;
}
.cl-countdown--inline .cl-countdown__value {
	top: 8px;
	position: relative;
	font-size: 2em;
}
.cl-countdown--inline .cl-countdown__label {
	color: var(--cl-countdown-label);
	letter-spacing: 0.02em;
	text-transform: uppercase;
}

/* Below 768px the circles are dropped rather than shrunk. Four 150px circles
   need 600px plus gaps; scaling them down far enough to fit a 320px viewport
   puts a four-figure day count inside a ~70px circle, where it either wraps or
   overflows the border it is supposed to sit inside. */
@media (max-width: 767.98px) {
	.cl-countdown--inline .cl-countdown__unit {
		width: auto;
		height: auto;
		border-radius: 0;
		border: none;
		aspect-ratio: auto;
	}
}

/* For a clock on a white or otherwise light band. Reassigns the two tokens the
   rules above are written against, so nothing here restates a property — the
   variant and its surface stay one rule set with two colour schemes.
   --cl-orange-700 is 5.67:1 on white: AA for the label text and past the 3:1
   SC 1.4.11 needs for the circle borders. */
.cl-countdown--inline.cl-countdown--on-light {
	--cl-countdown-accent: var(--cl-orange-700);
	--cl-countdown-label:  var(--cl-text);

	background-color: transparent;
	padding: 0;
}


/* ==========================================================================
5. SKIP LINK + MAIN LANDMARK — WP-2.7
==========================================================================

WCAG 2.2 SC 2.4.1 Bypass Blocks (Level A). The header holds roughly thirty
links plus fifteen team links, and it is repeated on all 43 pages, so without
a bypass a keyboard or switch user tabs the entire menu again before reaching
any content — on every page, every time.

⚠️ THE THREE OBVIOUS WAYS TO HIDE THIS LINK ALL BREAK IT. `display: none`,
`visibility: hidden` and the `hidden` attribute each remove the element from
the tab order, so the link can never receive focus and can never appear. It
would sit in the markup looking correct and do nothing — which is the exact
class of silent failure this package exists to find. It is moved off-screen
with `transform` instead, which leaves it focusable.

NOT `clip`/`clip-path` either, though those are also focusable-safe: this link
has to ANIMATE back in, and a transform is the one property that can do that on
the compositor. The reduced-motion rule in tokens.css §6 already zeroes the
duration for anyone who asks.
========================================================================== */

.cl-skip-link {
	position: fixed;
	top: var(--cl-space-3);
	left: var(--cl-space-3);

	/* Above everything: the sticky header, and the consent banner at 1080. */
	z-index: 1100;

	padding: var(--cl-space-3) var(--cl-space-4);
	border-radius: var(--cl-radius-md);

	background-color: var(--cl-surface-brand);
	color: var(--cl-text-inverse);
	font-family: var(--cl-font-body);
	font-size: var(--cl-text-base);
	font-weight: 600;
	text-decoration: none;
	box-shadow: var(--cl-shadow-3);

	/* Off the top of the viewport until focused. -200% rather than -100% so the
	   shadow and focus ring clear the edge too. */
	transform: translateY(-200%);
	transition: transform var(--cl-duration-base) var(--cl-ease-out);
}

.cl-skip-link:focus,
.cl-skip-link:focus-visible {
	transform: translateY(0);
}

/* The landmark itself carries tabindex="-1" so the link moves focus rather than
   only scrolling. No focus style is needed or wanted here: tokens.css §8 styles
   :focus-visible only, and a container focused programmatically does not match
   it — so this gets no ring without anything having to suppress one. */


/* ==========================================================================
6. BOOTSTRAP CONTRAST REPAIRS — WP-2.7
==========================================================================

Framework defaults that fail AA in the specific pairing this site puts them in.
Kept together and kept small: each rule names the measured ratio it replaces,
so it is obvious when one stops being needed after a Bootstrap upgrade.
========================================================================== */

/* Bootstrap's default code colour is #d63384, which is 4.86:1 on white — fine —
   but only 4.06:1 on .alert-warning's #fff3cd, and it fails there. The pairing
   occurs in the local-only DRAFT notice at the top of privacy-policy.php.

   Set through Bootstrap's own --bs-code-color rather than by declaring `color`,
   per rule 2 at the top of this file: any component that reads the variable
   keeps working. Scoped to .alert so code on white keeps the familiar pink.

   #a51e60 measures 6.42:1 on #fff3cd and 7.11:1 on white, so it is safe on
   every alert variant, not only the warning one. */
.alert {
	--bs-code-color: #a51e60;
}


/* ==========================================================================
7. THE 2027 SET — WP-2.10
==========================================================================

Derived out of design/2027-look, whose every style is inline — there was no
stylesheet to lift. Values are in tokens.css §5g.

⚠️ STEP 2 WRITES CSS ONLY. Nothing here is referenced by any page yet; the
   markup lands in steps 3–5. That is deliberate, so steps 1–2 revert cheaply.

🔑 EVERY BUTTON IN THE DESIGN IS A `<div style="cursor:pointer">`. Not focusable,
   no role, not operable by keyboard — so porting the markup literally would
   quietly undo part of WP-2.7, on the most prominent controls on the site.
   The variants below are therefore written to work on a real `<a>` or `<button>`
   and are NOT given `cursor: pointer` as a substitute for being a control.
   Every call site in steps 3–5 must use a genuine interactive element.

📌 SIZE: the design's large buttons are 16px/32px padding at 15px type, which is
   within a couple of px of the existing .btn-lg (12px/32px at 16px), and its
   small card CTAs are 9px/16px at 12px against .btn-sm's 5.6px/16px at 12.8px.
   So the WP-2.4 size scale already carries these and no global button token was
   retuned — retuning one would change every button on 59 pages to chase a few
   px. If Michael wants the design's exact proportions, the two one-line changes
   are --cl-btn-padding-y-lg 0.75rem → 1rem and --cl-btn-padding-y-sm
   0.35rem → 0.5rem.
========================================================================== */


/* ---- 7a. BUTTON VARIANTS ----

   Three new fills, composing with WP-2.4's §1 shape and size scale:

       .btn-cl-amber   solid amber, INK label     the 2027 primary CTA
       .btn-cl-light   solid white, green label   for a brand-green panel
       .btn-cl-ghost   translucent + hairline     over a photograph ONLY

   plus one modifier:

       .btn-cl-sentence   drops the uppercase + wide tracking

   ⚠️ THE AMBER LABEL IS INK, NOT WHITE, AND IT IS NOT A STYLE CHOICE. White on
      --cl-amber-500 measures 1.84:1 — it fails AA, AA-large and SC 1.4.11 all
      three. --cl-ink on the same fill is 8.52:1, and 10.24:1 on the -400 hover.
      This is the same constraint §5e records for the countdown: the brand amber
      is legible against dark, and nothing light survives on top of it. */

.btn-cl-amber {
	--bs-btn-color:               var(--cl-ink);          /*  8.52 on the fill */
	--bs-btn-bg:                  var(--cl-amber-500);
	--bs-btn-border-color:        transparent;
	--bs-btn-hover-color:         var(--cl-ink);          /* 10.24 */
	--bs-btn-hover-bg:            var(--cl-amber-400);
	--bs-btn-hover-border-color:  transparent;
	--bs-btn-active-color:        var(--cl-ink);          /*  6.39 */
	--bs-btn-active-bg:           var(--cl-amber-600);
	--bs-btn-active-border-color: transparent;
}

/* Solid white on the brand-green band ("See our economic impact"). The fill is
   8.64:1 against the green behind it, so the control's own boundary clears
   SC 1.4.11's 3:1 without needing a border at all — which is why it has none.
   Label: green-500 on white 8.64:1, on the sand-100 hover 7.96:1. */
.btn-cl-light {
	--bs-btn-color:               var(--cl-primary);
	--bs-btn-bg:                  var(--cl-white);
	--bs-btn-border-color:        transparent;
	--bs-btn-hover-color:         var(--cl-primary);
	--bs-btn-hover-bg:            var(--cl-sand-100);
	--bs-btn-hover-border-color:  transparent;
	--bs-btn-active-color:        var(--cl-primary-active);
	--bs-btn-active-bg:           var(--cl-sand-200);
	--bs-btn-active-border-color: transparent;
}

/* ⚠️ GHOST REQUIRES A SCRIM OF AT LEAST 0.70 BENEATH IT. Read the derivation in
   tokens.css §5g (--cl-ghost-scrim-min) before placing one. Its fill is white at
   10%, which contributes nothing to contrast, so the white label's legibility is
   borrowed entirely from whatever is underneath. Over the worst case a photo can
   present the label needs a >= 0.65 and the hairline needs a >= 0.70.

   The design puts exactly one of these in the hero, low down, where its gradient
   is at 0.9 and the label measures 10.37:1. Move the same button to the middle of
   that hero — where the gradient is 0.55 — and it renders 3.41:1 and fails, with
   nothing to indicate it. Do not use this variant on an unscrimmed image.

   The 1.5px border is the design's, kept: at 1px a 60%-white hairline over a
   photo is easy to lose, and this is the only thing giving the control an edge. */
.btn-cl-ghost {
	--bs-btn-color:               var(--cl-white);
	--bs-btn-bg:                  rgb(255 255 255 / 10%);
	--bs-btn-border-color:        rgb(255 255 255 / 60%);
	--bs-btn-border-width:        1.5px;
	--bs-btn-hover-color:         var(--cl-white);
	--bs-btn-hover-bg:            rgb(255 255 255 / 20%);
	--bs-btn-hover-border-color:  var(--cl-white);
	--bs-btn-active-color:        var(--cl-ink);
	--bs-btn-active-bg:           var(--cl-white);
	--bs-btn-active-border-color: var(--cl-white);
}

/* The 2027 voice for a large CTA is sentence case with a trailing arrow —
   "View the 2027 schedule →" — while §1's .btn forces uppercase and 0.1em
   tracking on every button the site has. This modifier opts out.

   Kept as a modifier rather than changed in .btn on purpose: flipping the
   default would restyle every button on 59 pages, and whether the sentence-case
   voice becomes the site-wide default is a step-4 question, not a step-2 one.
   The design's own small card CTAs ("READ UP →") stay uppercase, so both
   treatments are live in the same design and neither is simply "the new way". */
.btn-cl-sentence {
	text-transform: none;
	letter-spacing: 0.033em;   /* = the design's 0.5px at 15px */
	font-weight:    var(--cl-weight-extrabold);
}


/* ---- 7b. SECTION KICKERS ----

   The small uppercase line above a section heading. Two sizes; values and the
   note about the third (demo-widget) size are in tokens.css §5g.

   A kicker is NOT a heading — it is a label for the heading beneath it, and
   marking one up as h3/h4 is exactly the "sub-titles are not headings" pattern
   WP-2.7 had to unpick across events, listings, dugout posts and five store
   pages. Use a <p> or <div>; the heading is the next element. */

.cl-kicker {
	font-family:    var(--cl-font-body);
	font-size:      var(--cl-kicker-size);
	font-weight:    var(--cl-weight-extrabold);
	letter-spacing: var(--cl-kicker-tracking);
	text-transform: uppercase;
	line-height:    var(--cl-leading-snug);
	margin-bottom:  var(--cl-space-2);

	/* No colour. A kicker appears on green, on black and over a photo scrim in
	   the design, with a different value on each — so a default here would be
	   wrong on two surfaces out of three and would look deliberate. The call
	   site sets it from §2b's on-dark set, or --cl-text-brand on a light one. */
}

.cl-kicker--sm {
	font-size:      var(--cl-kicker-size-sm);
	letter-spacing: var(--cl-kicker-tracking-sm);
}

/* ⚠️ Tracking adds space AFTER the last letter too, so a centred kicker sits
   visibly left of centre — at 0.23em on 13px that is ~3px of phantom padding.
   Only correct it when the kicker is actually centred, hence the modifier
   rather than a blanket negative margin. */
.cl-kicker--center {
	text-align:   center;
	margin-inline-end: calc(-1 * var(--cl-kicker-tracking));
}


/* ---- 7c. SURFACES ----

   Two families, and which one to use is decided by what is UNDERNEATH, not by
   how the panel should look:

       .cl-card    opaque white + hairline    on the white page
       .cl-glass   translucent + hairline     on a dark panel or photograph

   Mixing them up is the failure this split prevents: a .cl-card on a photo hides
   the image it was meant to sit on, and a .cl-glass on the white page renders an
   invisible 7%-white fill with a 1.05:1 edge. */

.cl-card {
	background:    var(--cl-surface);
	border:        1px solid var(--cl-card-hairline);
	border-radius: var(--cl-radius-lg);
	padding:       var(--cl-card-pad);

	/* Not `transition: all` — see §1. The design animates transform and shadow
	   on the team tiles; enumerating them keeps a later border or colour change
	   from joining in silently. */
	transition:
		transform  var(--cl-duration-fast) var(--cl-ease),
		box-shadow var(--cl-duration-fast) var(--cl-ease);
}

/* Raised — the Opening Week card. A real shadow, so the 1.47:1 hairline is no
   longer the only thing separating it from the page. `overflow: clip` rather
   than `hidden` for the same reason as the site wrapper: it contains children
   inside the radius without creating a scroll container. */
.cl-card--raised {
	box-shadow: var(--cl-card-lift-raised);
	overflow:   clip;
}

/* Interactive tiles (the 15 team logos). The lift is the affordance, so it is
   attached to :focus-visible as well as :hover — a keyboard user gets the same
   feedback as a mouse user, and tokens.css §8's ring composes with it because
   this rule sets no box-shadow of its own on that state.
   ⚠️ transform on :hover only, never on :focus-visible alone: moving an element
   under a keyboard user's focus ring is disorienting. */
.cl-card--tile:hover {
	/* The design's own values, restored 2026-08-07 on Michael's note that the
	   rollover should be more pronounced. It sets translateY(-4px) with
	   `0 14px 30px rgba(20,20,20,.12)`; the -2px and --cl-card-lift used before
	   were a toned-down reading of it and read as almost nothing on a white
	   tile against a sand band. */
	transform:  translateY(-4px);
	box-shadow: var(--cl-card-lift-hover);
}
/* ⚠️ `transition: none` HERE IS LOAD-BEARING, TWICE OVER. Found by measuring the
   rendered value rather than reading the rule: immediately after focus this
   element computed three transparent zero-size shadow layers, because .cl-card
   transitions box-shadow and Chrome expands `none` into matching empty layers as
   the animation's start frame. The ring was correct 400ms later — but:

   ① A FOCUS INDICATOR MUST NOT FADE IN. Tabbing quickly through the 15 team
      tiles would leave the ring perpetually mid-animation and never fully drawn.
   ② A TRANSITIONED PROPERTY WHOSE VALUE COMES FROM var() DOES NOT PICK UP A
      CHANGE TO THAT var IN CHROME. This is the WP-2.6 header bug exactly
      (--cl-header-brand-h stayed pinned at 96px against a 60px token,
      indefinitely). --cl-focus-ring is redefined under prefers-reduced-motion
      and is a candidate for retuning, so a transitioned ring built from it could
      silently keep painting a stale value.

   Hover keeps its animation — it is decoration, and nothing depends on its
   value being current. */
.cl-card--tile:focus-visible {
	transition: none;
	box-shadow: var(--cl-card-lift-hover), var(--cl-focus-ring);
}

.cl-glass {
	background:    var(--cl-glass-fill);
	border:        1px solid var(--cl-glass-hairline);
	border-radius: var(--cl-radius-lg);
	padding:       var(--cl-card-pad);
}

.cl-glass--strong { background: var(--cl-glass-fill-strong); }

/* Small inline chips — the countdown unit boxes. --cl-radius-md rather than lg:
   16px on a 40px-tall box reads as a pill that failed to commit. */
.cl-glass--chip {
	border-radius: var(--cl-radius-md);
	padding:       var(--cl-space-3) var(--cl-space-4);
}


/* ---- 7d. STAT TILES ----

   The three economic-impact figures. Composes with .cl-glass rather than
   redeclaring a surface: `<div class="cl-glass cl-stat">`.

   ⚠️ THE FIGURE MUST STAY LARGE — the size is carrying the contrast. Amber on
   the .07 glass fill over --cl-green-500 measures 3.87:1, which passes only
   under WCAG's large-text allowance (>=24px, or >=18.66px bold). At body size
   the same pairing fails AA. If a smaller stat tile is ever needed, the figure
   has to change colour, not just shrink — --cl-on-brand is 6.09:1 there.

   📌 The numbers themselves ($710M+ / 1.7M+ / 10) are placeholders invented by
   the design tool. Michael approved shipping them as placeholders — C-08 in the
   reskin doc's CMS backlog. */

.cl-stat__figure {
	display:     block;
	font-family: var(--cl-font-display);
	font-weight: var(--cl-weight-bold);   /* erbaum has no 800 — see §3 */
	font-size:   clamp(2rem, 1.5rem + 2vw, 2.875rem);   /* 32 → 46px */
	line-height: 1;
	color:       var(--cl-amber-500);     /* 3.87 on the glass fill — large only */

	/* Tabular figures so a row of three stats aligns and does not reflow if a
	   number is swapped for the real one. Same reasoning as the countdown. */
	font-variant-numeric: tabular-nums;
}

.cl-stat__label {
	display:     block;
	margin-top:  var(--cl-space-2);
	font-size:   var(--cl-text-ui);
	font-weight: var(--cl-weight-semibold);
	color:       var(--cl-on-brand);      /* 6.09 on the glass fill */
	line-height: var(--cl-leading-snug);
}


/* ==========================================================================
8. THE 2027 HOMEPAGE BLOCKS — WP-2.10 step 4
==========================================================================

The six content bands of design/2027-look, between the step-3 header and the
step-3 footer. Tokens are in tokens.css §5h; the surfaces, buttons, kickers and
stat tiles they compose with are §7.

    8a. shared band scaffolding
    8b. hero               — slideshow, headline, clock, two CTAs
    8c. schedule spotlight — copy + the Opening Week card
    8d. team grid          — 15 tiles, live from CMS itemID 22
    8e. ballparks          — dark band, 10 rows, live from CMS itemID 18
    8f. explore            — 4 photo tiles
    8g. economic impact    — grass band, 3 stat tiles

🔑 EVERY CONTROL HERE IS A REAL <a> OR <button>. The design draws all of them as
   `<div style="cursor:pointer">` — not focusable, no role, no keyboard
   operation — so porting the markup literally would have undone WP-2.7 on the
   most prominent controls on the site. §7a's variants are written for a genuine
   control and deliberately withhold `cursor: pointer`.

⚠️ TWO SCRIMS IN THIS FILE ARE LOAD-BEARING AND DERIVED, NOT CHOSEN — the hero's
   (8b) and the economic-impact band's (8g). Both solves, including the measured
   proof that two of the four hero photographs contain pure white directly under
   the copy, are in tokens.css §5h. Do not retune either by eye.
========================================================================== */


/* ---- 8a. SHARED BAND SCAFFOLDING ----

   One padding step for all five content bands (see the 📌 in §5h about the
   design's five near-identical values), and one split-band grid, because four
   of the six are the same object: a copy column beside a content column. */

.cl-block {
	padding: var(--cl-block-pad-y) var(--cl-block-pad-x);
}

/* ---- The badge overlap, and why it is keyed off the PAGE and not the hero ----

   .cl-header reserves the overhanging badge's height as margin-bottom so that
   on the other 58 pages — which open with ordinary content, not a photograph —
   the badge never lands on an <h1>. The homepage is the one page that WANTS the
   overlap, because the design has the badge sitting on the hero photo.

   ⚠️ THE OBVIOUS FIX — A NEGATIVE TOP MARGIN ON .cl-hero — CANNOT WORK, AND THE
   REASON IS THE STEP-3 CUSTOM-PROPERTY TRAP SEEN FROM THE OTHER SIDE.
   --cl-header-overhang is DECLARED ON .cl-header (it has to be: declared at
   :root its calc resolves against :root's inputs and measures 110px at every
   breakpoint). Custom properties inherit DOWN, and .cl-hero is inside <main>, a
   SIBLING of <header> — so the value is not visible there at all. Re-deriving
   the calc on the hero would mean duplicating three tokens that the header
   overrides at two breakpoints, i.e. four numbers kept in sync by hand.

   Cancelling the reserve at its source has neither problem: one declaration, no
   duplicated arithmetic, and it stays correct at every breakpoint on its own.
   The badge already carries z-index 40 precisely so its overflow paints over
   whatever the page puts below the header, so nothing else is needed.

   📌 The hook is a class on <body>, which each page file writes itself — the
   only element above both the header and <main> that a single page controls.
   .cl-site-wrapper would have been the tidier ancestor and is opened inside the
   shared navigation.php, which no page can vary. */
.cl-page-home .cl-header { margin-bottom: 0; }

/* The two-column bands. Ratios differ per band and are set by the modifier;
   the collapse point does not, so it lives here.

   ⚠️ 992px, NOT a `minmax()` auto-fit. These columns are not interchangeable —
   one is prose and one is a data table or a logo grid — so they must break in a
   defined order rather than wherever the content happens to reflow. */
.cl-block__split {
	display: grid;
	gap:     var(--cl-block-gap);
	align-items: center;
}

/* 🔑 `min-width: 0` ON THE CHILDREN, BECAUSE A GRID ITEM DEFAULTS TO
   `min-width: auto` AND WILL NOT SHRINK BELOW ITS OWN min-content. Without it
   the Opening Week card's min-content (~276px) forced the single column wider
   than the 248px available at 320px — and the symptom appeared on the LEDE, a
   different element in the same track, which was stretched to match. Third time
   this shape has appeared in step 4: the hero clock's inherited `min-width:
   300px`, the footer's 52px team chips, and this. **When something overflows,
   check what its track is being sized by before looking at the thing itself.** */
.cl-block__split > * { min-width: 0; }

@media (min-width: 992px) {
	.cl-block__split          { grid-template-columns: 1.05fr 1fr; }
	.cl-block__split--parks   { grid-template-columns: 0.82fr 1.25fr; }

	/* Prose beside a photograph — step 5 batch 1, the shape the CMS's about
	   listings already have (the old markup was col-lg-7 / col-lg-5, i.e.
	   58/42, and this holds that ratio). ⚠️ `align-items: start`, unlike every
	   other variant: the base rule centres the two tracks, which is right when
	   both are compact and wrong when one is six paragraphs of body copy — the
	   photograph then floats at the vertical middle of a tall column and reads
	   as unaligned with the heading it belongs to. */
	.cl-block__split--figure  { grid-template-columns: 1.4fr 1fr; align-items: start; }
}

/* The standfirst under a band heading. Not `.lead` — Bootstrap's is 1.25rem at
   weight 300, and 300 is a weight montserrat ships but erbaum does not, so the
   pair would render at different weights depending on which face a band uses.

   🔑 THE DESIGN SETS 500 ON EVERY LEDE AND BODY LINE IT HAS, AND 500 IS NOT A
   FACE THIS KIT SHIPS. Checked rather than assumed, by fetching zov5lrg.css:
   montserrat is 200/300/400/600/700/800/900 — no 500, and erbaum is only
   200/400/700. Per CSS Fonts L4 a requested 500 resolves DOWNWARD first, so it
   renders 400 and the declaration quietly says something untrue. Every one of
   these is therefore written as --cl-weight-normal, which is what the browser
   was going to paint anyway.

   ⚠️ This is the third time the kit's inventory has decided a value in this
   package: WP-2.3 found erbaum has no 600 (a 600 there resolves UP to 700), §7
   found montserrat DOES have the 800 the design's buttons want, and this is the
   500. Look the weight up before writing it — two of the three went the way
   nobody guessed. */
.cl-block__lede {
	/* A 3px overhang showed up here at 320px — one line of this paragraph ran
	   past the card's right edge, where `overflow: clip` would have shaved the
	   last glyph. Guarding the wrap rather than retuning the max-width, because
	   this copy is C-01/C-09 in the CMS backlog: once an editor can type into
	   it, an unbreakable token (a URL, a long compound) is a matter of time, and
	   a fixed width only holds for today's sentence. */
	overflow-wrap: break-word;
	font-size:   var(--cl-text-lead);
	font-weight: var(--cl-weight-normal);
	line-height: var(--cl-leading-body);
	max-width:   34em;
	margin:      0 0 var(--cl-space-6);
}

/* Band headings. erbaum 700 uppercase, tight — the design's 48–56px. The site's
   own --cl-text-h1 tops out at 49px and is already fluid, so this rides on it
   rather than adding a seventh size to the ramp. */
.cl-block__heading {
	font-family:    var(--cl-font-display);
	font-weight:    var(--cl-weight-bold);
	font-size:      var(--cl-text-h1);
	line-height:    0.98;
	letter-spacing: -0.01em;
	text-transform: uppercase;
	margin:         0 0 var(--cl-space-4);
}

.cl-block__heading--light { color: var(--cl-white); }
.cl-block__heading--brand { color: var(--cl-text-brand); }

/* ⚠️ ADDED IN STEP 5, AND IT IS NOT A HOMEPAGE VALUE — it exists because the
   homepage's proportions do not transfer. --cl-text-h1 tops out at 49px, which
   is right for a band that IS the page, and wrong for a band that closes one:
   the callouts and sponsors run under real page content on 36+ pages, and at
   49px they announce themselves louder than the <h1> they sit beneath. This
   steps them down one rung while keeping the face, weight, case and tracking
   identical, so the two read as the same object at two scales rather than as
   two treatments. Batch 1's interior pages use it for the same reason. */
.cl-block__heading--sm { font-size: var(--cl-text-h3); }

/* ⚠️ ADDED IN STEP 5 BATCH 1, AND IT IS THE MIDDLE RUNG OF A RAMP RATHER THAN A
   THIRD OPINION. The component now sizes at three levels and each one is a
   position on the page, not a taste: unmodified --cl-text-h1 for a heading that
   IS the page (the homepage bands, and the interior <h1>), --md for a section
   INSIDE a page, --sm for the callout and sponsor bands that run BENEATH one.
   Face, weight, case, leading and tracking are identical across all three, so
   they read as one object at three scales — which is the whole reason the size
   is a modifier and not a separate component.
   🐛 IT LOSES TO `#about h2` ON FOUR PAGES, AND THAT IS NOT A BUG TO FIX HERE.
   styles-custom.css:394 sets `#about h2 { font-size: 2.6rem }` — an ID, (1,0,1),
   which no class can beat without !important. It reaches about, events,
   non-profit-partners and plan-your-trip: all of batch 1, all still in the old
   language except the one being reskinned. The reskinned page drops the id
   instead (about.php now opens `#about-sections`), so the legacy rule keeps
   working for the pages that still need it and stops applying to the one that
   does not. 📌 When the last of the four lands, styles-custom.css:394 and :400
   and the :736 media query are dead and go with it. */
.cl-block__heading--md { font-size: var(--cl-text-h2); }


/* ---- 8b. HERO ----

   ⚠️ READ THE SCRIM DERIVATION IN tokens.css §5h BEFORE TOUCHING THE GRADIENT.
   It is measured against the actual shipping photographs, two of which contain
   pure white pixels underneath the copy. The primary gradient is HORIZONTAL and
   the vertical one secondary — swapped relative to the design — because a
   bottom-up ramp cannot hold a contrast floor across a block of copy 480px
   tall, and this one has to hold 0.75 across all of it. */

.cl-hero {
	position:   relative;
	min-height: var(--cl-hero-h);
	display:    flex;
	/* Column, not the original single-row: the hero now holds TWO in-flow children
	   — .cl-hero__inner (the copy) and .cl-hero__sponsors (the "proudly supported
	   by" stack). On narrow screens the sponsors sit BELOW the copy, which a column
	   gives for free. justify-content:flex-end keeps the whole stack bottom-anchored
	   exactly as align-items:flex-end did for the lone inner before; min-height is a
	   floor, so taller content still grows the band rather than clipping at the top.
	   On wide screens the sponsors are lifted OUT of flow (position:absolute, below)
	   so only the copy remains here — identical to the pre-sponsor layout. */
	flex-direction:  column;
	justify-content: flex-end;
	align-items:     stretch;
	overflow:   clip;              /* `clip`, not `hidden` — §7b of tokens.css */
	background: var(--cl-black);   /* what shows before the first photo paints */
}

/* The slideshow layers. All four are in the DOM at once and cross-fade by
   opacity; only the active one is at 1.

   ⚠️ THE STACK IS NOT `position: absolute` ON A SHARED PARENT BY ACCIDENT —
   each layer must occupy the same box, and `inset: 0` with object-fit: cover is
   what makes the crop identical between them. A fade between two differently
   cropped photographs reads as a jump, not a fade. */
.cl-hero__media {
	position: absolute;
	inset:    0;
}

.cl-hero__slide {
	position:   absolute;
	inset:      0;
	width:      100%;
	height:     100%;
	object-fit: cover;
	opacity:    0;
	transition: opacity var(--cl-hero-fade) ease-in-out;
}

.cl-hero__slide.is-active { opacity: 1; }

/* ⚠️ WITHOUT THIS THE HERO IS A FLASHING ELEMENT. WCAG 2.2 SC 2.3.3 (Animation
   from Interactions) is AAA and does not bind, but SC 2.2.2 Pause/Stop/Hide is
   Level A and applies to anything auto-updating for more than five seconds —
   which a 4-photo carousel on a 5s hold is. js/hero.js does not start the timer
   at all under this query, so the first slide simply stays put; the rule below
   is the belt to that braces, for the case where the class is already applied
   when the query flips mid-session. */
@media (prefers-reduced-motion: reduce) {
	.cl-hero__slide           { transition: none; }
	.cl-hero__slide:not(.is-active) { opacity: 0; }
}

/* The scrim. ⚠️ THIS IS THE ONE CLASS TO TUNE — `.cl-hero__scrim`.

   Dark at the bottom-left, fading up and to the right. Two layers, both the
   design's own shape: a strong bottom-up ramp, plus a gentle 100deg diagonal
   that weights it to the left. Nothing else is involved — an earlier version
   masked a full-height horizontal layer to keep a contrast floor under the
   kicker, which was more machinery than this needs.

   To dial it in, change the alphas below. First gradient = how dark the bottom
   is and how fast it lifts; second = how far the darkness reaches to the right.

   📌 The one element with a real requirement here is the 14px amber kicker
   ("SPRING TRAINING 2027"), which needs 4.5:1 — the headline is large text and
   clears 3:1 easily. Measured ratios against the four real photographs are in
   MEMORY.md; if the kicker is the only thing holding the scrim up, it is
   cheaper to change the kicker than to darken the whole hero. */
.cl-hero__scrim {
	position: absolute;
	inset:    0;
	background:
		linear-gradient(to top,
			rgb(var(--cl-hero-scrim) / 90%)  0%,
			rgb(var(--cl-hero-scrim) / 55%) 32%,
			rgb(var(--cl-hero-scrim) / 12%) 62%,
			rgb(var(--cl-hero-scrim) /  0%) 100%),
		linear-gradient(100deg,
			rgb(var(--cl-hero-scrim) / 50%)  0%,
			rgb(var(--cl-hero-scrim) / 12%) 45%,
			rgb(var(--cl-hero-scrim) /  0%) 65%);
}

.cl-hero__inner {
	/* The copy column. Its gutter/vertical padding now lives on .cl-hero__layout
	   (the wrapper), so the sponsor rail shares the same padded box and the two
	   columns' baselines line up. z-index sits on the wrapper too. */
	width:     100%;
	max-width: 62rem;
}

.cl-hero__kicker  { color: var(--cl-amber-500); }   /* 4.5:1+ — see §5h */

.cl-hero__title {
	font-family:    var(--cl-font-display);
	font-weight:    var(--cl-weight-bold);
	/* 🎯 Michael, 2026-09-09: reduced so each line of the 2027 headline sits on ONE
	   line — "Baseball feels" / "better in the sun." The longer second line was
	   wrapping to three lines total at the old 108px cap. (Supersedes the earlier
	   "don't change the font size" note, which was about the copy swap, not this.) */
	font-size:      clamp(2.5rem, 1.5rem + 3.7vw, 5rem);   /* 40 → 80px */
	line-height:    0.9;
	letter-spacing: -0.01em;
	text-transform: uppercase;
	color:          var(--cl-white);
	margin:         0;
}

/* The second line. Amber on the scrim — and at this size it is large text
   several times over, so the 3:1 threshold applies and it clears it easily. */
.cl-hero__title-accent { color: var(--cl-amber-500); }

.cl-hero__lede {
	font-size:   clamp(1.0625rem, 0.95rem + 0.5vw, 1.3125rem);   /* 17 → 21px */
	font-weight: var(--cl-weight-normal);
	line-height: 1.45;
	color:       var(--cl-sand-200);
	max-width:   35rem;
	margin:      var(--cl-space-5) 0 0;
}

.cl-hero__actions {
	display:   flex;
	flex-wrap: wrap;
	gap:       var(--cl-space-3);
	margin-top: var(--cl-space-6);
}

/* ---- HERO LAYOUT (2026-09-04; sponsor rail removed 2026-09-09) --------------
   .cl-hero__layout wraps .cl-hero__inner (the copy). It is the single in-flow
   child of .cl-hero, which stays a bottom-anchored column, so the layout sits at
   the bottom of the band and carries the hero's gutter. It USED to be a two-column
   row (copy left, "Proudly supported by" sponsor rail right, baseline-aligned);
   the rail came off pre-launch (Discover Flagstaff moved to the Schedule
   Spotlight), so this is now a single-column wrapper. Kept as its own element
   because it owns the gutter/padding and z-index the copy sits on. */
.cl-hero__layout {
	position: relative;
	z-index:  1;                 /* the whole content layer, above the scrim */
	display:  flex;
	flex-direction: column;
	padding:  var(--cl-block-pad-y) var(--cl-block-pad-x);
}

@media (min-width: 64rem) {
	.cl-hero__inner { flex: 1 1 auto; max-width: 62rem; }
}

/* The hero clock. A second instance of the WP-2.8 component — which is possible
   only because that package made every part of it attribute-keyed, and because
   step 3 removed the last surviving id when the bottom bar stopped being
   `position: fixed`. Two clocks sharing an id means getElementById returns the
   first and the second reads 00:00:00:00 forever, with nothing erroring. */
.cl-hero__clock {
	display:     flex;
	align-items: flex-end;
	gap:         var(--cl-space-5);
	margin-top:  var(--cl-space-6);
}

.cl-hero__clock-lead {
	font-size:      var(--cl-kicker-size-sm);
	font-weight:    var(--cl-weight-extrabold);
	letter-spacing: var(--cl-kicker-tracking-sm);
	line-height:    var(--cl-leading-snug);
	text-transform: uppercase;
	color:          var(--cl-sand-350);
	padding-bottom: var(--cl-space-5);
}

.cl-hero .cl-countdown--hero { background: none; padding: 0; }

/* 🐛 `min-width: 0` IS THE WHOLE FIX FOR THE 320px CLIP, AND IT TOOK THREE
   WRONG ATTEMPTS TO FIND BECAUSE THE SYMPTOM POINTED AT THE UNIT BOXES.
   §4's shared .cl-countdown__display carries `min-width: 300px` — correct for
   the full-width bar it was written for, and inherited by this variant. A flex
   container's min-width is a hard floor, so it held the row at 300px inside a
   248px column no matter how small the boxes got: shrinking the type, cutting
   the min-width on the boxes and dropping a whole unit each moved the box sizes
   and left the row exactly 300px wide. The boxes were never the problem.

   🔑 THE READING THAT GENERALISES: when an element refuses to go below a size
   its own children do not require, look at what the PARENT is being held to,
   not at the children. And a shared component picking up a variant it was not
   written for will bring constraints that only made sense in the original. */
.cl-hero .cl-countdown--hero .cl-countdown__display {
	display:   flex;
	gap:       var(--cl-space-3);
	min-width: 0;
	flex-wrap: wrap;   /* a last resort below any width we test, not a layout */
}

.cl-hero .cl-countdown--hero .cl-countdown__unit {
	display:    block;
	text-align: center;
}

.cl-hero .cl-countdown--hero .cl-countdown__value {
	display:        block;
	font-family:    var(--cl-font-display);
	font-weight:    var(--cl-weight-bold);
	font-size:      clamp(2rem, 1.3rem + 2.8vw, 3.25rem);   /* 32 → 52px */
	line-height:    1;
	color:          var(--cl-white);
	background:     var(--cl-glass-fill-strong);
	border:         1px solid var(--cl-glass-hairline);
	border-radius:  var(--cl-radius-md);
	padding:        var(--cl-space-3) var(--cl-space-4);
	min-width:      4.5rem;

	/* Tabular figures, same reason as the bar: without them the box resizes
	   every time a 1 ticks past, and four boxes jitter once a second. */
	font-variant-numeric: tabular-nums;
}

.cl-hero .cl-countdown--hero .cl-countdown__label {
	display:        block;
	margin-top:     var(--cl-space-2);
	font-size:      var(--cl-kicker-size-sm);
	font-weight:    var(--cl-weight-bold);
	letter-spacing: var(--cl-kicker-tracking-sm);
	text-transform: uppercase;
	color:          var(--cl-sand-350);
}

/* ---- The clock's responsive steps ----

   Units are HIDDEN, never removed from the markup. They keep ticking, so the
   visually-hidden sentence js/countdown.js maintains is unchanged at every
   width and nothing in the JS knows this happened. Dropping a unit from the
   DOM per breakpoint would make the announced text width-dependent.

   🔑 WHICH UNITS GO IS NOT AN ARBITRARY BUDGET. Seconds first, then minutes —
   the same order, and for the same reason, as WP-2.8's decision that the
   screen-reader sentence stops at hours: the smaller the unit, the staler it is
   by the time it has been read, so the large ones carry the meaning. Days and
   hours is the pair the announcement has always used.

   ⚠️ The digits do NOT shrink further to make more units fit. Below ~32px the
   figures stop being the focal point the hero is built around, and a hero whose
   headline element is squinting-small on a phone has lost more than a unit. */

@media (max-width: 767.98px) {
	/* Seconds out, and the remaining boxes tighten. At 576px the four full-size
	   boxes measured 374px of content plus 36px of gaps against a 412px column
	   — over by two pixels, which the flex-wrap fallback caught as a second row
	   rather than as an overflow. Two pixels is not a number to tune against, so
	   the whole step goes to the next breakpoint up. */
	.cl-hero .cl-countdown--hero .cl-countdown__unit:last-child { display: none; }
	.cl-hero .cl-countdown--hero .cl-countdown__display { gap: var(--cl-space-2); }
	.cl-hero .cl-countdown--hero .cl-countdown__value {
		min-width: 3.5rem;
		padding:   var(--cl-space-2) var(--cl-space-3);
	}
}

@media (max-width: 575.98px) {
	/* "FIRST / PITCH IN" goes: it is a label for the boxes beside it, and once
	   the row is this narrow it costs more width than it explains. */
	.cl-hero__clock-lead { display: none; }
}

@media (max-width: 399.98px) {
	/* Minutes out. A three-digit day figure in 32px erbaum is ~96px of box, so
	   two is what a 320px phone holds at this type size. */
	.cl-hero .cl-countdown--hero .cl-countdown__unit:nth-child(3) { display: none; }
}

/* Phone padding for every band, hero included. The design is a desktop mockup
   and Michael's instruction was large screens first, mobile tuned later — this
   is not that tuning pass, it is the floor that keeps content off the card's
   rounded edge. --cl-block-pad-y stays generous on purpose so the bands still
   read as separate sections rather than as one column of text. */
@media (max-width: 575.98px) {
	/* 🐛 THE FOOTER MUST BE IN THIS LIST, and leaving it out clipped the brand
	   column at 320px. Giving the footer the page's 48px inset (so its columns
	   line up with the bands above) also gave it the page's problem: 276px of
	   page minus 96px of padding leaves 180px, and the footer badge and social
	   row are locked to --cl-footer-logo-w at 220px. They were cut off, silently
	   — `overflow: clip` again, so the overflow measured as zero.
	   At 24px a side the column gets 228px and the 220px block fits. */
	/* 🔑 .cl-subnav__inner JOINS THIS LIST FOR THE SAME REASON THE FOOTER HAD TO
	   (step 4b). Michael's instruction was that the curtain line up with the
	   homepage content, and honouring that at 3rem across ALL widths quietly
	   broke the phone: 320px minus 96px of gutter left 224px for fifteen team
	   chips, which measured **18px** each — under WCAG 2.2 SC 2.5.8's 24px
	   target floor. "Align with the page" means align with what the page
	   actually does at each width, and below 576px the page itself backs off to
	   24px. Following the token was right; following it only at one breakpoint
	   was not. */
	/* ⚠️ THE INTERIOR PAGE BACKS OFF TO THE SAME 24px AND IS *NOT* IN THIS LIST,
	   for a source-order reason rather than a design one. §10b's
	   `.cl-page-interior main .container` and this block's selector would tie on
	   specificity (0,2,1), and §10 is later in the file — so adding it here
	   would be silently overridden at exactly the width it exists to fix. The
	   two rules it needs live at the end of §10, and they are the same 24px.
	   🔑 The general form: a phone override only belongs in a shared list if the
	   list wins the cascade against every component that reads it. */
	.cl-block,
	.cl-hero__layout,
	.cl-footer .container,
	.cl-subnav__inner {
		padding-inline: var(--cl-space-5);
	}
	.cl-block,
	.cl-hero__layout {
		padding-block: var(--cl-space-8);
	}
}


/* ---- 8c. SCHEDULE SPOTLIGHT ----

   Copy column + the Opening Week card. The card is a .cl-card--raised (§7c);
   only what is specific to a game list lives here.

   📌 THIS COMPONENT USES THE DESIGN'S EXACT PX RATHER THAN THE 4px SCALE, AND
   THAT IS DELIBERATE (Michael, 2026-08-07: make this card look like the design).
   15px/22px row padding, 10px matchup gap, 6px before the venue — none of which
   sit on the scale. It is the one component signed off against the mockup
   directly, so it is exempt rather than inconsistent; everything else stays on
   --cl-space-*, and the general question of scale-vs-mockup is still the one
   decision waiting in §4c of the reskin doc. */

.cl-spotlight__meta {
	font-size:   var(--cl-text-ui);
	font-weight: var(--cl-weight-semibold);
	color:       var(--cl-text-secondary);   /* 4.71 on white */
}

.cl-spotlight__cta-row {
	display:     flex;
	flex-wrap:   wrap;
	align-items: center;
	gap:         var(--cl-space-4);
}

/* ---- FEATURED SPONSOR — Discover Flagstaff (2026-09-09) --------------------
   Moved out of the retired hero rail into the Schedule Spotlight's left column,
   under the CTA. A ~480×220 rectangular impressions unit on a white card (same
   white-surface idiom every sponsor logo uses). The placeholder creative is
   400×160, so the card renders a touch under 220 tall until a 480×220 asset
   drops in; max-width caps the spot's width and the image scales inside it. */
.cl-spotlight__sponsor {
	/* Michael, 2026-09-10: at --cl-space-6 the unit crowded the Full Schedule
	   button and read as more left-column copy rather than a sponsor. --space-7
	   opens a clear gap so the eyebrow + card land as their own block. */
	margin-top: var(--cl-space-7);
	max-width:  480px;
}

.cl-spotlight__sponsor-eyebrow {
	display:        block;
	margin-bottom:  var(--cl-space-2);
	font-size:      var(--cl-kicker-size-sm);
	font-weight:    var(--cl-weight-extrabold);
	letter-spacing: var(--cl-kicker-tracking-sm);
	text-transform: uppercase;
	color:          var(--cl-text-secondary);   /* 4.71 on the white band */
}

.cl-spotlight__sponsor-link {
	display:       block;
	background:    var(--cl-white);
	border-radius: var(--cl-radius-md);
	box-shadow:    var(--cl-shadow-1);
	overflow:      hidden;
	transition:    transform var(--cl-duration-fast) var(--cl-ease),
	               box-shadow var(--cl-duration-fast) var(--cl-ease);
}

.cl-spotlight__sponsor-link:hover {
	transform:  translateY(-2px);
	box-shadow: var(--cl-shadow-2);
}

.cl-spotlight__sponsor-link:focus-visible {
	outline:    none;
	box-shadow: var(--cl-focus-ring);
}

.cl-spotlight__sponsor-img {
	display:   block;
	width:     100%;
	height:    auto;
}

/* ⚠️ THE CARD SETS ITS OWN PADDING TO ZERO. .cl-card ships --cl-card-pad so a
   panel of prose is not flush to its own hairline; this one is a header strip
   plus full-bleed rows, and every one of them draws its own edge-to-edge rule.
   Left at the default the strip would float in a 24px white margin. */
.cl-schedule-card { padding: 0; }

.cl-schedule-card__head {
	display:         flex;
	align-items:     center;
	justify-content: space-between;
	gap:             var(--cl-space-3);
	background:      var(--cl-green-500);
	padding:         var(--cl-space-4) 22px;   /* 16px / the design's 22px */
}

.cl-schedule-card__title {
	font-family:    var(--cl-font-display);
	font-weight:    var(--cl-weight-bold);
	font-size:      1.0625rem;   /* 17px */
	letter-spacing: 0.03em;
	text-transform: uppercase;
	color:          var(--cl-white);        /* 8.64 on green-500 */
	margin:         0;
}

.cl-schedule-card__range {
	font-size:      var(--cl-text-sm);
	font-weight:    var(--cl-weight-bold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color:          var(--cl-amber-500);    /* 4.62 on green-500 */
	white-space:    nowrap;
}

/* The rows. A <ul>, because three games is a list — the design draws them as
   sibling divs, which announces nothing and gives a screen-reader user no count
   and no way to step between items. */
.cl-schedule-card__list {
	list-style: none;
	margin:     0;
	padding:    var(--cl-space-2) 0;
}

/* 🐛 `margin: 0` HERE IS THE OTHER HALF OF THE SAME BUG AS THE DATE, AND IT WAS
   WORTH 48px. These rows are <li>, and styles-custom.css:118 sets
   `li { margin-bottom: 1rem }` site-wide — so each of the three carried 16px
   below it, the list measured 266.8px against 218.8px of actual content, and
   the card ended in a band of dead space under the last game.

   🔑 THE GENERAL FAULT, TWICE IN ONE COMPONENT: a semantic element used for its
   meaning brings this codebase's legacy margins with it, and they only become
   visible when something is being aligned or measured against them. The <p>
   date and the <li> rows were both chosen for correct semantics — a <ul> of
   three games, a paragraph for the date — and both needed their inherited
   spacing cleared before the layout could be read. Check the reset before
   trusting a box in this stylesheet. */
.cl-schedule-card__row {
	margin:      0;
	display:     flex;
	align-items: center;
	gap:         var(--cl-space-4);
	padding:     15px 22px;   /* the design's, exactly — see the 📌 below */
}

.cl-schedule-card__row + .cl-schedule-card__row {
	border-top: 1px solid var(--cl-sand-200);
}

/* ---- Clickable teaser rows (2026-09-09) — each row opens the day's Fancybox
   popup. A stretched link covers the whole row so the click target is the row,
   not a small control, and the flex layout of the visible spans is untouched.
   The hover tint + pointer cue signal it is interactive; the focus ring lands on
   the invisible trigger. */
.cl-schedule-card__row--clickable { position: relative; cursor: pointer; }

.cl-schedule-card__row--clickable:hover { background: var(--cl-sand-100, rgb(0 0 0 / 3%)); }

.cl-schedule-card__row-trigger {
	position: absolute;
	inset:    0;
	z-index:  1;
	display:  block;
	border-radius: inherit;
	/* No text — aria-label names it. It sits above the decorative spans but below
	   the (currently none) in-row links; Tickets lives in the popup, not the row. */
}

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

/* 🐛 `margin: 0` HERE IS THE WHOLE "DATE ALIGNMENT IS OFF" BUG. This is a <p>,
   and nothing had reset it, so Bootstrap's reboot margin was still live —
   measured at **22.4px on the bottom**. In a row that is `align-items: center`
   that margin is part of the item's box, so the centring put the MARGIN BOX in
   the middle and the visible text sat high, with 12px above it and 34.4px
   below. It also inflated the row from ~61px to **83.3px**, which is the "odd
   spacing" in the same breath — one cause, two symptoms.

   🔑 Worth generalising: the sibling `.cl-schedule-card__title` is also a <p>
   and DID get `margin: 0`. Resetting one and not the other is the easy version
   of this mistake — a semantic element dropped into a flex row brings its UA
   and framework margins with it, and they are invisible until something is
   being aligned against them. */
.cl-schedule-card__date {
	margin:      0;
	flex:        0 0 3.5rem;
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-extrabold);
	line-height: 1.2;
	color:       var(--cl-brown-500);   /* 5.48 on white */
	text-transform: uppercase;
}

.cl-schedule-card__day {
	display:   block;
	font-size: var(--cl-text-lead);
	color:     var(--cl-ink);
}

.cl-schedule-card__matchup {
	display:     flex;
	align-items: center;
	gap:         10px;   /* the design's */
	flex:        1 1 auto;
	min-width:   0;
}

.cl-schedule-card__logo {
	width:      2.125rem;   /* 34px */
	height:     2.125rem;
	object-fit: contain;
	flex:       0 0 auto;
}

.cl-schedule-card__at {
	font-size:   var(--cl-text-ui);
	font-weight: var(--cl-weight-bold);
	color:       var(--cl-text-secondary);
}

.cl-schedule-card__venue {
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-semibold);
	color:       var(--cl-gray-700);
	margin-left: 6px;   /* the design's */
	overflow:    hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.cl-schedule-card__time {
	flex:        0 0 auto;
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-bold);
	color:       var(--cl-text-brand);   /* 8.64 on white */
	font-variant-numeric: tabular-nums;
}

/* Below 480px the venue name is what has to go — it is the only part of a row
   that is not the game itself, and truncating it to three characters is worse
   than omitting it. The park is one tap away on the full schedule. */
@media (max-width: 479.98px) {
	.cl-schedule-card__venue { display: none; }
}

/* And the row itself has to be able to get narrow, or `min-width: 0` above just
   moves the overflow inside the card. Measured at 320px: 248px of content
   against a row needing 24px padding a side + 16px gaps + a 56px date column +
   two 34px marks. Trimming the padding and gaps saves 32px and brings the whole
   card to ~244px, which fits. */
@media (max-width: 575.98px) {
	.cl-schedule-card__row  { padding-inline: var(--cl-space-3); gap: var(--cl-space-3); }
	.cl-schedule-card__head { padding-inline: var(--cl-space-3); }
	.cl-schedule-card__date { flex-basis: 3rem; }
}


/* ---- 8d. TEAM GRID ----

   15 tiles on a sand band. Live from CMS itemID 22 — the same query that has
   fed the homepage team strip all along, so this block is not new content, it
   is the existing content in the 2027 shape. */

.cl-teamgrid { background: var(--cl-sand-100); }

.cl-teamgrid__grid {
	display:   grid;
	gap:       var(--cl-space-3);
	max-width: 70rem;
	margin:    0 auto;
	padding:   0;
	list-style: none;

	/* 2 → 3 → 5 across. ⚠️ NOT auto-fit: 15 tiles divide evenly by 5 and by 3
	   and leave a single orphan on 4 or 2 — and an orphan in a logo grid reads
	   as a missing team rather than as a wrap. The step-3 footer hit exactly
	   this with six social icons in a 240px column. */
	grid-template-columns: repeat(2, 1fr);
}

@media (min-width: 576px) { .cl-teamgrid__grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 992px) { .cl-teamgrid__grid { grid-template-columns: repeat(5, 1fr); } }

/* The tile itself is the link — one control, one target, no nested focus stop.
   .cl-card--tile (§7c) carries the lift and the focus ring. */
.cl-teamgrid__tile {
	display:         flex;
	flex-direction:  column;
	align-items:     center;
	justify-content: center;
	gap:             var(--cl-space-3);
	padding:         var(--cl-space-5) var(--cl-space-3) var(--cl-space-4);
	text-align:      center;
	text-decoration: none;
	height:          100%;
}

.cl-teamgrid__logo {
	height:     3.625rem;   /* 58px */
	width:      auto;
	max-width:  100%;
	object-fit: contain;
}

/* ⚠️ INK, NOT --cl-link. These tiles are links, and links are brick #b23a2e
   site-wide as of step 1 — but a 15-up grid of brick team names reads as a
   warning state rather than as navigation, and the design has them at #3a3a3a.
   The tile's affordance is its lift and its cursor, not its colour. */
.cl-teamgrid__name {
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-bold);
	line-height: var(--cl-leading-snug);
	color:       var(--cl-gray-700);   /* 10.37 on white */
}


/* ---- 8e. BALLPARKS ----

   Dark band, copy column + a 10-row park list, live from CMS itemID 18.

   🔑 THE TENANCY IS DERIVED FROM THE GAMES TABLE, NOT FROM A CMS FIELD, because
   no stadium↔team relation exists in this CMS. The home teams playing at each
   park in the schedule ARE that relation, they are already maintained every
   season, and they are what index.php reads. See the note there before adding a
   CMS field for it.

   ⚠️ THE OBVIOUS-LOOKING SHORTCUT IS NOW MORE TEMPTING, NOT LESS, AND IT IS
   STILL WRONG. itemID 18's info6 holds the tenancy as English prose — "Spring
   Training Home of the Chicago Cubs" — which is very nearly the sentence this
   band now renders since the logo row became text on 2026-08-10. It reads like
   a field made for the job. It is not: it is free text with no guarantee of
   format, currency or even presence, and a season where one park's prose was
   never updated would publish a wrong claim in confident type. The schedule
   cannot drift that way. Do not repoint this at info6 because the output
   happens to look the same. */

.cl-parks {
	position:   relative;
	overflow:   clip;
	background: var(--cl-black);
}

/* The field photograph, at .16 — decoration, and nothing is read off it.
   ⚠️ BUT IT IS NOT CONTRAST-NEUTRAL, AND ASSUMING IT WAS PUT A REAL AA FAILURE
   IN THIS BAND. At opacity .16 over #141414 a worst-case white photograph lifts
   the effective background from rgb(20,20,20) to **rgb(58,58,58)** — and to
   rgb(71,71,71) inside a row, once the .07 glass fill is composited on top.
   That is a large move, and the city line was the one foreground it broke:
   the design's #9aa0a4 measures 3.49:1 there, not the 6.55:1 it gets on flat
   black. Found by sweeping all 55 of axe's `incomplete` contrast nodes against
   the real composite, NOT by the sample check that preceded it — which had
   spot-checked this band against rgb(20,20,20) and passed it.

   🔑 THE SAME MISTAKE IN THE SAME SHAPE AS WP-2.7's HALL OF FAME CARDS: axe
   cannot compute contrast over an image, so it reports `incomplete` and every
   one of these looked like a pass. Measured at .16, on rgb(58/71,58/71,58/71):
   heading white 11.44 · kicker amber 6.21 · lede gray-350 7.27 · park name
   white 9.23 — all fine. Only the city line needed moving. */
.cl-parks__bg {
	position:   absolute;
	inset:      0;
	width:      100%;
	height:     100%;
	object-fit: cover;
	opacity:    0.16;
}

.cl-parks__inner { position: relative; }

.cl-parks__list {
	display:    grid;
	gap:        var(--cl-space-2);
	list-style: none;
	margin:     0;
	padding:    0;
}

@media (min-width: 768px) { .cl-parks__list { grid-template-columns: 1fr 1fr; } }

.cl-parks__row {
	display:       flex;
	align-items:   center;
	gap:           var(--cl-space-3);
	background:    var(--cl-glass-fill);
	border:        1px solid var(--cl-glass-hairline);
	border-radius: var(--cl-radius-md);
	padding:       var(--cl-space-3);
}

/* The stadium marks are dark artwork on transparent SVG — the same fact that
   forced the footer's ballpark strip to stay on white in WP-2.6. On the glass
   fill over black they would be invisible, so each sits on its own white chip. */
.cl-parks__mark {
	flex:            0 0 3rem;
	width:           3rem;
	height:          3rem;
	border-radius:   var(--cl-radius-sm);
	background:      var(--cl-white);
	display:         flex;
	align-items:     center;
	justify-content: center;
}

.cl-parks__mark img {
	width:      2.5rem;
	height:     2.5rem;
	object-fit: contain;
}

.cl-parks__text { flex: 1 1 auto; min-width: 0; }

.cl-parks__name {
	display:       block;
	font-size:     var(--cl-text-ui);
	font-weight:   var(--cl-weight-bold);
	color:         var(--cl-white);      /* 16.9 on the fill over black */
	line-height:   var(--cl-leading-snug);
}

.cl-parks__city {
	display:     block;
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-normal);
	/* ⚠️ NOT the design's --cl-gray-450: that is 3.49:1 once the field photo is
	   composited at its worst case (see the ⚠️ on .cl-parks__bg), and it only
	   looks safe because it measures 6.55:1 against the flat black underneath.
	   --cl-gray-350 is 5.86:1 on the same real background and is already this
	   band's secondary tier — .cl-block__lede next to it uses it — so the fix
	   also makes the two dimmer lines here one tier instead of two.
	   The hierarchy the design wanted survives: the park name is white at
	   9.23:1 and bold, this is 5.86:1 and regular. */
	color:       var(--cl-gray-350);     /* 5.86 on the real composite */
}

/* 🎨 THE TENANCY IS A THIRD TEXT LINE AS OF 2026-08-10 (Michael): the row's
   logo chips "look kind of cramped here", so "Home of the …" goes under the
   city instead. What this replaced was a flex row of 38px white circles pinned
   to the right of every row — ~76px of the row's width on the shared parks,
   inside a band that is two columns wide from 768px up.

   ⚠️ IT IS INSIDE .cl-parks__text, NOT A SIBLING OF IT. The logo row was a
   sibling with `flex: 0 0 auto`, which is what made it compete with the name
   and city for the row's width. As a third line in the text block it competes
   with nothing, and the row grows downward — which is free here, because these
   rows are grid items in a two-column list and the tallest row in a pair sets
   the height either way.

   📌 That also means the old row is now a one-line block. If a park is ever
   wanted back as logos, this is not a CSS revert — the markup moved. */
.cl-parks__tenants {
	display:     block;
	margin-top:  0.15rem;
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-normal);

	/* Same tier as the city line, and for the same measured reason — see the ⚠️
	   on .cl-parks__city. --cl-gray-450 is 3.49:1 once the field photograph is
	   composited at its worst case; --cl-gray-350 is 5.86:1 on that same real
	   background. The frame words are the dim half of this line. */
	color:       var(--cl-gray-350);
	line-height: var(--cl-leading-snug);
}

/* 🔑 THE CLUB NAMES CARRY THE HIERARCHY, NOT A THIRD COLOUR. With the city and
   this line both at --cl-gray-350 the row would otherwise have two identical
   tiers stacked, and the obvious fix — a dimmer grey for the tenancy — is
   exactly the one this band has already proved unavailable: everything below
   gray-350 fails against the composited photograph. So the emphasis goes on
   the WEIGHT and on white, and it lands on the actual information. "Home of
   the" stays grey; the clubs read as the content of the line.
   White measures 9.23:1 on the real composite — the figure .cl-parks__name
   already relies on. */
.cl-parks__tenant-name {
	color:       var(--cl-white);
	font-weight: var(--cl-weight-semibold);

	/* 🐛 WITHOUT THIS THE LINE BREAKS INSIDE CLUB NAMES. The rendered two-tenant
	   rows wrapped as "Seattle Mariners and San / Diego Padres" and "Texas
	   Rangers and Kansas / City Royals" — the break landing between the city and
	   the club, which is the one place in the sentence it must not, because it
	   reads for an instant as two different teams. Keeping each name atomic
	   moves the break to the spaces the sentence actually offers, around "and".

	   ⚠️ IT FITS AT 320px BY 9 PIXELS, AND THAT IS THE REAL NUMBER — measured in
	   a 320px frame, not estimated. The text column there is 162px and the
	   longest club name in the CMS, "Arizona Diamondbacks", renders 153px at
	   --cl-text-sm. Every one of the fifteen fits; the next four are 139, 136,
	   136 and below. Verified at 320/375/480/576/768/992/1200/1400/1800: zero
	   overflowing nodes and zero rows whose content exceeds their own box.

	   🔑 SO THE MARGIN IS REAL BUT THIN, AND IT IS WORTH KNOWING WHICH WAY IT
	   MOVES. `white-space: nowrap` on a string that no longer fits does not
	   wrap — it overflows, and this row sits in a two-column grid where that
	   shows up as a band-wide horizontal scroll rather than as one ugly row.
	   The input is CMS info3, so a name is one text field away from being
	   longer. In practice club names get SHORTER (the Athletics dropped
	   "Oakland" and read "Athletics" here today), which is why this ships as is.
	   📌 The escape, if a longer name ever lands: drop this one declaration
	   inside a `@media (max-width: 575.98px)` block. Below that breakpoint the
	   list is single-column, and a name broken across two lines is a cosmetic
	   nit where an overflow is a bug. */
	white-space: nowrap;
}

/* 🔑 BOTH ARE NOW LINKS (WP-2.10, 2026-08-17: name → stadium.php, tenant → team.php)
   AND THE GLOBAL `a:hover` COLOUR IS NOT SAFE HERE. styles-custom.css sets
   `a:hover { color: var(--cl-link-hover) }` — an orange tuned to 5.67:1 on a WHITE
   page background, with no measurement against this band's composited field
   photograph. Every colour tier this band uses was chosen by testing against that
   real composite (see the ⚠️ notes above: gray-450 fails at 3.49:1, gray-350 is the
   floor at 5.86:1), so an unmeasured colour swap on hover is exactly the mistake
   those notes exist to prevent. Both classes already carry a specificity edge over
   plain `a` (0,1,0 vs 0,0,1) that holds for the base state; `a:hover` is (0,1,1) and
   WOULD win over them without this. Underline is the affordance instead — free,
   uses the existing (already-verified) text colour, and needs no new measurement. */
.cl-parks__name:hover,
.cl-parks__name:focus-visible,
.cl-parks__tenant-name:hover,
.cl-parks__tenant-name:focus-visible {
	/* Both base rules already set this same white — restated explicitly,
	   not `inherit`, because inherit would resolve off the parent .cl-parks__text
	   span instead of guaranteeing the one colour this band has verified. */
	color:           var(--cl-white);
	text-decoration: underline;
}


/* ---- 8f. EXPLORE TILES ----

   Four photo cards. The design's hover is
   `translateY(-10px) rotateX(4deg) scale(1.025)` on a 1200px perspective.

   ⚠️ THE rotateX IS DROPPED AND THE TRANSLATE HALVED. A 4-degree X rotation on
   a 340px card moves its top edge ~12px in Z and shears the photograph and its
   caption — on text, that is a legibility cost paid for a decoration, and it is
   the sort of motion SC 2.3.3 exists to let a user turn off. What survives is
   the lift and the amber ring, which is what actually signals "this is a link". */

.cl-explore__grid {
	display:    grid;
	gap:        var(--cl-space-4);
	list-style: none;
	margin:     0;
	padding:    0;
	grid-template-columns: 1fr;
}

@media (min-width: 576px) { .cl-explore__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 992px) { .cl-explore__grid { grid-template-columns: repeat(4, 1fr); } }

/* --three — store.php's landing (WP-2.10 step 5c). Exactly three tiles, so the
   4-up base would leave a fourth column empty and the 2-up tablet step would
   strand the third tile on its own row. Instead: one column until the tablet
   width, then three across. Same (0,1,0) specificity as the base rules, so these
   must stay AFTER them to win on source order. */
@media (min-width: 576px) { .cl-explore__grid--three { grid-template-columns: 1fr; } }
@media (min-width: 768px) { .cl-explore__grid--three { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 992px) { .cl-explore__grid--three { grid-template-columns: repeat(3, 1fr); } }

.cl-explore__tile {
	position:      relative;
	display:       block;
	min-height:    21.25rem;   /* 340px */
	border-radius: var(--cl-radius-lg);
	overflow:      clip;
	text-decoration: none;
	box-shadow:    0 2px 10px rgb(15 12 9 / 12%);
	transition:
		transform  var(--cl-duration-base) var(--cl-ease),
		box-shadow var(--cl-duration-base) var(--cl-ease);
}

.cl-explore__tile:hover {
	transform:  translateY(-5px);
	box-shadow: 0 26px 46px rgb(15 12 9 / 40%), 0 0 0 2px var(--cl-amber-500);
}

/* Same finding as §7c's team tiles: `transition: none` here is load-bearing.
   .cl-explore__tile transitions box-shadow, so Chrome expands the focus ring's
   `none` start frame into transparent zero-size layers and the ring FADES IN —
   which means tabbing quickly through four tiles never fully draws it. */
.cl-explore__tile:focus-visible {
	transition: none;
	box-shadow: 0 26px 46px rgb(15 12 9 / 40%), var(--cl-focus-ring);
}

.cl-explore__img {
	position:   absolute;
	inset:      0;
	width:      100%;
	height:     100%;
	object-fit: cover;
}

/* ⚠️ 0.92 AT THE BOTTOM IS THE §5f SOLVE AGAIN, NOT THE DESIGN'S 0.9 BY EYE.
   These four photographs sit under white 26px display type (large: 3:1) and
   #eee7db 14px body copy (4.5:1, needs a >= 0.621). The gradient is vertical
   and the caption sits in its bottom ~30%, so the floor only has to hold there
   — which is the difference from the hero, where the copy spans 60% of the box.
   ⚠️ The Dugout tile is the one card whose photograph is not yet final (the
   design's is an unlicensed comp), so this must hold for whatever replaces it. */
.cl-explore__scrim {
	position:   absolute;
	inset:      0;
	background: linear-gradient(180deg,
		rgb(15 12 9 /  5%)  0%,
		rgb(15 12 9 / 55%) 58%,
		rgb(15 12 9 / 92%) 82%);
}

.cl-explore__body {
	position: absolute;
	inset:    auto var(--cl-space-5) var(--cl-space-5) var(--cl-space-5);
}

.cl-explore__title {
	display:        block;
	font-family:    var(--cl-font-display);
	font-weight:    var(--cl-weight-bold);
	font-size:      1.625rem;   /* 26px */
	line-height:    var(--cl-leading-tight);
	text-transform: uppercase;
	color:          var(--cl-white);
	margin:         0;
}

.cl-explore__text {
	display:     block;
	font-size:   var(--cl-text-ui);
	font-weight: var(--cl-weight-normal);
	line-height: 1.45;
	color:       var(--cl-sand-200);
	margin:      var(--cl-space-2) 0 0;
}

/* Not a control — the whole tile is the link, and a nested <a> or <button> here
   would be a second tab stop to the same destination. It is styled as a pill
   because the design draws one, and it is inert by construction. */
/* 🔑 CENTRED ON THE GLYPHS' REAL INK, WHICH IS NOT WHERE THE LINE BOX IS.
   All-caps has no descenders, so the ink sits in the upper part of the line box
   and equal padding always reads high. The earlier attempts at this were
   measured with `Range.getClientRects()`, which returns the LINE box — so they
   reported the label as already centred and the correction as ~0. Canvas
   `TextMetrics.actualBoundingBox*` gives the true ink, and with it the numbers
   are unambiguous (montserrat 800 at 12.8px, line-height 1):

       line box            12.80px, baseline 9.90px from its top
       ink                 0.94px → 10.08px  (caps 8.96 above the baseline)
       space above ink     padTop + 0.94
       space below ink     padBottom + 2.72

   Equal padding therefore leaves 1.78px more below the glyphs than above, and
   centring means moving 0.89px from the bottom to the top. Held at ±1px so the
   pill's total height is unchanged.

   📌 Michael's own eye put this at `padding-top: 0.96rem` (15.36px) — about
   2.4px lower than optical centre. That difference is taste rather than
   metrics, and it is a one-value change here if he prefers it. */
.cl-explore__cue {
	display:        inline-block;
	margin-top:     var(--cl-space-3);
	line-height:    1;
	padding:        calc(var(--cl-space-3) + 1px) var(--cl-space-4) calc(var(--cl-space-3) - 1px);
	border-radius:  var(--cl-radius-pill);
	background:     var(--cl-amber-500);
	color:          var(--cl-ink);        /* 8.52 — white on amber is 1.84 */
	font-size:      var(--cl-text-sm);
	font-weight:    var(--cl-weight-extrabold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}


/* ---- 8f-b. THE FLAT TILE — a destination with no photograph ----

   🔑 THIS EXISTS BECAUSE THE PHOTO TILE CANNOT DEGRADE, AND THE OLD CALLOUT
   CARD COULD. asset-callouts*.php always guarded its artwork with file_exists()
   and simply skipped the <img>; a card with no photo was still a card.
   .cl-explore__tile is a different animal — its text sits ON the photograph
   over a scrim, so "no image" is not a smaller tile, it is white text on
   nothing. Six of the CMS's callout rows have artwork for three.

   Michael's call 2026-08-11 (option B): build the no-photo variant now and let
   real photography replace it set by set, rather than stall batch 0b on a photo
   budget or collapse to one site-wide set.

   ⚠️ THE RISK THIS SHAPE HAS TO DEFEAT IS "LOOKS BROKEN". A flat dark rectangle
   where its neighbours have photographs reads as an image that failed to load,
   which is worse than the two-tier layout it replaces. So it is not a photo
   tile minus the photo: it carries its own gradient, a hairline and a soft
   corner highlight, so it reads as a drawn surface rather than an empty one.

   Measured, not assumed — the gradient runs --cl-green-600 → --cl-green-900 and
   the WORST case is the green-600 end:
       white title    9.91   (needs 3:1 large)
       sand-200 text  8.04   (needs 4.5:1)
   Both clear with room. The amber cue keeps its own background so the tile
   underneath it is irrelevant to its contrast.

   📌 It reuses .cl-explore__body/__title/__text/__cue untouched, so the two
   variants stay in step by construction — change the caption and both follow. */
.cl-explore__tile--flat {
	background-image:
		radial-gradient(120% 90% at 100% 0%, rgb(251 176 66 / 10%) 0%, rgb(251 176 66 / 0%) 60%),
		linear-gradient(155deg, var(--cl-green-600) 0%, var(--cl-green-900) 100%);
	box-shadow:
		inset 0 0 0 1px rgb(255 255 255 / 8%),
		0 2px 10px rgb(15 12 9 / 12%);
}

/* The photo tiles' hover keeps their shadow and adds the amber ring. This one
   has an inset hairline to preserve as well, so the ring is composed with it
   rather than replacing it — otherwise the border blinks out on hover. */
.cl-explore__tile--flat:hover {
	box-shadow:
		inset 0 0 0 1px rgb(255 255 255 / 8%),
		0 26px 46px rgb(15 12 9 / 40%),
		0 0 0 2px var(--cl-amber-500);
}

/* Same `transition: none` finding as the photo tile above — without it the
   focus ring fades in and fast tabbing never draws it fully. */
.cl-explore__tile--flat:focus-visible {
	transition: none;
	box-shadow:
		inset 0 0 0 1px rgb(255 255 255 / 8%),
		0 26px 46px rgb(15 12 9 / 40%),
		var(--cl-focus-ring);
}


/* ---- 8f-c. TOP-ALIGNED FLAT TILES — store-memberships.php's four types ----

   The base .cl-explore__body anchors to the tile's BOTTOM (`inset: auto ...`),
   which is correct for a photo caption sitting over a scrim — less text just
   means more photograph shows above it. It is wrong once there is no
   photograph: less text then means the whole body sits lower, so the four
   titles never share a top edge and the body text inherits the drift.
   Michael, 2026-08-12: the four membership cards need both their H2s and
   their body copy to align as rows.

   ⚠️ A MODIFIER, NOT A CHANGE TO THE BASE RULE. The homepage/callouts band
   mixes flat tiles into a row of PHOTO tiles (§8f-b), where bottom-anchoring
   is the correct, already-signed-off behaviour — it is what makes a flat tile
   read as "the same shape without a photo" rather than a different
   component. This only applies where every tile in the row is flat. */
.cl-explore__body--top {
	position: static;
	inset:    auto;
	display:  flex;
	flex-direction: column;
	height:   100%;
	padding:  var(--cl-space-5);
}

/* 🐛 `height: 100%` ON THE BODY DID NOTHING WITHOUT THIS. A percentage height
   only resolves against a parent with a DEFINITE height, and .cl-explore__tile
   had none — it is `min-height` only, sized by its own content, which made the
   body's 100% resolve to auto. Two knock-on bugs from the same cause: the four
   cards in a row were NOT equal height (340 / 351 / 351 / 389px, measured),
   because each tile just wrapped its own content instead of stretching to the
   grid row's height — and the cue's `margin-top: auto` had no leftover space
   to become a real gap, so it sat flush against whatever came before it (0px,
   measured, on every card). This gives the tile the definite height the body
   needs: the grid item (`<li>`) already stretches to the row's tallest card by
   default, so `height: 100%` here is the last link in that chain, not a new
   constraint of its own. */
.cl-explore__tile--top {
	height: 100%;
}

/* Reserves the tallest title's height (measured: 3 lines, "Corporate
   Membership & Directory Listing") so every card's text starts on the same
   row regardless of how many lines its own title happens to wrap to. */
.cl-explore__body--top .cl-explore__title {
	min-height: calc(1.625rem * var(--cl-leading-tight) * 3);
}

/* The cue pins to the card floor, same equal-weight reasoning as
   .cl-partner-card__cta (§10f) — every card's call to action lands on one
   baseline no matter how long its text or requirement note runs.
   ⚠️ `margin-top: auto` ALONE IS NOT A GAP, IT IS LEFTOVER SPACE — AND THE
   TALLEST CARD IN THE ROW HAS NONE. It only distributes whatever room is left
   after the flex column's other children, so on whichever card happens to set
   the row's height (its own content exactly fills it) there is nothing left
   to distribute and the cue lands flush against the text above it — measured
   0px on the industry card, the one with the longest note. The floor below
   guarantees real space there too; this auto margin only adds MORE on top of
   it when a card has room to spare. */
.cl-explore__body--top .cl-explore__cue {
	margin-top: auto;
	align-self: flex-start;
}

/* The membership eligibility line ("*Must represent..."), pulled from each
   detail page's own disclaimer field so it can't drift from the real
   requirement. A footnote, not body copy — smaller and italic to read as a
   condition rather than a benefit.
   The bottom margins on this and on .cl-explore__text just above are the
   real floor the cue's auto margin cannot provide on its own — whichever one
   sits directly above the cue (the note if there is one, the description if
   not) guarantees the button never touches it, even on the card that sets
   the row's height. */
.cl-explore__note {
	display: block;
	margin-top: var(--cl-space-2);
	font-size: 0.75rem;
	font-style: italic;
	color: var(--cl-sand-200);
}
.cl-explore__body--top .cl-explore__text {
	margin-bottom: var(--cl-space-4);
}
.cl-explore__body--top .cl-explore__note {
	margin-top: 0;
	margin-bottom: var(--cl-space-4);
}


/* ---- 8g. ECONOMIC IMPACT ----

   Grass band, centred copy, three stat tiles (§7d) and a light CTA.
   ⚠️ The scrim is derived — tokens.css §5h. The design's ramped version fails
   AA four ways against a bright photograph; this one is flat because every
   horizontal position in this band carries text, which is precisely the case a
   horizontal ramp cannot serve. */

.cl-impact {
	position: relative;
	overflow: clip;
}

/* ⚠️ THE brightness() FILTER WAS REMOVED 2026-08-07 ON MICHAEL'S CALL — he
   prefers the unfiltered grass, and it is his look to choose. Recording what it
   cost, because nothing else will:

   The filter was doing two jobs. It made the band lighter-looking at a given
   scrim alpha, AND it capped how bright this layer could ever be, so the
   contrast solve held for any future replacement image. Both are gone.

   Measured against the grass photo's brightest pixel under the text band
   (rgb(177,203,106), L 0.531) at the current --cl-impact-scrim:

       kicker  amber #fbb042 13px   needs 4.5
       lede    #e6ede9       18px   needs 4.5
       h2      white         49px   needs 3.0   (large text)
       stat figure / label          on the tile's own glass fill

   The current numbers are in MEMORY.md. ⚠️ THE AMBER KICKER AND THE AMBER STAT
   FIGURES ARE WHAT BIND — everything white or cream clears far earlier. If this
   band needs to pass without darkening the photograph again, the cheap move is
   the amber on THIS band, not the scrim: it is the only foreground that needs
   more than the rest, and it is one declaration each. */

/* 🐛 THIS RULE WAS DELETED ENTIRELY WHEN THE FILTER CAME OFF, AND THAT BROKE THE
   WHOLE BAND. The edit that removed `filter:` was a text slice that ran from the
   comment above down to `.cl-impact__scrim`, so it took `.cl-impact__bg` with
   it. Left unstyled, the <img> falls back to an ordinary inline image at its
   natural 1920x1280 — no `position: absolute`, so it sits IN the flow and
   pushes the copy, the stat tiles and the CTA down the page instead of sitting
   behind them. Restored below, minus the filter, which was the only thing that
   was actually meant to go. */
.cl-impact__bg {
	position:   absolute;
	inset:      0;
	width:      100%;
	height:     100%;
	object-fit: cover;
}

.cl-impact__scrim {
	position:   absolute;
	inset:      0;
	background: var(--cl-impact-scrim);
}

/* ⚠️ The heading here takes MORE leading than the other bands, and the design
   does the same thing: its ballparks h2 carries `line-height: .98` and this one
   carries none at all. 0.98 is right for a one- or two-word line and cramped on
   a six-word one that wraps — "THE CACTUS LEAGUE MEANS / BUSINESS." had its
   two lines almost touching. */
.cl-impact .cl-block__heading {
	line-height: 1.12;
	margin-bottom: var(--cl-space-5);
}

.cl-impact__inner {
	position:   relative;
	max-width:  62.5rem;
	margin:     0 auto;
	text-align: center;
}

.cl-impact__lede {
	color:     var(--cl-on-brand);   /* #e6ede9 — 4.5:1+ on the scrim, see §5h */
	margin:    0 auto var(--cl-space-7);
	max-width: 40rem;
}

.cl-impact__stats {
	display:    grid;
	gap:        var(--cl-space-5);
	list-style: none;
	margin:     0 0 var(--cl-space-7);
	padding:    0;
	grid-template-columns: 1fr;
}

@media (min-width: 768px) { .cl-impact__stats { grid-template-columns: repeat(3, 1fr); } }

.cl-impact__stat { text-align: center; }


/* ==========================================================================
9. THE SHARED INTERIOR BANDS — WP-2.10 step 5, batch 0
==========================================================================

The two blocks that close almost every page on the site:

    9a. callouts   — asset-callouts.php,  on 36 pages
    9b. sponsors   — asset-sponsors.php,  on 38 pages

🔑 THESE WENT FIRST FOR THE SAME REASON navigation.php WENT IN STEP 4b. They
   are included by nearly every page in step 5, so reskinning the pages and
   THEN changing these would stale the bottom-of-page review on all of them.

⚠️ THE SPONSOR BAND WAS PULLED FORWARD OUT OF STEP 6, AND THE REASON IS A
   SHARED BACKGROUND, NOT CONVENIENCE. `.complex-gradient` dressed this band
   AND the filter UI on directory / directory-members / dugout / hof — which is
   all of batch 3. Restyling it after batch 3's contrast sweep would have staled
   the numbers on the four riskiest pages in the step.
   📌 Step 6 is NOT closed by this. It remains the question Michael reserved:
   where the three hardcoded homepage ad units live in the 2027 design, as an
   advertising product rather than a port. This is only the existing band.
========================================================================== */


/* ---- 9a. CALLOUTS ----

   Six CMS destinations in two tiers, and ⚠️ THE TIERS ARE THE DATA, NOT A
   DESIGN PREFERENCE. The CMS holds these as two itemIDs — 25 (three "main"
   rows) and 26 (three "sub" rows) — and only itemID 25's rows have artwork:
   68, 831 and 66 have an `img/cms-callouts/{id}_large.jpg`, while 585, 832 and
   70 have no `_large` AND no `_thumb` on disk. Checked file by file, not
   inferred from the template.

   So a uniform photo-tile grid — the obvious thing, since it is what the
   homepage's explore band already is — IS NOT AVAILABLE FROM TODAY'S CONTENT.
   Three of the six would render an empty box. The two tiers below are what the
   data can actually support, and they happen to say the right thing: featured
   destinations carry photography, secondary ones are text.

   📌 This is C-07 in the reskin doc's CMS backlog, seen from the front end.
   Six rows across two itemIDs for a design with four slots is the clearest
   single argument that the callouts CMS wants rebuilding rather than wiring. */

.cl-callouts {
	background: var(--cl-white);
}

/* The featured three REUSE §8f's tile wholesale — .cl-explore__tile and its
   img / scrim / body / title / text / cue children — rather than cloning them.
   🔑 That is not just DRY: 8f's scrim is a DERIVED value (the 0.92 bottom stop
   solves white 26px display type to 3:1 and #eee7db 14px body to 4.5:1 over an
   arbitrary photograph). Cloning the component would have meant either copying
   that solve or quietly re-deriving it, and a second copy is a second thing to
   keep true when the floor moves.
   📌 The name still reads correctly here — this band's own heading has always
   been "Explore the Cactus League". */
.cl-callouts__featured {
	display:    grid;
	gap:        var(--cl-space-4);
	list-style: none;
	margin:     0 0 var(--cl-space-5);
	padding:    0;
	grid-template-columns: 1fr;
}

@media (min-width: 576px) { .cl-callouts__featured { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 992px) { .cl-callouts__featured { grid-template-columns: repeat(3, 1fr); } }

/* The secondary three. .cl-card because the page underneath is white — §7c's
   rule is that the surface is chosen by what is BENEATH, and a .cl-glass here
   would render a 7%-white fill with a 1.05:1 edge, i.e. nothing. */
.cl-callouts__grid {
	display:    grid;
	gap:        var(--cl-space-4);
	list-style: none;
	margin:     0;
	padding:    0;
	grid-template-columns: 1fr;
}

@media (min-width: 576px) { .cl-callouts__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 992px) { .cl-callouts__grid { grid-template-columns: repeat(3, 1fr); } }

/* 🔑 min-width: 0 ON THE GRID CHILDREN, the same trap step 4 hit three times in
   one step: a grid item defaults to `min-width: auto` and will not shrink below
   its own min-content, so one long unbroken title forces the whole track — and
   the element that visibly overflows is usually a SIBLING stretched to match. */
.cl-callouts__featured > *,
.cl-callouts__grid > * { min-width: 0; }

.cl-callouts__card {
	display:        flex;
	flex-direction: column;
	height:         100%;
}

.cl-callouts__title {
	font-family:    var(--cl-font-display);
	font-weight:    var(--cl-weight-bold);
	font-size:      1.375rem;   /* 22px — a step under 8f's 26px featured tile */
	line-height:    var(--cl-leading-tight);
	text-transform: uppercase;
	color:          var(--cl-text-brand);
	margin:         0 0 var(--cl-space-2);
}

.cl-callouts__text {
	/* CMS free text (Items.info2) — an editor can type an unbreakable token into
	   it at any time, and a card is the narrowest column on the page. */
	overflow-wrap: break-word;
	font-size:     var(--cl-text-ui);
	line-height:   var(--cl-leading-body);
	color:         var(--cl-text-secondary);
	margin:        0 0 var(--cl-space-4);
}

/* Pushes the link to the card's bottom edge so three cards of unequal copy
   still align their calls to action. */
.cl-callouts__link {
	margin-top:      auto;
	align-self:      start;
	/* 🐛 THIS BLOCK EXISTS BECAUSE THE LINK MEASURED 22px TALL — ONE PIXEL UNDER
	   WCAG 2.2 SC 2.5.8's 24px FLOOR, at every one of the nine widths swept.
	   14px text on a 1.5-ish line box lands at 22, and nothing about the design
	   makes that visible. There IS a plausible exemption — SC 2.5.8's Spacing
	   exception, since these sit one per card with far more than 24px between
	   them — but leaning on an exemption to keep a 2px shortfall is how a floor
	   stops being one, and the fix costs nothing visually.
	   inline-flex + min-height rather than padding: padding would have grown the
	   box downward past the card's bottom edge and knocked the three cards'
	   CTAs out of the alignment `margin-top: auto` exists to create. */
	display:         inline-flex;
	align-items:     center;
	min-height:      1.5rem;   /* 24px */
	font-size:       var(--cl-text-ui);
	font-weight:     var(--cl-weight-extrabold);
	letter-spacing:  0.04em;
	text-transform:  uppercase;
	text-decoration: none;
	/* ⚠️ brick-700 (8.33:1 on white), NOT brick-500 (5.94:1). Both pass AA as
	   body text, but this is 14px uppercase and it is the only coloured thing on
	   an otherwise quiet card — and -700 is already the package's answer for
	   brick that has to survive a non-white surface. Keeping one brick for links
	   means a card that later gains a sand or amber fill does not need a second. */
	color:           var(--cl-brick-700);
}

.cl-callouts__link:hover,
.cl-callouts__link:focus-visible {
	color:           var(--cl-brick-500);
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

/* The chevron. Font Awesome renders through a pseudo-element, so this <i> is
   genuinely empty content — aria-hidden at the call site, and the link's own
   text carries the accessible name. */
.cl-callouts__link .fa-solid {
	margin-left: var(--cl-space-2);
	transition:  transform var(--cl-duration-fast) var(--cl-ease);
}

.cl-callouts__link:hover .fa-solid { transform: translateX(3px); }


/* ---- 9b. SPONSORS ----

   ⚠️ SCOPE: this restyles the EXISTING band. It does not answer step 6.

   🔑 EVERY LOGO SITS ON A WHITE CHIP, AND THAT IS THE THIRD TIME THIS PACKAGE
   HAS REACHED THE SAME CONCLUSION ABOUT THE SAME KIND OF ARTWORK. The footer
   team chips found it first ("dark artwork on #141414 all but disappears"), the
   subnav found it again when eight of fifteen team marks measured below 3:1 on
   the design's green. Here the mechanism is different but the fix is identical:
   these are `_large.jpg` files, and JPEG HAS NO ALPHA — so every logo arrives
   with an opaque background of its own, chosen by whoever supplied it and not
   by us. On a tinted band that background paints as a visible rectangle.

   A white chip is correct whichever way a given logo goes: it disappears behind
   a white-background logo, and it frames a coloured one deliberately rather
   than letting it collide with the band. ⚠️ Do not "simplify" this away by
   putting the logos straight on the band — it only looks redundant for as long
   as every supplied file happens to be white-backed, and that is a property of
   today's uploads, not of the component. */

.cl-sponsors {
	background: var(--cl-surface-field);
}

.cl-sponsors__head {
	text-align:    center;
	margin-bottom: var(--cl-space-6);
}

/* 🔑 THE LEVEL IS h2 AND THE STYLING RIDES ON THIS CLASS — BOTH DELIBERATE, AND
   THE CLASS IS WP-2.7's SCAR. The only rule dressing this heading used to be
   `#sponsors-new h4`, an ELEMENT selector, so promoting the tag to fix a
   heading-order violation silently dropped every declaration and left #232323
   on #000 at 1.33:1 with nothing erroring. That is the exact trap §7 of the
   reskin doc warns step 5 about — "check what styles an element before you
   change its tag" — and it is already defused here.
   ⚠️ Keep level and appearance decoupled. Never reintroduce `#sponsors-new h2`. */
#sponsors-new .sponsor-title,
.cl-sponsors__title {
	font-family:    var(--cl-font-display);
	font-weight:    var(--cl-weight-bold);
	font-size:      var(--cl-text-h4);
	line-height:    var(--cl-leading-tight);
	text-transform: uppercase;
	letter-spacing: 0.02em;
	/* 10.4:1 on --cl-surface-field (#e1ded5). The old rule painted this white on
	   the near-white end of .complex-gradient and relied on the gradient's dark
	   top to carry it — which is why it had to be white AND italic AND clamped. */
	color:          var(--cl-text-brand);
	margin:         0;
}

.cl-sponsors__grid {
	/* CENTERED FLEX-WRAP, NOT A GRID — and the reason is the same last-row problem
	   the grid was fighting, solved from the other side. The row is CMS-driven and
	   its length changes every season, so incomplete rows are the norm, not the
	   exception: 12 sponsors wrap to a 4-orphan row, the small tier holds exactly
	   one. A `grid` (auto-fill or auto-fit) can only LEFT-ALIGN or STRETCH those
	   orphans — auto-fit stretched the lone small chip to 1300px, auto-fill left it
	   dangling at the left margin. Neither reads as intentional on a partner wall.
	   🔑 flex-wrap + justify-content:center CENTERS every row, so an incomplete row
	   sits balanced under the full ones and a single sponsor reads as a deliberate
	   centered chip rather than a break. The fixed-basis, no-grow chip below keeps
	   the invariant the grid comment cared about — one sponsor renders at one
	   sponsor's width, never stretched to fill the row. */
	display:         flex;
	flex-wrap:       wrap;
	justify-content: center;
	gap:             var(--cl-space-4);
	list-style:      none;
	margin:          0;
	padding:         0;
}

/* 🎨 THE CHIPS ARE SIZED FOR CLEAN LOGOS — Michael, 2026-08-19: "make the Payson,
   Arizona and Legends logos your assumed standard… assume all sponsors will have
   only logos soon and bring them all up in size a bit more." Alena is collecting a
   clean logo (no busy banner crops) for every sponsor — see the to-do in
   prompts/SPONSOR-IMPRESSIONS-SHIFT.md. So the band is tuned for a wordmark or
   mark that sits comfortably in the box, NOT for shrunk banner artwork, and the
   whole wall is a step larger than the first pass now that we don't have to keep
   cluttered crops small.

   The chip's footprint lives on the <li>: fixed basis, no grow, allowed to shrink
   on very narrow screens so a chip never overflows the gutter. 11.5rem gives a
   horizontal wordmark (Payson, Arizona) room to breathe while still fitting a
   comfortable multi-column wall.

   ⚠️ ONE SIZE FOR EVERY SPONSOR — Michael, 2026-08-19: "make all of them the same
   size… we should get away from" the large/small tier split. So the --sm grid is
   NO LONGER a smaller chip: it inherits this same basis, and both tiers read as one
   uniform wall. The two CMS lists (itemID 20 and 62) still exist and still render as
   two <ul>s, but that is now an ordering/grouping detail, not a visual hierarchy.
   The inter-grid gap below is set equal to the intra-grid row gap so the seam is
   invisible and the whole thing reads as a single continuous wall. */
.cl-sponsors__grid > li { flex: 0 1 11.5rem; }

/* Phones: exactly two chips per row — the 11.5rem basis falls to one column on a
   narrow screen and the wall scrolls too long (Michael, 2026-09-03). Basis is
   half the row minus half the gap, so two chips + one gap == 100%; keeping
   `flex-grow: 0` leaves a lone final chip centred at half width rather than
   stretched, same reasoning as the base rule. */
@media (max-width: 575px) {
	.cl-sponsors__grid > li { flex-basis: calc(50% - (var(--cl-space-4) / 2)); }
}

.cl-sponsors__grid + .cl-sponsors__grid { margin-top: var(--cl-space-4); }

.cl-sponsors__chip {
	/* width:100% so the chip fills the fixed-basis <li> — the footprint is set on
	   the list item (see .cl-sponsors__grid > li), the chip just paints it. */
	width:           100%;
	display:         flex;
	align-items:     center;
	justify-content: center;
	/* Comfortably past WCAG 2.2 SC 2.5.8's 24px floor — these are links, and the
	   footer team grid is the cautionary tale: fifteen chips in a 224px row came
	   out at 18px each because the inline padding had been honoured at 320px.
	   5.5rem as of the 2026-08-19 clean-logo pass: an impressions unit reads as
	   intentional when the chips have real presence, and a taller box lets a clean
	   wordmark sit large and centered with even air around it.
	   Padding trimmed space-4→space-3 the same day (Michael: "reducing the padding,
	   increasing the logo sizes… assuming we'll get clean logos means we can bring
	   them up a bit more") so the logo, not the whitespace, fills the chip.

	   ⚠️ FIXED height, not min-height (Michael, 2026-09-04: "all rectangles need to
	   be the same size, width and height"). The <li> already fixes the WIDTH
	   (flex: 0 1 11.5rem); pinning the height here makes every chip an identical
	   rectangle regardless of its logo's aspect ratio — a min-height let a tall or
	   heavily-padded logo grow its chip past its neighbours, so the wall stopped
	   reading as a uniform grid. Bumped 5.5→6.5rem the same day: with the logo now
	   filling the box (see the img rule) a little more height gives every mark
	   presence without letting it touch the padded edges. */
	height:          6.5rem;
	padding:         var(--cl-space-3);
	background:      var(--cl-white);
	border:          1px solid var(--cl-card-hairline);
	border-radius:   var(--cl-radius-md);
	transition:
		transform  var(--cl-duration-fast) var(--cl-ease),
		box-shadow var(--cl-duration-fast) var(--cl-ease);
}

.cl-sponsors__chip:hover {
	transform:  translateY(-2px);
	box-shadow: var(--cl-card-lift-hover);
}

/* Same load-bearing `transition: none` as §7c's tiles and §8f's: this element
   transitions box-shadow, so Chrome expands the ring's `none` start frame into
   transparent zero-size layers and the indicator FADES IN — which means tabbing
   quickly along a row of sponsors never fully draws it. */
.cl-sponsors__chip:focus-visible {
	transition: none;
	box-shadow: var(--cl-focus-ring);
}

.cl-sponsors__chip img {
	/* MAXIMISE THE LOGO IN ITS RECTANGLE (Michael, 2026-09-04: "maximize the logos
	   in the rectangles so they are larger without touching the edges"). The chip
	   is a fixed WxH box (see above) and the logos are not one fixed aspect ratio,
	   so the artwork is fitted to the box, not the box to the artwork. Filling the
	   full padded content box — width AND height 100% with object-fit: contain —
	   scales every logo up until it hits the box on its long axis, so a wide
	   wordmark grows to the chip's width and a squarer mark to its height; each
	   ends up as large as its own shape allows. The chip's space-3 padding is the
	   breathing room that keeps a maximised logo off the edges. This replaces the
	   old flat 4.5rem max-height cap, which held every logo short of the box no
	   matter how much room it had. */
	width:      100%;
	height:     100%;
	object-fit: contain;
}


/* ==========================================================================
10. THE INTERIOR PAGE — WP-2.10 step 5, batch 1

    10a. .cl-page-head — the <h1> + intro block
    10b. the interior gutter
    10c. .cl-seam — the section divider that replaces $logoDividerNew

🔑 THIS SECTION IS THE INTERIOR LANGUAGE THE OTHER 38 PAGES INHERIT. §7a of the
   reskin doc designates about.php as the page that sets it, and the reason it
   goes first in batch 1 is leverage rather than difficulty: a page header, a
   gutter and a section divider are the three things every one of those pages
   has, so getting them right here is most of the step.

⚠️ THE DESIGN HAS NO INTERIOR PAGE. `design/2027-look/` contains exactly one
   mockup — the homepage — and `Cactus League Site.dc.html` is not a second one
   (its heading inventory is identical, compared file to file). So everything
   below is DERIVED from §7/§8 rather than measured against an artifact, which
   is why §7a's cadence requires each new pattern to be shown as a one-page
   sample before it propagates. Nothing here has been applied to a second page.
========================================================================== */


/* --------------------------------------------------------------------------
10a. THE PAGE HEAD — .cl-page-head

Michael, 2026-08-11: *"The H1s and little intro's… should sit left-aligned with
the 'WELCOME TO THE CACTUS LEAGUE' header element and up on the page a bit. To
the right of the bottom half of the logo."*

Two instructions, and they solve each other. The horizontal one moves the head
right past the badge; the vertical one moves it up INTO the badge's overhang. Doing
only the second would put the badge on top of the <h1> — which is the exact
thing `.cl-header`'s margin-bottom exists to prevent, and why the reserve could
never simply be deleted. Doing both is safe by construction: the head starts
28–32px to the right of where the artwork ends, so the badge overlaps only
padding.

🔑 THE RESERVE IS CANCELLED AT ITS SOURCE, NOT NEGATED AT THE HEAD. A negative
   top margin on .cl-page-head would have to re-derive --cl-header-overhang from
   three tokens that change at four breakpoints, and it is the same argument
   §8a already made for the homepage hero from the other direction. One
   declaration on the header, no duplicated arithmetic, correct at every width.
   📌 The hook is a class on <body>, mirroring `.cl-page-home`: it is the only
   element above both <header> and <main> that a page file controls, because
   `.cl-site-wrapper` is opened inside the shared navigation.php.

⚠️ IT IS OPT-IN, AND THAT IS WHAT MAKES THE BATCH SAFE. 33 of the 39 pages in
   step 5 are still in the old language and will be for several batches. A rule
   keyed off `.cl-page-interior` reaches only the pages that have actually been
   built and reviewed; the same rule written against `body` would restyle every
   unreviewed page in the step the moment it landed.
-------------------------------------------------------------------------- */

/* 🎨 IT IS A GRASS BAND AS OF 2026-08-12, AND THAT IS WHAT FIXED "ODDLY PLACED".
   Michael: *"Right now it looks oddly placed and kind of out of place and a bit
   unrefined… I want the page-head section to have the cl-impact__bg background
   image [and the h1 and description to] look and feel more like the economic
   impact section."*

   🔑 THE DIAGNOSIS UNDERNEATH THE INSTRUCTION IS THAT THE BADGE HAD NOTHING TO
   SIT ON. A 200px medallion with a drop-shadow, hanging over plain white, reads
   as a sticker somebody left on the page — it works on the homepage precisely
   because there is a photograph beneath it. Giving the head a surface puts the
   badge ON something, and it is the same move the hero already makes. That is
   why this reads as finished and the white version did not: nothing about the
   type changed.

   ⚠️ SO THE BAND HAS TO START AT THE BAR'S BOTTOM EDGE, NOT BELOW THE BADGE —
   otherwise the badge still hangs over white and the band is just a stripe
   underneath it. The header's reserve is therefore cancelled at EVERY width now,
   including the phone, where it previously stayed; the head supplies its own
   top padding instead. See the media query below.
------------------------------------------------------------------------- */
.cl-page-head {
	/* The photograph and the wash are absolutely positioned children, so this is
	   their containing block. `clip`, not `hidden`, per tokens.css §7b.
	   ⚠️ The overhanging badge is NOT clipped by this — it lives in <header>,
	   which is a sibling, so it is not a descendant of this box. */
	position: relative;
	overflow: clip;

	/* --cl-lead-inset is DERIVED ON .cl-site-wrapper — see the 🔑 at the top of
	   §2. It is not a copy of the welcome line's measured position; it is the
	   same three terms the header's flex row resolves, so the two edges cannot
	   drift apart when a breakpoint moves one of them.
	   The end padding is the ordinary page gutter, and Michael's 2026-08-12
	   instruction depends on it: *"the description should run all the way to the
	   right content margin."* It is the margin the content below the band uses,
	   so the two line up on the right even though they do not on the left. */
	padding-inline: var(--cl-lead-inset) var(--cl-block-pad-x);
	padding-block:  var(--cl-page-head-pad-t) var(--cl-page-head-pad-b);

	/* 🐛 THE GUARANTEE THAT THE BADGE ALWAYS HAS BAND BENEATH IT, AND IT IS NOT
	   DECORATIVE SPACING. With the reserve cancelled, the badge hangs
	   --cl-header-overhang below the bar and this element is what occupies that
	   space. A head that happened to be SHORTER than the overhang — an <h1> with
	   no intro paragraph, which several of the 38 pages have — would end above
	   the badge's bottom edge, so the artwork would cross the band's lower edge
	   and hang onto the white section below it. Before the band existed the same
	   defect was worse and quieter: the badge overlapped the first content
	   section, whose inset is 48px against a badge right edge of 232px — 184px
	   of artwork over body copy, with nothing erroring.
	   ⚠️ THE + 4rem IS THE REFINEMENT, not padding. At exactly the overhang the
	   badge's bottom edge and the band's are the same line, which reads as the
	   mark being cropped by the band rather than resting on it. 4rem is enough
	   grass under the circle to look deliberate at all three desktop steps.
	   Reading the token rather than hardcoding a height is what makes it hold at
	   every breakpoint and survive a change to the badge size. */
	min-height: calc(var(--cl-header-overhang) + var(--cl-space-8));
}

/* The photograph. Same file and same treatment as §8g's — five positional
   declarations and no more.
   📌 DELIBERATELY NOT `.cl-impact__bg` REUSED, and this is the one place in §10
   that clones rather than reuses. The rule is trivial and identical; the
   COUPLING is not. Every interior page would then depend on a homepage band's
   class, so a future tune to the impact band — which is step 6/7 work and
   already carries known debt — would silently restyle 38 pages that nobody was
   looking at. The scrim below cannot be shared at all (different alpha, see
   tokens.css §5i), so half the pair was always going to be local.
   ⚠️ NO `loading="lazy"` IN THE MARKUP, unlike the homepage's. That band is far
   below the fold; this one IS the fold, and lazy-loading the first thing painted
   gives every interior page a flash of bare scrim over white — pale sage, which
   MEMORY.md records being mistaken for a missing-image bug once already. */
.cl-page-head__bg {
	position:   absolute;
	inset:      0;
	width:      100%;
	height:     100%;
	object-fit: cover;
}

.cl-page-head__scrim {
	position:   absolute;
	inset:      0;
	background: var(--cl-page-head-scrim);
}

/* The copy sits above both layers. Without this the text is painted UNDER the
   scrim — it still shows through, veiled, at whatever the alpha is, which looks
   like a colour mistake rather than a stacking one. */
.cl-page-head__inner {
	position: relative;
}

/* ⚠️ MORE LEADING THAN THE HOMEPAGE BANDS, AND §8g ALREADY MADE THIS EXACT CALL
   for the same reason: 0.98 is right for a one- or two-word line and cramped on
   anything that wraps. Interior <h1>s come from the CMS and are sentences —
   "Take me out to the ballgame – and the mountains, golf courses and pools" is
   a real one on this page — so wrapping is the normal case here, not the edge. */
.cl-page-head .cl-block__heading {
	line-height:   1.12;
	margin-bottom: var(--cl-space-5);
}

/* The intro. `.cl-block__lede` supplies the type and this supplies the two
   things that have to change on a photograph.

   1. ⚠️ `max-width: none` — Michael, 2026-08-12: *"`.cl-block__lede` limits the
      width, I want it 100% inside the area."* The 34em cap is right on the
      homepage, where the lede is one sentence introducing a band; here it is the
      page's own standfirst and the band is only as tall as its copy, so a capped
      measure left a column of empty grass to the right of every interior page.

   2. 🐛 THE COLOUR MUST BE SET ON THE PARAGRAPHS, NOT ONLY ON THE WRAPPER, AND
      THIS IS THE THIRD TIME THIS BUG HAS APPEARED IN WP-2.10. styles-custom.css
      has `:is(body, p, a) { color: var(--cl-text) }` — an element selector that
      matches every <p> on the site — and this lede is a WRAPPER around nl2p()
      output, so its children are <p>s that match that rule and would render
      near-black on the grass while the wrapper's own colour applied to nothing.
      It is not inheritance failing; inheritance only fills where nothing else
      matches. Same finding as the footer signup paragraph (WP-2.6, #232323 on
      #141414 at 1.06:1) and the header welcome line (§2). */
.cl-page-head__lede,
.cl-page-head__lede p {
	color: var(--cl-on-brand);   /* 3.80:1 on the scrim — see tokens.css §5i */
}

.cl-page-head__lede {
	max-width: none;
	margin:    0;

	/* Heavier than the homepage lede — Michael, 2026-08-12. ⚠️ 600, NOT 500:
	   montserrat ships 200/300/400/600/700/800/900 and has no 500, so a 500
	   resolves DOWN to 400 and the declaration would quietly say something
	   untrue. §8a records the same finding for --cl-block__lede.
	   📌 IT DOES NOT MOVE THE WCAG THRESHOLD, and it is worth being precise
	   about why: "large scale" is 18pt (24px) at any weight, or 14pt (18.66px)
	   BOLD — and bold means >= 700. At 18px/600 this is still small text and
	   still owes 4.5. What the weight buys is real legibility over a
	   photograph — thicker strokes hold up against texture — not a lower bar. */
	font-weight: var(--cl-weight-semibold);
}

.cl-page-head__lede > :last-child { margin-bottom: 0; }

/* ⚠️ THE GLASS PANEL LIGHTENS THIS SURFACE, WHICH IS THE OPPOSITE OF WHAT THE
   NAME SUGGESTS AND IT COSTS CONTRAST. Michael asked for `.cl-glass` on the
   intro, 2026-08-12, and it is built — but --cl-glass-fill is
   `rgb(255 255 255 / 7%)`, a WHITE lift designed to raise a panel off a dark
   surface, so it makes the ground under this text brighter rather than dimmer.
   Measured over the 0.52 band: the intro goes 3.80 → 3.33, and the CEILING for
   any text colour at all drops 4.52 → 3.96. With glass behind it, pure white
   cannot reach 4.5.
   📌 A two-line change makes the same panel help instead: a dark fill —
   `rgb(0 0 0 / 12%)` — reads as the same recessed panel and takes the intro the
   other way. Recorded rather than done, because the visual call is Michael's.
   The panel edge, not the text inside it, is what lines up with the <h1>; the
   text is inset by --cl-card-pad, which reads as the panel being an object. */
.cl-page-head__lede.cl-glass {
	/* The lede is a wrapper of <p>s, so the panel needs a little more room than
	   --cl-card-pad gives a stat tile. */
	padding: var(--cl-card-pad) var(--cl-space-6);
}

/* 🎨 THE DUGOUT POST STANDFIRST PANEL — Michael, 2026-08-14: "make the scrim
   background black instead of white at 0.4 opacity. Don't change anything else
   but the background. I like the border." So ONLY the fill flips — from the
   white .cl-glass lift to black 40% — and the hairline border, padding and blur
   stay. This is the documented dark-fill safeguard (see the ⚠️ above and §10a
   --photo note) made the default here: over the deliberately light --photo scrim
   a recessed dark panel is what carries the standfirst's contrast, which is why
   the standfirst reads even when the photo behind it is bright.
   🔑 SCOPED TO .cl-page-head--photo — the ONLY page-head that is a photo — so
   hof.php's grass-band glass panel (white lift, by design) is untouched. */
.cl-page-head--photo .cl-page-head__lede.cl-glass {
	background: rgb(0 0 0 / 0.4);
}

/* 🎨 THE HOF LOGO, INSIDE THE SAME PANEL — Michael, 2026-08-13, ahead of
   being asked: "place the HOF logo inside the same box as the intro text,
   right side, adjust size and width to make it look good and balanced."
   hof.php only — the modifier stays off `.cl-page-head__lede` itself so
   every other interior page's plain text lede is untouched.
   130px is a little over a third of the panel's own copy measure at the
   width this text runs (~34-40ch), which is what "balanced" meant to solve:
   big enough to read as a real mark next to a paragraph of body copy, not so
   big it out-weighs the text it is sitting beside. */
/* align-items: flex-start (not center) — Michael, 2026-08-13: "align start
   (top) with the paragraph intro text." */
.cl-page-head__lede--with-logo {
	display: flex;
	align-items: flex-start;
	gap: var(--cl-space-6);
}
.cl-page-head__lede-text {
	flex: 1;
	min-width: 0;
}
.cl-page-head__lede-logo {
	flex-shrink: 0;
	width: 130px;
	height: auto;
}
/* 🎨 225px on large screens — Michael, 2026-08-13: "increase size on big
   screens... these faces are a bit too big at 4 per row" was the HOF-card
   note, this is the separate logo-size note from the same round. 992px
   matches where the rest of this page's own layout starts treating itself
   as "desktop" (the filter grid's 3-column step). Below it, 130px stays the
   tablet size — there just isn't the width to spare at 225px yet. */
@media (min-width: 992px) {
	.cl-page-head__lede-logo {
		width: 225px;
	}
}
/* Side-by-side has no room below the panel's own single-column breakpoint
   (§10a, 575.98px) — stacked instead, rather than shrinking the mark until
   it stops reading as a seal. Text stays left-aligned, per §10e's "nothing on
   the site is centred"; only the logo itself centres under it. */
@media (max-width: 575.98px) {
	/* ⚠️ THE COLUMN GAP IS OVERRIDDEN, not just the logo's margin — Michael,
	   2026-08-14: "too much space between the intro and the hof logo." The base
	   row rule sets `gap: var(--cl-space-6)` (32px); left alone it becomes the
	   VERTICAL gap here and dwarfs any margin tweak. It comes down to space-4,
	   the logo's own margin goes to 0 (the gap owns the spacing now), and the
	   intro's trailing paragraph margin is zeroed too — the logo is the lede's
	   last child, so §10a's `> :last-child` never reached that <p>. */
	.cl-page-head__lede--with-logo {
		flex-direction: column;
		align-items: flex-start;
		gap: var(--cl-space-4);
	}
	.cl-page-head__lede-text > :last-child { margin-bottom: 0; }
	/* Centred (already) and a little bigger on the phone. */
	.cl-page-head__lede-logo {
		width: 120px;
		align-self: center;
		margin-top: 0;
	}
}

/* ---- SCHEDULE "PRESENTED BY" — the right column of schedule.php's page-head
   lede (2026-09-04). Its OWN component, deliberately NOT .cl-map-presenter: map.php
   owns that byte-for-byte, so the larger card and centred eyebrow here must not
   ride on it. "Presented by" sits centred ABOVE the card; the card's equal padding
   on all four sides gives the (whitespace-trimmed) Payson logo even margins. */
.cl-sched-presenter {
	flex-shrink: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--cl-space-3);
}
.cl-sched-presenter__eyebrow {
	font-size: var(--cl-text-xs);
	font-weight: var(--cl-weight-semibold);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	text-align: center;
	opacity: 0.85;   /* inherits the lede's light text over the grass */
}
/* The white card — same white-surface-under-dark-artwork idiom as the map/footer
   logos. inline-flex + equal padding wraps the logo snugly with an even border. */
.cl-sched-presenter__link {
	display: inline-flex;
	padding: var(--cl-space-4);
	background: var(--cl-white);
	border-radius: var(--cl-radius-md);
	box-shadow: var(--cl-shadow-1);
	transition: transform var(--cl-duration-fast) var(--cl-ease),
	            box-shadow var(--cl-duration-fast) var(--cl-ease);
}
.cl-sched-presenter__link:hover  { transform: translateY(-2px); box-shadow: var(--cl-shadow-2); }
.cl-sched-presenter__link:focus-visible { outline: none; box-shadow: var(--cl-focus-ring); }
.cl-sched-presenter__logo {
	display: block;
	height: 100px;      /* Michael, 2026-09-04: 150 was too big; 100px */
	width: auto;
	max-width: 100%;
}
@media (max-width: 575.98px) {
	/* Lede stacks (§10a); centre the presenter under the intro like the hof logo. */
	.cl-page-head__lede--with-logo .cl-sched-presenter { align-self: center; }
	.cl-sched-presenter__logo { height: 88px; }
}

/* 🎨 TEAM.PHP GRASS PANEL — logo LEFT, copy RIGHT (Michael, 2026-08-19: "switch
   the logo and description text… logo left column, description and link right
   column"). The mirror of hof.php's --with-logo (which keeps its seal on the
   right), so it is its own modifier rather than an override of the shared one.

   🔑 THE LOGO SITS IN A FIXED SQUARE so every team's mark carries EQUAL VISUAL
   WEIGHT regardless of aspect ratio — a wide wordmark and a tall monogram both
   occupy the same 200×200 footprint, contained and centred, height capped at
   200px (Michael: "the tall logos are too big… cap the height at 200px max…
   place them in a square area"). object-fit + max constraints letterbox the art
   inside the box; the SVG marks scale crisply up or down to fill it. */
.cl-page-head__lede--team {
	display:     flex;
	align-items: center;
	gap:         var(--cl-space-6);
}
.cl-page-head__team-logo {
	flex-shrink:     0;
	width:           200px;
	height:          200px;
	display:         flex;
	align-items:     center;
	justify-content: center;
}
.cl-page-head__team-logo img {
	display:    block;
	max-width:  100%;
	max-height: 100%;
	width:      100%;
	height:     100%;
	object-fit: contain;
}
/* Stacks below the panel's own single-column breakpoint (§10a, 575.98px), logo
   on top. Left-aligned, not centred — §10e's "nothing on the site is centred" —
   and the square comes down to 140px where there isn't room for 200. */
@media (max-width: 575.98px) {
	.cl-page-head__lede--team {
		flex-direction: column;
		align-items:    flex-start;
		gap:            var(--cl-space-4);
	}
	.cl-page-head__team-logo {
		width:  140px;
		height: 140px;
	}
	.cl-page-head__lede--team .cl-page-head__lede-text > :last-child {
		margin-bottom: 0;
	}
}

/* 🎨 LEDE LOGO CHIPS, SAME PANEL, RIGHT SIDE — a shared component. Introduced
   on stadium.php (Michael 2026-08-18: "Add the Team logos into cl-glass area.
   Align logo(s) right, keep text left."), then reused on team.php for the
   home-ballpark chip. So the class is `--with-chips`, not `--with-teams`: the
   chip holds whichever related entity the page points AT (a stadium page shows
   its resident teams, a team page shows its home ballpark). Same shape as
   --with-logo just above (text flex:1, mark flex-shrink:0 trailing) but the
   mark is one or two CMS logos, each a real link — so it needs its own hit
   target and a surface a dark logo can sit on, not just an <img>.

   🔑 THE WHITE CHIP IS .cl-footer__team's CONVENTION, NOT A NEW ONE (§3). A
   CMS logo is dark artwork on a transparent PNG; on the footer's black panel
   and on this grass photo alike, a rounded-square white surface is what gives
   it somewhere to sit. Sized down from the footer's 52px chip because this
   panel only ever shows one or two logos, not fifteen in a grid.
   .cl-page-head__lede-text is the same class --with-logo already defines
   above — reused as-is, not redeclared. */
.cl-page-head__lede--with-chips {
	display: flex;
	align-items: center;
	gap: var(--cl-space-5);
}
.cl-page-head__lede-chips {
	display: flex;
	flex-shrink: 0;
	list-style: none;
	margin: 0;
	padding: 0;
	gap: var(--cl-space-3);
}
/* 🐛 THE CHIP SAT 8px HIGH IN THE PANEL, AND THE <li> WAS THE CAUSE — Michael,
   2026-08-18: "the logo in the grass area still has more padding below than
   above." Measured on the render: the panel padding was symmetric (24/24) and
   the panel centred its children, but each <li> carried Bootstrap reboot's
   `margin-bottom: 1rem`, so the flex row's one item was 64px of chip + 16px of
   phantom margin = an 80px item with the chip pinned to its top. The `margin:0`
   above zeros the UL, not its <li>; this zeros the item so the chip is 64px
   again and centres flush. (Same trap the footer's grid sidesteps by never
   giving its <li> a cross-axis margin.) */
.cl-page-head__lede-chips li {
	margin: 0;
	display: flex;
}
/* 🐛 SPACE BELOW THE PANEL TEXT DIDN'T MATCH THE SPACE ABOVE IT — Michael,
   2026-08-18: "make the space below the stadium logo the same as above... the
   margin on the <p> of the stadium name may be to blame." Exactly right: the
   panel's own `> :last-child { margin-bottom: 0 }` (§10a) reaches only DIRECT
   children of .cl-page-head__lede, and the tagline <p> is nested one level
   deeper inside .cl-page-head__lede-text — so its trailing margin survived and
   pushed the text off-centre against the vertically-centred team chips. Zeroing
   it here lets align-items:center do its job symmetrically. Scoped to the
   with-teams variant so hof.php's approved --with-logo panel is untouched. */
.cl-page-head__lede--with-chips .cl-page-head__lede-text > :last-child {
	margin-bottom: 0;
}
.cl-page-head__lede-chip {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 64px;
	height: 64px;
	border-radius: var(--cl-radius-md);
	background: var(--cl-white);
	transition: transform var(--cl-duration-fast) var(--cl-ease),
	            box-shadow var(--cl-duration-fast) var(--cl-ease);
}
.cl-page-head__lede-chip img {
	width: 46px;
	height: 46px;
	object-fit: contain;
}
.cl-page-head__lede-chip:hover {
	transform: translateY(-2px);
	box-shadow: 0 4px 12px rgb(0 0 0 / 30%);
}
/* transition: none for the same two reasons as .cl-footer__team:focus-visible
   (§3) — a focus ring must not fade in, and a transitioned box-shadow built
   from var() does not pick up a var() change in Chrome. */
.cl-page-head__lede-chip:focus-visible {
	transition: none;
	box-shadow: var(--cl-focus-ring);
}
/* Same single-column collapse as --with-logo, same breakpoint (§10a). */
@media (max-width: 575.98px) {
	.cl-page-head__lede--with-chips {
		flex-direction: column;
		align-items: flex-start;
		gap: var(--cl-space-4);
	}
	.cl-page-head__lede-chips { align-self: flex-start; }
}

/* The eyebrow — Michael, 2026-08-12: *"a little orange eyebrow above the h1."*

   --cl-amber-500, the same token the homepage band's kicker uses, so the two
   read as one treatment. ⚠️ IT MEASURES 2.45 ON THIS SCRIM AND OWES 4.5, exactly
   as the homepage's does — that element is one of that band's eight known
   failures and this inherits the problem rather than solving it.
   🔑 NO COLOUR CLOSES IT. On this surface pure white measures 4.52, so the whole
   palette is inside 2 points of the requirement and the eyebrow is 2 points
   below that. `--cl-orange-500` — the literal reading of "orange" — is 1.34.
   The fix is a darker surface under this element, not a different foreground:
   amber-500 inside a `rgb(0 0 0 / 45%)` chip measures 5.53. That is a design
   change and it is Michael's call; see the ⚠️ in tokens.css §5i. */
.cl-page-head__kicker {
	color: var(--cl-amber-500);
}

/* --------------------------------------------------------------------------
THE PHOTO-BACKED PAGE HEAD — .cl-page-head--photo (+ __tag, __credit)

WP-2.10 step 5, dugout-post.php. Every other interior page's band is the ONE
controlled grass texture; a Dugout post puts its own CMS hero photo in the same
slot. Three reusable additions — a listing/detail page with a hero photo can
opt into any of them without new CSS.

⚠️ A LIGHT SCRIM, MICHAEL'S CALL (2026-08-14): "I turned it off completely and
   liked it — reduce it a lot." So this is a near-open photo with only a whisper
   of brand green weighted to the bottom, where the standfirst glass panel sits.
   📌 THE TRADE-OFF IS REAL AND WORTH NAMING: at this strength the copy's legibility
   rides on the PHOTO staying dark enough, which the Sierra Vista mountains do but
   an arbitrary CMS hero (a bright sky, a white building) will not always. The
   safeguard if a future post's hero washes the text out is to switch THAT panel's
   fill from the white .cl-glass lift to the documented dark fill (rgb(0 0 0/12%),
   §10a) so the text box carries its own contrast independent of the scrim —
   rather than darkening this wash back up and losing the open look.
-------------------------------------------------------------------------- */
.cl-page-head--photo .cl-page-head__scrim {
	background: linear-gradient(
		180deg,
		rgb(20 44 32 / 6%) 0%,
		rgb(15 33 24 / 22%) 100%
	);
}

/* The category pill. White on the dark scrim (not the amber kicker or the green
   .cl-hof-team chip, both of which are tuned for other grounds) — a translucent
   white outline reads as a tag laid on the photo rather than a solid label
   floating over it. */
.cl-page-head__tag {
	display:        inline-block;
	margin-bottom:  var(--cl-space-4);
	padding:        0.3em 0.85em;
	font-size:      var(--cl-text-sm);
	font-weight:    var(--cl-weight-semibold);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	line-height:    1.2;
	color:          var(--cl-white);
	background:     rgb(255 255 255 / 14%);
	border:         1px solid rgb(255 255 255 / 40%);
	border-radius:  var(--cl-radius-pill);
}

/* The photo credit (CMS info8), pinned to the band's bottom-right like the old
   .dugout-photo-credit. It is NOT inside .cl-page-head__inner, so it is absolutely
   positioned against the band itself and needs the band's own end gutter to line
   up with the copy's right edge above it. */
.cl-page-head__credit {
	position:      absolute;
	right:         var(--cl-block-pad-x);
	bottom:        var(--cl-space-3);
	margin:        0;
	color:         var(--cl-on-brand);   /* same measured value as the lede, §10a */
	text-align:    right;
}
@media (max-width: 575.98px) {
	.cl-page-head__credit { right: var(--cl-space-5); }
}

/* A link in CMS intro copy. ⚠️ NOT the amber used as an accent elsewhere: amber
   on this scrim measures 3.00, which is a large-text-only value, and this is
   18px body copy. White at 5.53 carries the contrast.
   🎨 Michael, 2026-09-09: "remove underline from links inside our cl-glass areas —
   bold is enough to set them apart." So the resting affordance is now WEIGHT
   (bold), a non-colour cue (WCAG 1.4.1), not the underline; the underline returns
   on hover/focus. This rule governs every glass page-head lede (about, contact,
   schedule, map, team, my-schedule, the stadium landing, …). */
.cl-page-head__lede a {
	color: var(--cl-text-inverse);
	font-weight: var(--cl-weight-bold);
	text-decoration: none;
	text-underline-offset: 0.15em;
}
.cl-page-head__lede a:hover,
.cl-page-head__lede a:focus-visible {
	text-decoration: underline;
}

/* The same treatment for any OTHER .cl-glass context that carries links (glass
   panels that aren't a page-head lede). Kept lower-specificity than the rule above
   so the page-head lede's inverse colour still wins there. */
.cl-glass a {
	font-weight: var(--cl-weight-bold);
	text-decoration: none;
}
.cl-glass a:hover,
.cl-glass a:focus-visible {
	text-decoration: underline;
}

/* A real CTA inside the band — batch 3's directory.php, Michael: "turn it into
   a branded button." .btn-cl-secondary.btn-cl-on-dark (§1e) already exists for
   exactly this surface — white outline on a photograph, inverting to a white
   fill with a green label on hover — so nothing new was needed there. */
.cl-page-head__cta {
	display: inline-flex;
	margin-top: var(--cl-space-5);
}

/* The <h1> is `.cl-block__heading .cl-block__heading--brand` in the markup, not
   a class of its own — §8a's band heading at its full --cl-text-h1 size, which
   is the size it was written for. 🔑 REUSE RATHER THAN A CLONE, and the reason
   is the one §9a gives for the callout tiles: the heading carries the display
   face, the 0.98 leading, the −0.01em tracking and the uppercase transform as
   ONE decision, and a second copy is a second thing to keep true. The only
   thing the page head adds is that this one is an <h1>. */

/* 🗑️ `.cl-block__lede--stack` IS GONE (2026-08-12). It existed for one week to
   zero the last paragraph's margin inside a multi-<p> lede; `.cl-page-head__lede`
   below does that and the colour and the width in one place, and it is the only
   thing that ever used the modifier. Two classes for one element, one of which
   only ever appeared alongside the other, is a modifier that was never earning
   its name. */

/* 🔑 THE RESERVE IS NOW CANCELLED AT EVERY WIDTH, AND THAT CHANGED ON 2026-08-12
   WHEN THE HEAD BECAME A BAND. It used to be gated at `min-width: 768px`,
   because the lift only existed to put the <h1> beside the badge and there is no
   room to do that on a phone. Now the lift has a second job — starting the grass
   at the bar so the badge sits ON the band — and that job is width-independent.
   A phone that kept the reserve would show 168px of white between the bar and
   the band, with the badge floating in it: the exact defect this was fixing,
   preserved on the devices most people use. */
.cl-page-interior .cl-header { margin-bottom: 0; }

/* ⚠️ 768px, AND IT IS THE SAME BREAKPOINT THE WELCOME LINE ITSELF USES (§2).
   That is not a coincidence to be tidied into a shared token — it is the whole
   condition. Below 768 the welcome line is `display: none`, so there is nothing
   to align to and the inset would point at empty space. The badge is also at its
   largest there (230px of artwork in a 207px slot on a 320–430px page), so the
   <h1> cannot sit beside it at any inset.
   📌 So the phone puts the heading BELOW the badge instead of beside it, on the
   same grass, and pays for it with top padding derived from the overhang rather
   than guessed. Michael's own framing, 2026-08-11: "usually we have some css
   rules for mobile screens and large screens that optimize UX" — this is that
   split, and here the correct variable really is width, because what changes at
   768 is a layout the CSS itself defines. */
@media (max-width: 767.98px) {
	.cl-page-head {
		padding-inline: var(--cl-block-pad-x);
		/* Clear the badge, then a gap. ⚠️ DERIVED, NOT 192px — the overhang is
		   168px at 375–767 and 118px at 320, so a literal would leave the two
		   smallest phones with 50px of dead grass. */
		padding-top: calc(var(--cl-header-overhang) + var(--cl-space-5));
		/* The padding already clears the badge, so there is nothing left for a
		   minimum to guarantee. */
		min-height: 0;
	}
}


/* --------------------------------------------------------------------------
10b. THE INTERIOR GUTTER

🎯 Michael, 2026-08-10: *"All of the content — except the stadium map (full
width) — should have the same padding-inline 3rem on large screens. The left and
right margins should carry through the whole site. That one change will
singlehandedly fix a lot."*

🔑 THE TOKEN ALREADY HELD HIS VALUE; WHAT WAS MISSING WAS THE PAGES.
--cl-block-pad-x has been 3rem since step 4, and the homepage bands, the footer
and the subnav were all already on it. The interior pages were on raw Bootstrap
`.container`, which is not a gutter at all — it is a max-width plus ~12px of
padding, so the inset is whatever is left over after centring.

🐛 MEASURED ON about.php BEFORE THIS RULE, as the distance from the card's edge
   to the <h1>: **12 / 42 / 78 / 80 / 18 / 12 px** at 1440 / 1100 / 900 / 700 /
   576 / 430. Six widths, five different insets, none of them 48. That is the
   "margins wander" Michael is describing, and it is why it reads as a fault on
   the interior pages and not on the homepage.

⚠️ `max-width: none` IS HALF THE FIX AND THE HALF THAT IS EASY TO MISS. Padding
   alone leaves Bootstrap's centring in place, so between 576 and 1400 the
   container is narrower than the card and the auto margins add themselves ON
   TOP of the 48px — which is where 78 and 80 above come from. The copy measure
   is not lost with it: the card is capped at 1300px (--cl-site-max minus two
   matte widths), so the widest this can ever produce is 1204px, and prose
   inside it is held by its own column classes or by .cl-prose.

📌 Scoped to `.cl-page-interior` for the reason §10a gives — it reaches the
   pages that have been built, and grows one batch at a time. When the last
   batch lands, the scope is every interior page and the selector can drop to
   `main .container` if that reads better then.
-------------------------------------------------------------------------- */

.cl-page-interior main .container {
	max-width: none;
	padding-inline: var(--cl-block-pad-x);
}


/* --------------------------------------------------------------------------
10c. THE SECTION SEAM — .cl-seam

Replaces `$logoDividerNew` (php/anamorphics-cms-inits.php:26) — a 120px round
logo centred on a 2px brown rule. Michael, 2026-08-11: *"old and stale… lets do
better"*, promoted in his own note from about.php/contact.php to an all-pages
item.

📌 IT WAS ON FIVE VISIBLE PAGES, NOT SEVEN — checked rather than counted from
   the grep. `$logoDividerNew` appears on seven, but team.php:156 and
   schedule-internal-team.php:149 both carry `d-none` on the wrapper, so it has
   not rendered there for as long as those lines have existed. Of the five that
   showed it — about, contact, plan-your-trip, non-profit-partners and
   pitchers-and-catchers — the last was retired in batch N and its file
   deleted 2026-08-17, so it's four now: about, contact, plan-your-trip and
   non-profit-partners.

🔑 WHY A TICK AND A HAIRLINE, RATHER THAN A NEW ORNAMENT. The 2027 design
   separates sections with space and surface changes and contains no dividers at
   all, so the literal answer was to delete it. But a divider is doing real work
   on these pages: the CMS emits a flat run of same-shaped sections with no
   background changes to break them up, which is precisely the case the homepage
   never has. So the seam keeps the job and drops the medallion — and its accent
   tick is the amber `is-current` underline the header bar and footer columns
   already use, at the same 3px, turned horizontal. It reads as part of the
   system because it IS the system's existing mark. Colour and width are tokens
   (§5i); the ⚠️ there explains why the tick is orange on white and amber on
   green rather than one colour everywhere.

⚠️ DECORATIVE, AND THE OLD ONE WAS NOT DECLARED AS SUCH. $logoDividerNew ships
   `alt="Cactus League Round Logo"` on a divider image, so a screen reader
   announced the words "Cactus League Round Logo" between every pair of sections
   — five times on about.php. It is not an axe violation (the alt is present and
   non-empty, which is all a scanner checks) and that is exactly why it survived
   WP-2.7. An <hr> has an implicit `separator` role and no text, so the
   announcement goes away without needing an aria-hidden.
-------------------------------------------------------------------------- */

.cl-seam {
	/* Bootstrap's <hr> is `border-top: 1px solid currentColor` at `opacity: .25`
	   — a translucent line whose colour depends on inherited text colour, which
	   makes it unmeasurable on a coloured surface. Replaced outright with an
	   opaque token so the value in the file is the value on screen. */
	border: 0;
	opacity: 1;
	height: 1px;
	background: var(--cl-seam-line);
	margin: 0;

	/* The tick is absolutely positioned, so the rule needs to be its containing
	   block. It sits ABOVE the hairline (-1px) rather than centred on it: a 3px
	   mark centred on a 1px rule lands on a half pixel and renders soft at 1x. */
	position: relative;
}

.cl-seam::before {
	content: "";
	position: absolute;
	inset-block-start: -1px;
	inset-inline-start: 0;
	width:  var(--cl-seam-tick-w);
	height: var(--cl-seam-tick-h);
	background: var(--cl-seam-tick-color);
}

/* 🐛 THE SEAM CONTRIBUTES NO SPACING OF ITS OWN, AND THE FIRST VERSION DID.
   `margin-block: var(--cl-space-8)` looked right in isolation and rendered a
   **260px** hole between sections on the page: the seam sits between two
   `.cl-page-section`s that already carry 4rem of padding each, so its 4rem
   margins landed on top of theirs and every gap was doubled. Measured on the
   render, not spotted in the file — 132px above the rule and 128px below it,
   against the 128px total that was intended.
   🔑 A separator placed BETWEEN two elements that own their vertical rhythm
   must not own any itself. The rule is `margin: 0` above; there is nothing left
   for a modifier to say, which is why one no longer exists. */


/* --------------------------------------------------------------------------
10d. THE PHONE GUTTER FOR THE INTERIOR PAGE

The canonical list of what backs off to 24px below 576px is in §8a, and the
reasoning for the number is there — the footer's brand column and the subnav's
fifteen team chips both stopped fitting when 3rem was honoured at 320px, the
second of them dropping under WCAG 2.2 SC 2.5.8's 24px target floor.

⚠️ THESE TWO SELECTORS CANNOT LIVE IN THAT LIST, and the reason is cascade
   rather than taste. §10b's `.cl-page-interior main .container` is (0,2,1) and
   §8a's list is (0,2,0)/(0,1,0) — but §8a is ~900 lines EARLIER, so a tie or a
   loss there is decided by source order and the 48px would win at 320px. Kept
   here, after the rule they override, they behave.
-------------------------------------------------------------------------- */
@media (max-width: 575.98px) {
	.cl-page-head,
	.cl-page-interior main .container {
		padding-inline: var(--cl-space-5);
	}
}


/* --------------------------------------------------------------------------
10e. THE INTERIOR SECTION — .cl-page-section

The repeating unit below the page head: heading, optional standfirst, body, and
sometimes a photograph beside the body. On about.php it is a CMS row; on the
other batch-1 pages it is the same shape by hand.

🔑 THE OLD MARKUP CENTRED THESE AND THE NEW ONE DOES NOT, which is the single
   most visible change on the page. Every row was `.row.justify-content-center`
   with column classes, so each section's copy was centred against the page
   while its <h1> was not — and that, combined with the column bug below, is
   what put the body text well to the right of the heading it sits under. The
   2027 language is left-aligned throughout: the header, the hero, all six
   homepage bands and both interior partials. Nothing on the site is centred.

🐛 AND ONE OF THOSE ROWS WAS BROKEN OUTRIGHT, on about.php only. Lines 77 and 83
   carried `col-xl-10 col-xl-8` — the SAME breakpoint declared twice, so only
   col-xl-8 ever applied and the other class was dead. Line 83 also read
   `ccol-12`, a typo, leaving that div with no base column class at all. With
   justify-content-center the effect was body copy indented well right of its
   own <h1>, visible on the page today. economic-impact.php was built off this
   markup in batch N and documents the same defect at length; it avoided it by
   writing col-12, and this retires the columns entirely.
-------------------------------------------------------------------------- */

.cl-page-section {
	padding-block: var(--cl-space-8);
}

/* Body copy caps at the reading measure rather than at the container. 68ch is
   --cl-measure, the site's existing prose cap (tokens.css §3), and it is the
   reason the interior gutter could drop Bootstrap's max-width without the copy
   running the full 1204px the card allows. */
.cl-page-section__body {
	max-width: var(--cl-measure);
}

/* ⚠️ AND IT COMES OFF AGAIN INSIDE A SPLIT, where the grid track is already
   doing the capping at ~700px. Two caps in series is not "extra safe" — the
   narrower one wins, so the copy column would stop at 68ch while its track
   stayed 700px wide, and the photograph beside it would sit ~150px away from
   text that had quietly stopped filling its own column.
   🔑 Written as a descendant selector rather than as a modifier class in the
   markup, because the condition is structural: it is true whenever this element
   is inside a split and never depends on the page author remembering. */
.cl-block__split .cl-page-section__body {
	max-width: none;
}

.cl-page-section__body > :last-child,
.cl-page-section__figure > :last-child {
	margin-bottom: 0;
}

/* The CMS emits description2 as a run of <li> (nl2li), so the template supplies
   the <ul> — WP-2.7's fix, and dropping the parent is the violation it closed.
   🐛 IT MUST NOT BE `ps-0`, WHICH IS WHAT THE OLD MARKUP USED. That class kills
   the list's left padding; it was survivable while the column was narrow and
   centred, because the markers still fell inside the container, but at full
   width they land outside it and the list silently loses its bullets. Same
   finding economic-impact.php recorded in batch N, now fixed at the source. */
.cl-page-section__body ul {
	padding-inline-start: var(--cl-space-5);
}

.cl-page-section__figure {
	/* Bootstrap's reboot gives <figure> a 1rem bottom margin; the section's own
	   padding owns that rhythm here. */
	margin: 0;
}

.cl-page-section__figure img {
	width: 100%;
	height: auto;
	border-radius: var(--cl-radius-md);
}

/* The image credit under a photograph. `.small-byline` already exists in
   styles-custom.css and is kept — this only supplies the rhythm the old inline
   `pt-1` was doing. */
.cl-page-section__credit {
	margin-top: var(--cl-space-2);
}

@media (max-width: 991.98px) {
	/* The photograph goes under the copy rather than beside it, and gets a gap
	   the grid was providing horizontally. */
	.cl-page-section__figure { margin-top: var(--cl-space-5); }
}


/* --------------------------------------------------------------------------
THE DUGOUT POST STANDFIRST — dugout-post.php

The sponsor byline and optional actions inside the band's glass lede
(.cl-page-head__lede.cl-glass), above the standfirst paragraph. The lede itself
supplies the panel, the measure and the text colour (§10a); these only set the
byline's smaller size and the rhythm between the three parts.
-------------------------------------------------------------------------- */
.dugout-post__byline {
	margin-bottom: var(--cl-space-4);
	font-size:     var(--cl-text-sm);
	letter-spacing: 0.02em;
}
.dugout-post__actions {
	margin-top: var(--cl-space-5);
}


/* --------------------------------------------------------------------------
THE DIRECTORY LISTING CONTACT CARD — listing.php / listing-members.php

The aside beside a listing's write-up: a website CTA, phone/map icon-links, an
hours block and a social row, stacked in a .cl-card. Replaces the old
full-width centred stack of the same links.
-------------------------------------------------------------------------- */
/* The right column: the hero photo above the contact card. */
.cl-listing-aside {
	display:        flex;
	flex-direction: column;
	gap:            var(--cl-space-6);
	align-self:     start;
}
.cl-listing-hero {
	margin: 0;   /* the aside gap owns the rhythm; §10e figure img gives the radius */
}
.cl-listing-contact {
	display:        flex;
	flex-direction: column;
	gap:            var(--cl-space-4);
	align-self:     stretch;   /* fill the aside width; the aside is already start-aligned */
}
.cl-listing-contact__cta {
	align-self: start;   /* the button hugs its label, not the card width */
}
.cl-listing-contact__row {
	/* icon-links already align icon+text (§ styles-custom); no extra rule beyond
	   sitting in the flex column. Kept as a hook for per-row tuning. */
	margin: 0;
}
.cl-listing-contact__block + .cl-listing-contact__block,
.cl-listing-contact__row + .cl-listing-contact__block {
	padding-top: var(--cl-space-4);
	border-top:  1px solid var(--cl-card-hairline);
}
.cl-listing-contact__label {
	margin-bottom:  var(--cl-space-2);
	font-size:      var(--cl-text-sm);
	font-weight:    var(--cl-weight-semibold);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color:          var(--cl-text-brand);
}
.cl-listing-contact__social {
	display:   flex;
	gap:       var(--cl-space-4);
	flex-wrap: wrap;
}


/* --------------------------------------------------------------------------
10f. THE PARTNER CARD WALL — non-profit-partners.php (variation A)

Michael's chosen design for this page (WP-2.10-STEP-5-NOTES.md, "The Card Wall"):
*"A uniform grid of partner cards (3-up desktop / 2-up tablet / 1-up phone).
Every card identical: logo panel, name, a 2-line clamped description, one CTA.
No card is bigger than another."* The point is EQUAL WEIGHT — the old stacked
sections gave the top partner the most attention purely by position; a uniform
footprint is the strongest possible signal that none is ranked above another.

🔑 IT SERVES ONE PAGE, so there is nothing to propagate — this is the §7a sample
   and its own destination at once. plan-your-trip.php gets variation B (the chip
   /panel layout on batch 3's filter engine), NOT this.

⚠️ LOGOS SIT ON WHITE, NOT ON A TINTED PANEL, AND THAT IS DELIBERATE. The five
   assets are mixed: transparent PNGs (hohokams, charros, diablos) and
   white-BACKGROUND JPGs (peoria "...white", surprise). A tinted logo panel would
   show a white rectangle behind the JPGs and could hide a white-on-transparent
   mark — the assets were made to be shown on white, which is also how the old
   page showed them. So the card is the surface and the logo box only fixes the
   height so unequal aspect ratios line up.

📌 THE CTA IS EXTRACTED FROM description3, WHICH IS WHERE THE CMS AUTHORS IT — a
   single <a> per partner ("Learn More and Support the X"). The visible label is
   a uniform "Learn More" so the cards read as equal; the partner name rides in
   an aria-label so five identical links are still distinct to a screen reader
   (WCAG 2.4.4), which a bare repeated "Learn More" would fail.
-------------------------------------------------------------------------- */

.cl-partner-grid {
	display: grid;
	gap: var(--cl-block-gap);
	grid-template-columns: 1fr;                        /* 1-up phone */
}
@media (min-width: 576px) {
	.cl-partner-grid { grid-template-columns: repeat(2, 1fr); }   /* 2-up tablet */
}
@media (min-width: 992px) {
	.cl-partner-grid { grid-template-columns: repeat(3, 1fr); }   /* 3-up desktop */
}

/* Each card is a .cl-card (white + sand hairline) laid out as a flex column so
   the CTA can pin to the bottom — every card's action then sits on one baseline
   regardless of how long the description is, which is the equal-weight point.
   `height: 100%` because grid stretches the cells to the tallest in the row and
   the column needs to fill that height for margin-top:auto to reach the floor. */
.cl-partner-card {
	display: flex;
	flex-direction: column;
	gap: var(--cl-space-4);
	height: 100%;
}
/* min-width:0 so a long unbroken word cannot force a track wider than its cell —
   the same grid-item trap §8's .cl-block__split guards against. */
.cl-partner-card > * { min-width: 0; }

/* The logo box only normalises height; no background (see the ⚠️ above). Left-
   aligned like everything else in the 2027 interior language. */
.cl-partner-card__logo {
	display: flex;
	align-items: center;
	justify-content: flex-start;
	height: 4rem;
}
.cl-partner-card__logo img {
	max-height: 100%;
	max-width:  100%;
	width:      auto;
	object-fit: contain;
}

/* ---- PHOTO HEADER (plan-your-trip only — 2026-09-09) ------------------------
   .cl-partner-card--photo leads with the destination's main image as a card
   header instead of the logo box, matching the homepage explore tiles / callouts.
   The image is full-bleed: pulled out by the card's own padding so it reaches the
   card edges, with the top corners rounded to the card's radius (−1px for the
   card's 1px border). Scoped to the modifier so non-profit-partners.php keeps its
   logo header. */
.cl-partner-card__photo {
	margin: calc(var(--cl-card-pad) * -1) calc(var(--cl-card-pad) * -1) 0;
	border-radius: calc(var(--cl-radius-lg) - 1px) calc(var(--cl-radius-lg) - 1px) 0 0;
	overflow: hidden;
	aspect-ratio: 16 / 10;
	background: var(--cl-sand-100, rgb(0 0 0 / 4%));   /* honest ground before the photo paints */
}
.cl-partner-card__photo img {
	display:    block;
	width:      100%;
	height:     100%;
	object-fit: cover;
}

/* UI text, NOT the display band-heading face — a card name is interface text.
   Green to tie it to the section headings the rest of the batch uses. The logo
   already carries the name; this repeats it small for scanning and heading
   structure (a real <h2> under the page <h1>). */
.cl-partner-card__name {
	margin: 0;
	font-size:      var(--cl-text-lead);
	font-weight:    var(--cl-weight-bold);
	line-height:    1.2;
	text-transform: none;
	color:          var(--cl-text-brand);
}

/* Clamped — variation A trades full prose for a uniform footprint, and the CTA
   carries the rest. 4 lines rather than the note's 2: these descriptions are the
   partner's whole story and 2 lines lost too much, while 4 still holds the cards
   to one height. */
.cl-partner-card__desc {
	margin: 0;
	font-size: var(--cl-text-ui);
	color:     var(--cl-text-secondary);
	display: -webkit-box;
	-webkit-line-clamp: 4;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

/* Pinned to the bottom of the card, left-aligned (align-self:start so the button
   is its own width, not stretched across the card). */
.cl-partner-card__cta {
	margin-top: auto;
	align-self: flex-start;
}

/* ---- ECONOMIC IMPACT — the "statistical snapshot" stat cards (2026-09-09) ----
   economic-impact.php renders the study's figures as on-brand raised cards on the
   white page (a light counterpart to the homepage's green .cl-impact band). Green
   figure (high contrast on white, unlike the amber used on the glass fill), the
   study's sentence beneath. Purely a nicer presentation of existing CMS numbers. */
.cl-impact-figures {
	list-style: none;
	margin:     var(--cl-space-5) 0 var(--cl-space-6);
	padding:    0;
	display:    grid;
	gap:        var(--cl-space-4);
	grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
.cl-impact-figure {
	display:        flex;
	flex-direction: column;
	gap:            var(--cl-space-2);
}
.cl-impact-figure__value {
	display:      block;
	font-family:  var(--cl-font-display);
	font-weight:  var(--cl-weight-bold);
	font-size:    clamp(1.75rem, 1.4rem + 1.5vw, 2.5rem);
	line-height:  1;
	color:        var(--cl-text-brand);   /* green — reads on white, unlike amber */
	font-variant-numeric: tabular-nums;
}
.cl-impact-figure__body {
	margin:      0;
	font-size:   var(--cl-text-ui);
	color:       var(--cl-text-secondary);
	line-height: var(--cl-leading-body);
}
.cl-impact-source {
	margin:      0;
	font-size:   var(--cl-text-sm);
	color:       var(--cl-text-secondary);
	font-style:  italic;
}


/* --------------------------------------------------------------------------
10g. THE PARTNER MODAL — non-profit-partners.php "Learn More" popup

The card's Learn More button (§10f) no longer leaves the site; it opens a
Fancybox v3 popup carrying the partner's FULL story plus the website link that
used to be the button's href. Same engine the schedule calendar already loads
(footer.php), so nothing new ships — this is only the theme.

🔒 SCOPED so it cannot touch the schedule's own `.fancyboxgames` popups: every
   rule hangs off `.cl-partner-fancybox`, the mainClass set per-trigger in the
   page's data-options. The bare Fancybox chrome (white 44px box) is re-dressed
   into a .cl-card-style surface: white, sand hairline, the §10f card language
   scaled up to reading size.

Layout mirrors the card so the popup reads as the same object enlarged — logo
box on white (the ⚠️ in §10f applies: mixed transparent-PNG / white-JPG assets
must sit on white), green name, then the un-clamped prose and one accent CTA.
-------------------------------------------------------------------------- */

/* The Fancybox content shell — turn the default white slab into the card.
   🔑 SCOPED TO `.cl-partner-modal`, NOT to a container mainClass: Fancybox moves
   our hidden #partnerModalN div in and ADDS `fancybox-content` to it, so the
   element is itself the content box (`class="cl-partner-modal fancybox-content"`).
   The two-class selector then out-specifies Fancybox's own `.fancybox-content`
   (44px pad / square corners) with no !important and no dependency on the
   mainClass option reaching the container (which this build does not honour).

   🔑 CAPPED, WITH THE STORY SCROLLING INSIDE (Michael, 2026-08-19: "make it sit
   nicely on the large screen and content scrolls in the popup"). The shell is a
   flex COLUMN capped at the viewport (`max-height` + `overflow: hidden`); the
   pinned hero is child one and the scrollable `__scroll` region is child two, so
   a tall popup scrolls its own body instead of growing past the screen and the
   close × keeps the hero to sit on. Standard modal behaviour — a dialog taller
   than the viewport should scroll internally, not push the whole overlay.

   --cl-modal-pad is the single source of truth for the inset. The shell itself
   is padding-0 now (so the hero is naturally full-bleed and the scrollbar hugs
   the edge); the hero and the __scroll region each apply the pad, so both still
   track together when the phone query narrows it. */
/* THE CARD IS FANCYBOX'S OWN CONTENT ELEMENT. Fancybox moves our hidden
   #partnerModalN div in and adds `fancybox-content`, so this element IS the
   content box (`class="cl-partner-modal fancybox-content"`) and Fancybox centres
   it. We style it directly as the card and give it ONE explicit width — no inner
   wrapper.
   🐛 WHY NO INNER __panel (had one, removed 2026-08-19): a wrapper inside the
   inline-block content ended up a different size from it, so Fancybox's close ×
   — which it pins to the CONTENT element's corner — floated off the card's corner,
   and the width read as "strange" (Michael's screenshot). Making this element the
   card itself puts the corner, the width and the × back in agreement.
   🔑 position:relative so the × pins to THIS corner; overflow:hidden clips the
   full-bleed hero to the radius; the single .cl-partner-modal__scroll inside is
   the only thing that scrolls (the × is a sibling of it, so it stays put). We do
   NOT set display here — Fancybox keeps it inline-block and centres it; forcing
   flex broke that centring once already. */
.fancybox-content.cl-partner-modal {
	--cl-modal-pad: var(--cl-space-6);
	position: relative;
	width: min(40rem, calc(100vw - 2rem));
	max-width: none;
	padding: 0;
	overflow: hidden;
	background: var(--cl-surface);
	border: 1px solid var(--cl-sand-300);
	border-radius: var(--cl-radius-lg);
	box-shadow: var(--cl-shadow-3);
}
/* Wide variant — plan-your-trip.php (Michael, 2026-08-19: "consider going wider
   on the pop-up layout on large screens"). Same second-class specificity as the
   rule above, so it must stay after it to win on source order. */
.fancybox-content.cl-partner-modal--wide {
	width: min(56rem, calc(100vw - 2rem));
}

/* 🔑 ONE SCROLL REGION FOR THE WHOLE POPUP — hero included (Michael, 2026-08-19:
   "I want the entire pop-up content to scroll. Right now only the stuff under the
   image scrolls"). It caps to the viewport and scrolls everything; the close ×
   lives on the card (a sibling of this element), so it stays pinned while the hero
   and story scroll beneath it. The pad is the body inset; the hero negates it to
   reach the card edges (below). */
.cl-partner-modal__scroll {
	max-height: calc(100vh - 6rem);   /* fits inside Fancybox's slide gutter with room to spare */
	overflow-y: auto;
	padding: var(--cl-modal-pad);
}

/* Close button — Fancybox drops its small × at the content's top-right, which is
   now OVER the hero photo, so the default dark glyph would vanish on a dark
   frame. Give it a translucent dark disc so it reads on any image. */
.cl-partner-modal .fancybox-close-small {
	top: var(--cl-space-3);
	right: var(--cl-space-3);
	width: 2rem;
	height: 2rem;
	padding: 0;
	color: var(--cl-white);
	background: rgb(20 20 20 / 45%);
	border-radius: var(--cl-radius-pill);
	opacity: 1;
	transition: background-color 0.15s ease;
}
.cl-partner-modal .fancybox-close-small:hover {
	background: rgb(20 20 20 / 70%);
}

/* The hero band — the partner's photo, full-bleed at the TOP of the scroll region
   (Michael wants it to scroll away with the rest). It is the scroll region's first
   child, so negative margins cancel that region's padding on three sides (top +
   both flanks) to reach the card edges; a pad-sized gap below separates it from the
   logo. The card's own overflow:hidden + radius clip its top corners; it also
   rounds its own, belt-and-braces. Partner heroes show at NATURAL 3:2 — no crop —
   because several frames carry a baked-in caption (see the PHP note). */
.cl-partner-modal__hero {
	margin:
		calc(-1 * var(--cl-modal-pad))
		calc(-1 * var(--cl-modal-pad))
		var(--cl-modal-pad);
	border-top-left-radius: var(--cl-radius-lg);
	border-top-right-radius: var(--cl-radius-lg);
	overflow: hidden;
	background: var(--cl-sand-100);   /* fills the frame while the photo loads */
}
.cl-partner-modal__hero img {
	display: block;
	width: 100%;
	height: auto;
}
/* The WIDE (trip) hero is far larger than the partner hero — a 56rem-wide 3:2
   frame is ~600px tall and would fill the whole opening view before any copy shows.
   Cap it so the popup opens on photo + the start of the story (then scrolls).
   cover-crop is fine here: trip destination photos carry no baked-in caption,
   unlike the partner heroes the §10g note protects — so this is scoped to --wide. */
.cl-partner-modal--wide .cl-partner-modal__hero img {
	height: clamp(180px, 34vh, 320px);
	object-fit: cover;
}

/* Logo box: same "normalise the height, sit on white" role as the card's, one
   size up so the mark reads at popup scale. Left-aligned like the card. */
.cl-partner-modal__logo {
	display: flex;
	align-items: center;
	justify-content: flex-start;
	height: 5.5rem;
	margin-bottom: var(--cl-space-4);
}
.cl-partner-modal__logo img {
	max-height: 100%;
	max-width: 100%;
	width: auto;
	object-fit: contain;
}

/* Name — the §10f card-name face, scaled to a modal heading. */
.cl-partner-modal__name {
	margin: 0 0 var(--cl-space-4);
	font-size: var(--cl-text-h4);
	font-weight: var(--cl-weight-bold);
	line-height: 1.2;
	text-transform: none;
	color: var(--cl-text-brand);
}

/* The un-clamped story. nl2p emits <p> runs — space them, don't clamp. */
.cl-partner-modal__body {
	color: var(--cl-text);
	line-height: 1.6;   /* body copy inherits the base 16px; only the rhythm is set */
}
.cl-partner-modal__body p {
	margin: 0 0 var(--cl-space-4);
}
.cl-partner-modal__body p:last-child {
	margin-bottom: 0;
}
/* Authored links inside the story — brand green, underlined so they read as
   links on the white card (plan-your-trip.php's resource lists lean on these). */
.cl-partner-modal__body a {
	color: var(--cl-text-brand);
	text-decoration: underline;
	text-underline-offset: 0.15em;
}
.cl-partner-modal__body a:hover {
	color: var(--cl-primary-hover);
}
/* The resource list (plan-your-trip.php's description2). nl2li emits bare <li>s;
   give them markers and rhythm — styles-custom.css strips list defaults site-wide,
   so the modal restores them locally. */
.cl-partner-modal__list {
	margin: 0 0 var(--cl-space-4);
	padding-left: 1.25em;
	list-style: disc;
}
.cl-partner-modal__list li {
	margin-bottom: var(--cl-space-2);
}
.cl-partner-modal__list li:last-child {
	margin-bottom: 0;
}

/* The single conversion CTA — go support the partner. Accent (orange) because
   this is the "act" button, the one place the popup wants to pop. */
.cl-partner-modal__actions {
	margin-top: var(--cl-space-5);
}
/* Photo credit (plan-your-trip.php) — a small muted byline under the CTA, kept
   low-key so attribution is present without competing with the action. */
.cl-partner-modal__credit {
	margin: var(--cl-space-3) 0 0;
	font-size: var(--cl-text-ui);
	color: var(--cl-text-secondary);
}

/* Phone: the popup gives back the outer gutter Fancybox reserves, and the logo
   box relaxes so tall marks aren't cramped. */
@media (max-width: 575.98px) {
	.fancybox-content.cl-partner-modal {
		--cl-modal-pad: var(--cl-space-5);   /* hero + body both track this */
	}
	.cl-partner-modal__logo {
		height: 4.5rem;
	}
}

/* Next / previous arrows — the gallery navigation Fancybox shows once every
   partner shares one group (see the PHP note). Re-dressed to the SAME translucent
   dark disc as this modal's close × just above (§10g), so the popup's three
   controls read as one set, and lit brand-green on hover/focus.
   🔑 SCOPED WITH :has(), NOT a mainClass — this build does not apply the mainClass
   option to `.fancybox-container` (verified: the class never lands), so the only
   reliable hook is the container that CONTAINS our modal. The schedule calendar's
   own popups don't carry `.cl-partner-modal`, so they keep the default Fancybox
   chrome. `.fancybox-button` here is only the arrows — the top toolbar is off
   (toolbar:false) and the close is a different class. */
.fancybox-container:has(.cl-partner-modal) .fancybox-navigation .fancybox-button {
	width: 3rem;
	height: 3rem;
	padding: 0.75rem;
	color: var(--cl-white);
	background: rgb(20 20 20 / 45%);
	border-radius: var(--cl-radius-pill);
	transition: background-color 0.15s ease;
}
.fancybox-container:has(.cl-partner-modal) .fancybox-navigation .fancybox-button:hover,
.fancybox-container:has(.cl-partner-modal) .fancybox-navigation .fancybox-button:focus-visible {
	background: var(--cl-primary);
}


/* --------------------------------------------------------------------------
10h. THE STORE — membership / golf / luncheon detail pages (batch 2)

The store detail pages share one shape: a pitch (prose + a benefits list) beside
an application card that carries the price and the Jotform. This section adds the
three pieces the interior language did not already have — the pitch/apply split,
the price block, and the application card's own trim — plus an interior-page
breadcrumb. Everything else (grass band, gutter, headings, seam, .storeUL) is
reused from §10a-e and styles-custom.css.

🔑 SAMPLE + PROPAGATION: built on store-membership-cactus-league.php and shared,
   unchanged but for the itemID, by the other three membership pages. The golf /
   luncheon trio reuse the same .cl-apply card.
-------------------------------------------------------------------------- */

/* The pitch/apply split. Wider pitch than form, and top-aligned (not the base
   .cl-block__split centre) because the two columns are different lengths — a
   short form must not float to the vertical middle of a long benefits list. */
.cl-block__split--store {
	align-items: start;
	grid-template-columns: 1fr;   /* stacked on phone; the form drops below */
}
@media (min-width: 992px) {
	.cl-block__split--store { grid-template-columns: 1fr 1fr; }
}

/* The benefits heading sits above the list with the section's own rhythm; it
   follows prose, so it needs air above it that a bare --md heading lacks. */
.cl-store-benefits__heading {
	margin-top: var(--cl-space-6);
}

/* The application card — the price + CTA + form, raised off the white page. */
.cl-apply {
	--cl-card-pad: var(--cl-space-5);
}

/* The price block — the one loud number on the page. Display face, brand green,
   with the label as quiet UI text beneath it. */
.cl-price {
	display: flex;
	flex-direction: column;
	gap: var(--cl-space-1);
	padding-bottom: var(--cl-space-4);
	margin-bottom: var(--cl-space-4);
	border-bottom: 1px solid var(--cl-card-hairline);
}
.cl-price__value {
	font-family: var(--cl-font-display, inherit);
	font-size: var(--cl-text-h2);
	font-weight: var(--cl-weight-bold);
	line-height: 1;
	color: var(--cl-text-brand);
}
.cl-price__label {
	font-size: var(--cl-text-ui);
	color: var(--cl-text-secondary);
}

/* The CTA line above the form — interface text, not a band heading. */
.cl-apply__heading {
	margin: 0 0 var(--cl-space-4);
	font-size: var(--cl-text-lead);
	font-weight: var(--cl-weight-bold);
	line-height: 1.25;
	text-transform: none;
	color: var(--cl-text);
}

/* The Jotform lives here. The vendor script injects an <iframe>; hold it to the
   card width so a fixed-width embed cannot force the card wider than its column. */
.cl-apply__form {
	margin: 0 calc(-1 * var(--cl-space-2));
}
.cl-apply__form iframe {
	max-width: 100% !important;
	width: 100% !important;
}

.cl-apply__disclaimer {
	margin-top: var(--cl-space-4);
	margin-bottom: 0;
}

/* The interior breadcrumb — a slim, centred trail on the white page under the
   grass band. Small caps, generous room above (the band sits tight to it
   otherwise) and below before the content. 0.75rem: 14px read "much too big" as
   uppercase, so it drops a step. Wraps and centres on its own line. */
.cl-breadcrumb {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: var(--cl-space-1) 0;
	/* Top room only — it sits tight under the grass band and the .cl-page-section
	   that follows already carries a --cl-space-8 top pad for the gap below. */
	padding-top: var(--cl-space-7);
	font-size: 0.75rem;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--cl-text-secondary);
}
.cl-breadcrumb a {
	color: var(--cl-link);
	text-decoration: none;
}
.cl-breadcrumb a:hover {
	color: var(--cl-link-hover);
	text-decoration: underline;
}
.cl-breadcrumb__sep {
	margin: 0 var(--cl-space-2);
	color: var(--cl-sand-300);
}
.cl-breadcrumb [aria-current="page"] {
	color: var(--cl-text-secondary);
}


/* --------------------------------------------------------------------------
10i. THE STORE GRID — merch products + directory listings (batch 2)

store-merch.php's 10 live products and store-directory.php's (currently empty,
0 active rows) business listings are the same shape: an optional photo, a
title, a short subtitle/note, body copy of variable length, and a price and/or
CTA pinned to the card's bottom edge. One card serves both rather than two
near-duplicates — the merch grid renders it fully populated; the directory
grid renders it with the price row omitted whenever a listing has none.

🔑 BUILT AGAINST WHAT THE CMS CAN HOLD, NOT WHAT IT HOLDS TODAY. The directory
   set (itemID 60) has zero active rows right now — WP-2.7's lesson applies in
   reverse here: an empty set is not evidence the component is unneeded, only
   that there is nothing to screenshot it against yet.
-------------------------------------------------------------------------- */

.cl-store-grid {
	display: grid;
	gap: var(--cl-block-gap);
	grid-template-columns: 1fr;
	list-style: none;
	padding: 0;
	margin: 0;
}
@media (min-width: 576px) { .cl-store-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 992px) { .cl-store-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1200px) { .cl-store-grid { grid-template-columns: repeat(4, 1fr); } }

/* 🎨 Michael, 2026-08-13: match the membership tiles' gap (`.cl-explore__grid`,
   §8f — --cl-space-4, 1rem), not this grid's own --cl-block-gap (--cl-space-7,
   3rem) — "carry this through as the standard for remaining designs." A
   modifier rather than a change to .cl-store-grid itself: that base rule is
   still live on store-merch.php/store-directory.php (batch 2, already signed
   off), and retrofitting their gap wasn't part of this ask. Every batch-3
   grid (directory, directory-members, dugout, hof) carries this class from
   here on; treat it as the default for anything new too. */
.cl-store-grid--card-gap {
	gap: var(--cl-space-4);
}

/* HOF only — Michael, 2026-08-13: "5 per row on large screens. These faces
   are a bit too big at 4 per row." Same 1200px step §10i already uses for
   the 3→4 jump; this just carries it one column further, scoped so
   directory/directory-members/dugout/store stay at the 4-column cap. */
@media (min-width: 1200px) {
	.cl-store-grid--five { grid-template-columns: repeat(5, 1fr); }
}

.cl-store-card {
	display: flex;
	flex-direction: column;
	gap: var(--cl-space-3);
	height: 100%;
}
/* min-width:0 so a long unbroken word (a URL in a directory listing, say)
   cannot force a track wider than its cell — same grid-item trap §10f guards. */
.cl-store-card > * { min-width: 0; }

/* The photo bleeds to the card's own edges — cancelling .cl-card's padding on
   three sides rather than giving the image its own inset panel, so a 600×600
   product photo reads as the card's top rather than as an image floating
   inside it. Square by construction: every product/listing thumbnail the CMS
   produces is 600×600. */
.cl-store-card__media {
	margin: calc(-1 * var(--cl-card-pad)) calc(-1 * var(--cl-card-pad)) 0;
	aspect-ratio: 1 / 1;
	overflow: clip;
	border-radius: var(--cl-radius-lg) var(--cl-radius-lg) 0 0;
	background: var(--cl-sand-100);
}
.cl-store-card__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* UI text, not the display face — same call as .cl-partner-card__name (§10f):
   a card title is interface text, green to tie it to the section heading. */
.cl-store-card__title {
	margin: 0;
	font-size: var(--cl-text-lead);
	font-weight: var(--cl-weight-bold);
	line-height: 1.2;
	color: var(--cl-text-brand);
}
.cl-store-card__subtitle {
	margin: 0;
	font-size: var(--cl-text-ui);
	color: var(--cl-text-secondary);
}
.cl-store-card__note {
	margin: 0;
	font-size: var(--cl-text-ui);
	font-style: italic;
	color: var(--cl-text-secondary);
}

/* Body copy — a directory listing's subsections (nl2p/nl2ul/nl2ol). Grows to
   fill the card so every meta row in a row of cards lands on one baseline,
   the same equal-weight reasoning as the partner-card CTA. */
.cl-store-card__body {
	flex: 1;
}
.cl-store-card__body > :last-child { margin-bottom: 0; }

/* The price + CTA row, pinned to the card floor with a hairline above it —
   .cl-price's own rule, reused at card scale instead of page scale. */
.cl-store-card__meta {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--cl-space-3);
	margin-top: auto;
	padding-top: var(--cl-space-3);
	border-top: 1px solid var(--cl-card-hairline);
}
.cl-store-card__price {
	font-size: var(--cl-text-lead);
	font-weight: var(--cl-weight-bold);
	color: var(--cl-text-brand);
}
.cl-store-card__cta {
	flex-shrink: 0;
}

.cl-store-card__disclaimer {
	margin: 0;
	font-size: 0.75rem;
	color: var(--cl-text-secondary);
}


/* ==========================================================================
10j. THE FILTER DRAWER + CHIPS — batch 3 (directory / directory-members /
     dugout / hof)

Retires MixItUp (js/mixitup.min.js, js/mixitup-multifilter.min.js) in favour
of ~/js/filter-drawer.js + js/filter-engine.js. See STEP-5-NOTES.md's
2026-08-11 proposal for the reasoning; the two load-bearing choices it landed
on:

  · Filter state lives in the URL, not a JS variable — one mechanism answers
    "list updates live behind the drawer", "back button restores the last
    filter", and "a filtered view is a real, bookmarkable link" at once.
  · Cards are hidden with the `hidden` ATTRIBUTE, never a `display:none`
    class. §7a's own finding is why: MixItUp's `display:none` on a
    filtered-out card is invisible to axe, which is how WP-2.7's 54 hidden
    failures happened. `hidden` is semantic — a scanner (and a screen reader)
    can reason about it, and every filter state can be forced open for a
    sweep the same way step 4c forced the newsletter collapse open.

Drawer built generic — `[data-cl-drawer]` — because RESKIN.md §7b already
calls out reuse by map.php's filter package. Nothing below the token layer
names "directory".

    10j-i.  the drawer shell
    10j-ii. the filter chips + trigger button
    10j-iii. the card additions — badges + link rows, layered on §10i's
             .cl-store-card rather than a new component
-------------------------------------------------------------------------- */

/* ---- 10j-i. THE DRAWER ----
   Same recipe as .cl-subnav (§2c): transform + `visibility` with a
   close-delay equal to the transition, so a keyboard user's tab order empties
   the moment the panel is invisible and not a frame before. The differences
   from the curtain are only what has to differ: `position: fixed` because
   this drawer is not scoped to the header card and must sit above the whole
   viewport regardless of scroll position, and it slides from the RIGHT edge
   (Michael: "off-canvas... opening right side of viewport") rather than down
   from under the bar. */
.cl-drawer {
	position: fixed;
	top: 0;
	right: 0;
	bottom: 0;
	width: var(--cl-drawer-w);
	z-index: 1000;   /* above the header's 30, clear below Fancybox's 1100
	                    (§10g) so a partner-card popup can never be trapped
	                    under an open drawer */

	background: var(--cl-drawer-bg);
	box-shadow: var(--cl-drawer-shadow);

	display: flex;
	flex-direction: column;

	transform: translateX(100%);
	visibility: hidden;
	transition:
		transform  var(--cl-drawer-dur) var(--cl-drawer-ease),
		visibility 0s linear var(--cl-drawer-dur);
}

.cl-drawer.is-open {
	transform: translateX(0);
	visibility: visible;
	transition:
		transform  var(--cl-drawer-dur) var(--cl-drawer-ease),
		visibility 0s;
}

.cl-drawer__inner {
	display: flex;
	flex-direction: column;
	min-height: 0;   /* lets __body scroll instead of the panel overflowing */
	height: 100%;
}

.cl-drawer__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--cl-space-3);
	padding: var(--cl-space-5) var(--cl-space-5) var(--cl-space-4);
	border-bottom: 1px solid var(--cl-card-hairline);
	flex-shrink: 0;
}

.cl-drawer__title {
	margin: 0;
	font-size: var(--cl-text-lead);
	font-weight: var(--cl-weight-bold);
	color: var(--cl-text-brand);
}

/* A real <button>, appearance reset — same reasoning as .cl-subnav__backdrop:
   a control, not a styled div. */
.cl-drawer__close {
	appearance: none;
	border: 0;
	background: transparent;
	padding: var(--cl-space-2);
	line-height: 1;
	font-size: 1.25rem;
	color: var(--cl-text-secondary);
	cursor: pointer;
	border-radius: var(--cl-radius-sm);
}
.cl-drawer__close:hover,
.cl-drawer__close:focus-visible {
	color: var(--cl-text-brand);
}

.cl-drawer__body {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;   /* same rubber-band containment as §2c */
	padding: var(--cl-space-5);
}

.cl-drawer__clear {
	margin-top: var(--cl-space-3);
	width: 100%;
}

/* ---- The backdrop ---- */
.cl-drawer__backdrop {
	position: fixed;
	inset: 0;
	z-index: 999;   /* one under the panel, same pairing as .cl-subnav's 20/10 */

	appearance: none;
	padding: 0;
	border: 0;
	background: var(--cl-drawer-backdrop);
	cursor: pointer;

	opacity: 0;
	visibility: hidden;
	transition: opacity    var(--cl-drawer-dur-backdrop) var(--cl-ease),
	            visibility 0s linear var(--cl-drawer-dur-backdrop);
}
.cl-drawer__backdrop.is-open {
	opacity: 1;
	visibility: visible;
	transition: opacity    var(--cl-drawer-dur-backdrop) var(--cl-ease),
	            visibility 0s;
}

/* ---- 10j-ii. THE FILTER CHIPS + TRIGGER ----
   A checkbox, not a div with an onclick — MixItUp's `.filter` divs (still
   live on the other three batch-3 pages until they convert) carry no
   keyboard or screen-reader semantics at all. The input is visually hidden
   rather than `display:none`, so it stays in the tab order and Space toggles
   it; the label is what paints the pill. */
.cl-filter-group {
	border: 0;
	margin: 0 0 var(--cl-space-4);
	padding: 0;
}
.cl-filter-group:last-of-type { margin-bottom: var(--cl-space-4); }

.cl-filter-group__legend {
	padding: 0 0 var(--cl-space-3);
	font-size: var(--cl-text-ui);
	font-weight: var(--cl-weight-bold);
	color: var(--cl-text-brand);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.cl-filter-group__chips {
	display: flex;
	flex-wrap: wrap;
	gap: var(--cl-space-1) var(--cl-space-2);
}

.cl-filter-chip__input {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* 🎨 SHRUNK, Michael 2026-08-13: "reduce in size, clean up the look... looks a
   bit strewn-about." Kept as a checkbox chip rather than a plain link — a
   multi-select filter needs a visible checked/unchecked state a bare link
   can't carry — but sized down a full step (--cl-text-sm, not --cl-text-ui)
   with tighter padding and a hairline border instead of a full 1px stroke's
   worth of visual weight, so a 9-city row reads as one tidy group instead of
   nine separate buttons. */
.cl-filter-chip__label {
	display: inline-flex;
	align-items: center;
	padding: 0.3rem var(--cl-space-3);
	border-radius: var(--cl-radius-pill);
	border: 1px solid var(--cl-filter-chip-border);
	background: var(--cl-filter-chip-bg);
	color: var(--cl-filter-chip-text);
	font-size: var(--cl-text-sm);
	font-weight: var(--cl-weight-semibold);
	/* 🐛 1.2 LOOKED OFF-CENTRE, MEASURED NOT GUESSED — Michael, 2026-08-13,
	   caught the text sitting high in the pill. Montserrat's ascent/descent
	   aren't symmetric around its own line box, so a flex-centred label with
	   line-height:1.2 leaves ~0.85px more room under the text than above it —
	   small, but visible at this scale. Tried 1, 1.05 and `normal` too (the
	   last centres perfectly but grows the pill ~5px taller, undoing the
	   size-down two paragraphs up); 0.9 centres to within 0.02px AND is
	   shorter than 1.2, so both asks are satisfied by the same number. Same
	   root cause is very likely why "a few other places" show the same
	   drift — .btn relies on line-height + padding with no flexbox to
	   compensate, so it inherits the same asymmetry at a smaller magnitude.
	   Not touched here: line-height is one of the two inputs to .btn's
	   documented, precisely-measured height (WP-2.4's 34/38/48px fixes), and
	   changing it site-wide is a bigger, riskier edit than this component
	   warranted on its own — flagging it rather than guessing at a global fix. */
	line-height: 0.9;
	cursor: pointer;
	user-select: none;
	transition: background var(--cl-duration-fast) var(--cl-ease),
	            border-color var(--cl-duration-fast) var(--cl-ease),
	            color var(--cl-duration-fast) var(--cl-ease);
}

/* The ring targets the LABEL, keyed off the input's focus state — the input
   itself is visually hidden, so its own :focus-visible box-shadow would never
   be seen. Same visually-hidden-input pattern as any custom checkbox. */
.cl-filter-chip__input:focus-visible + .cl-filter-chip__label {
	box-shadow: var(--cl-focus-ring);
}

.cl-filter-chip__input:checked + .cl-filter-chip__label {
	background: var(--cl-filter-chip-bg-active);
	border-color: var(--cl-filter-chip-border-active);
	color: var(--cl-filter-chip-text-active);
}

/* The trigger — opens the drawer on directory/directory-members. Dugout and
   HOF have few enough options to sit inline instead (Michael's own call,
   STEP-5-NOTES) and never render this button. */
.cl-filter-trigger {
	display: inline-flex;
	align-items: center;
	gap: var(--cl-space-2);
}

.cl-filter-trigger__count {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 1.25rem;
	height: 1.25rem;
	padding-inline: 0.35em;
	border-radius: var(--cl-radius-pill);
	background: var(--cl-surface);
	color: var(--cl-text-brand);
	font-size: 0.7rem;
	font-weight: var(--cl-weight-bold);
}

/* The row the trigger sits in — Michael: "the filter button can move to the
   top right of the row just before the items list." Also carries the
   live-region announcement of how many results are showing, for anyone not
   watching the grid reflow. */
/* Right-aligned now that the "Become a Part of the Directory" CTA has moved
   into the page head (§10a) as its own button — this row is just the result
   count and the drawer trigger. */
.cl-filter-bar {
	display: flex;
	align-items: center;
	justify-content: flex-end;
	gap: var(--cl-space-4);
	flex-wrap: wrap;
	margin-bottom: var(--cl-space-5);
}

.cl-filter-bar__count {
	color: var(--cl-text-secondary);
	font-size: var(--cl-text-ui);
}

/* The inline row variant — dugout/hof render the chips here directly instead
   of behind a trigger (Michael's call, STEP-5-NOTES: few enough options that
   a drawer is unnecessary overhead). Same .cl-filter-group markup as the
   drawer, just not inside a .cl-drawer, with the result count and Clear
   Filters riding alongside the chips instead of below them. */
.cl-filter-row {
	margin-bottom: var(--cl-space-6);
}
.cl-filter-row .cl-filter-group { margin-bottom: var(--cl-space-3); }
.cl-filter-row .cl-filter-group:last-of-type { margin-bottom: var(--cl-space-3); }

.cl-filter-row__top {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--cl-space-4) var(--cl-space-6);
	flex-wrap: wrap;
}

.cl-filter-row__meta {
	display: flex;
	align-items: center;
	gap: var(--cl-space-3);
	flex-shrink: 0;
	/* Lines up with the chips, not the legend above them. */
	margin-top: 1.9rem;
}


/* ---- 10j-iii. THE GALLERY CARD ----
   Composed as `.cl-store-card.cl-card.cl-directory-card` in markup — §10i's
   shape (bleed photo, title, grow body, floor row) already fits a directory
   listing; this section only adds what §10i doesn't have: badges over the
   photo and a stacked list of contact links instead of a price row.

   🔑 TITLE AND CATEGORY ARE VISIBLE AT REST, ON EVERY CARD, NO MEDIA QUERY
   NEEDED. STEP-5-NOTES flagged hover-reveal card text as a WP-2.7-style risk
   worth a small/large-screen split — but checked against the CURRENT markup
   before rebuilding anything: on all four batch-3 pages the `.label`/
   `.text-title`/`.text-category` hover-reveal (styles-custom.css "HOVER FX")
   is dead. The jQuery `.hover()` handler that ever set their opacity to 1 is
   commented out in every one of directory.php, directory-members.php,
   dugout.php and hof.php, and none of the four PHP files that build the
   `.mix` markup ever emit a `.label`/`.label-text` element at all — so the
   opacity:0 in the CSS has nothing live wired to it and this text has never
   actually appeared, on any input, in the current build. Nothing to port and
   nothing to gate on `hover:hover` — this card just shows its text, the same
   baseline every other reskinned card (§10f, §10h, §10i) already uses. */
/* 🎨 3:2, NOT SQUARE — Michael, 2026-08-13: "a bit more letterbox... suggest a
   ratio that is on brand." §10i's 1:1 is right for a 600×600 product photo
   with a known crop; these are arbitrary CMS-uploaded travel/attraction shots
   (a downtown street, a canyon vista, a stadium bowl), and 3:2 is the classic
   landscape-photography ratio — wide enough to read as editorial rather than
   an app icon, not so wide (16:9) that a portrait-leaning upload loses its
   subject to object-fit:cover. Overrides §10i's aspect-ratio by cascade order
   (same specificity, declared later) rather than a new modifier on §10i
   itself, which store-merch/store-directory still need square. */
.cl-directory-card__media {
	position: relative;
	aspect-ratio: 3 / 2;
}

/* 🎨 REVISION, Michael 2026-08-13, three changes to this card:

   1. THE WHOLE CARD IS NOW ONE CLICK TARGET. `.stretched-link` (Bootstrap
      core) goes on the title <a>; its `::after` covers the nearest
      `position: relative` ancestor, which is `.cl-directory-card` itself —
      so clicking the photo, the tagline, or any empty space now navigates.
      The photo's own (formerly hidden) duplicate link is GONE; it existed
      only to make the photo clickable before the whole card was, and is
      redundant now.
      🔑 THE REAL CONTACT LINKS (View Details / Website / phone / map) STILL
      HAVE TO WIN THE CLICK, not the stretched overlay underneath them. Two
      things make that true: they come AFTER the title in DOM order, so they
      already paint on top in the same stacking context by default — and
      `position: relative` + `z-index: 1` below makes that explicit rather
      than relying on source order alone, per Bootstrap's own stretched-link
      guidance.

   2. THE HOVER STATE MATCHES `.cl-card--tile:hover` (§3), NOT the explore
      band's flat tile. The flat tile's shadow (`0 26px 46px / 40%` + a hard
      amber ring) is tuned for a dark green surface — Michael, re-seeing it on
      a photo card on white: "much darker and much stronger" than intended.
      `--cl-card-lift-hover` is the SAME token the 15 homepage team tiles use,
      already proven right for a light card, and it already lifts
      (translateY(-4px)) — the store-memberships flat tile does too, by
      inheriting `.cl-explore__tile:hover`'s transform; this just uses the
      token built for this card's own surface instead of copying the wrong
      one across.

   3. THE IMAGE ZOOM IS GONE. Michael: "that effect is dated and doesn't
      really add anything." The lift + shadow alone carries the hover. */
.cl-directory-card {
	position: relative;
	transition:
		transform  var(--cl-duration-fast) var(--cl-ease),
		box-shadow var(--cl-duration-fast) var(--cl-ease);
}
.cl-directory-card:hover {
	transform:  translateY(-4px);
	box-shadow: var(--cl-card-lift-hover);
}

.cl-directory-card__links a {
	position: relative;
	z-index: 1;
}

/* The title IS the stretched-link now (markup: `<a class="stretched-link">`
   inside the <h2>) — without this it would inherit nothing, since color is
   set on `.cl-store-card__title` (the <h2>), not on a descendant, and
   styles-custom.css's `:is(body, p, a) { color: var(--cl-text) }` matches the
   <a> directly and beats inheritance regardless of specificity. */
.cl-store-card__title a {
	color: inherit;
	text-decoration: none;
}
/* 🎨 NO HOVER UNDERLINE — Michael, 2026-08-13: "the hover state is enough
   and the underline is redundant." The card's own lift + shadow already
   carries the affordance; keyboard focus still gets a visible ring from the
   global `:focus-visible` rule (tokens.css §8), so removing this loses
   nothing for a non-mouse user. */

/* 🎨 Michael, 2026-08-13: "--cl-card-pad is 1.5rem all around but the bottom
   padding looks better at 0.8rem" — scoped to this card only (directory,
   directory-members, dugout, and hof once it converts), not a change to
   `.cl-card` itself, which store/partner cards still use at the full 1.5rem
   on every side. */
.cl-directory-card {
	padding-bottom: 0.8rem;
}

.cl-directory-card__badges {
	position: absolute;
	inset: 0;
	pointer-events: none;
}

/* One shared shape for both badge kinds — colour is the only thing that
   varies. Built on --cl-scrim-legible (tokens.css §5f), the same
   worst-photo-proofed scrim WP-2.7 derived for exactly this job: text over an
   unpredictable CMS photo. */
.cl-directory-card__badge {
	position: absolute;
	left: 0;
	right: 0;
	padding: 0.3rem var(--cl-space-3);
	background: var(--cl-scrim-legible);
	color: var(--cl-on-brand);
	font-size: 0.7rem;
	font-weight: var(--cl-weight-bold);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	display: flex;
	align-items: center;
	gap: 0.4em;
}
.cl-directory-card__badge--featured {
	top: 0;
	color: var(--cl-amber-500);
}
.cl-directory-card__badge--favorite {
	bottom: 0;
	/* Not a fresh colour choice: --cl-sand-500 is the exact pairing
	   tokens.css §5f derived --cl-scrim-legible's 0.85 alpha against
	   (".badge-CL-favorite" is one of the three named there, and its 0.839
	   requirement is the binding constraint for the whole scrim). */
	color: var(--cl-sand-500);
}

/* Dugout/HOF's category tag(s) — a post can carry more than one, joined into
   ONE badge rather than one div per category. 🐛 THE OLD MARKUP RENDERED A
   SEPARATE `.badge-dugout` PER CATEGORY, absolutely positioned at the same
   bottom-right corner — a post in two categories silently stacked the second
   name directly over the first, illegible, and nobody had noticed because a
   post with 2+ live categories is an edge case that never got eyeballed.
   Fixed at the data layer (imploded before it ever reaches this class), not
   with a layout trick, since the actual defect was building N overlapping
   elements for what is really one line of text. */
/* Dugout only — HOF's year badge is --year below, so this modifier is now
   exclusive to dugout.php and safe to tune without touching the other three
   pages' badges. */
.cl-directory-card__badge--category {
	bottom: 0;
	color: var(--cl-text-inverse);

	/* 🎨 Michael, 2026-08-13: smaller (0.6rem, was the base rule's 0.7rem) and
	   "0.8 opacity." NOT implemented as `opacity: 0.8` on the wrapper —
	   opacity composites the whole subtree together, so the badge TEXT would
	   fade by the same 20% as its background with no way to opt a child back
	   out. The visually identical result (and the one that keeps the text at
	   full legibility, per Michael's own "if there's an easy way... do it")
	   is to put the alpha on the background colour instead: 0.68 is
	   --cl-scrim-legible's own 0.85 × 0.8, i.e. the same composite darkness
	   `opacity: 0.8` would have produced over this background, applied only
	   to the background rather than the whole element.
	   ⚠️ THIS TRADES AWAY SOME OF §5f'S WORST-CASE MARGIN. --cl-scrim-legible's
	   0.85 was derived against a pure-white worst-case photo; 0.68 was not
	   re-derived the same way. Dugout post photos are all mid-toned today and
	   this reads fine against them, but a very bright future upload could
	   come closer to the line than the rest of the badge system allows for.
	   Recorded rather than blocking on it — this is Michael's own call for
	   this one badge, not a site-wide scrim change. */
	font-size: 0.6rem;
	background: rgba(20, 20, 20, 0.68);
}

/* HOF's induction-year badge — same shape as --category, split into its own
   modifier so dugout's font-size/background tuning above cannot leak here. */
.cl-directory-card__badge--year {
	bottom: 0;
	color: var(--cl-text-inverse);
}

.cl-directory-card__tagline {
	margin: 0;
	font-size: var(--cl-text-ui);
	color: var(--cl-text-secondary);
}

/* The contact rows (View Details / Website / Phone / Directions) — a plain
   stack rather than §10i's single price+CTA row, because a listing can carry
   up to four of these and none of them is more "the" action than the others. */
.cl-directory-card__links {
	display: flex;
	flex-direction: column;
	gap: var(--cl-space-2);
	margin-top: auto;
	padding-top: var(--cl-space-3);
	border-top: 1px solid var(--cl-card-hairline);
}
.cl-directory-card__links a {
	display: flex;
	align-items: center;
	gap: var(--cl-space-2);
	color: var(--cl-text);
	font-size: var(--cl-text-ui);
	font-weight: var(--cl-weight-semibold);
}
.cl-directory-card__links a:hover,
.cl-directory-card__links a:focus-visible {
	color: var(--cl-text-brand);
}
.cl-directory-card__links i { color: var(--cl-text-secondary); width: 1.1em; }

/* ---- The results grid + empty state ---- */
.cl-filter-empty {
	padding: var(--cl-space-7) 0;
	text-align: center;
	color: var(--cl-text-secondary);
}

/* FLIP support: js/filter-engine.js applies an inline `transform` for the
   first-paint invert and this class for the "play" frame; the transition is
   declared here rather than inline so prefers-reduced-motion (tokens.css §6)
   can zero it globally instead of the script having to check the media query
   itself. */
[data-filter-item] {
	transition: transform var(--cl-duration-base) var(--cl-ease);
}


/* ==========================================================================
10k. THE BOARD OF TRUSTEES + CONTACT PAGE — hof.php and contact.php

Michael, 2026-08-13: "differentiate with a different background, and give
that section's content some on-brand TLC." The old markup (.contact/
.contactName/.contactTitle/...) was pre-reskin — dated, unstyled by anything
WP-2.10 has touched — and it is retired outright rather than patched;
grabItems still supplies the same six fields (name/title/org/phone/email/
website), just rendered through this new component.

🔑 EXTENDED TO contact.php, 2026-08-21 (Michael: "needs TLC and styling...
use elements from our other layouts") — contact.php read the SAME legacy
.contact/.contactName markup this section already retired once on hof.php,
just never got the same TLC pass. .cl-board__grid/__card/__photo/__name/
__title/__org/__links are unchanged; contact.php does NOT use the
--cl-surface-accent band below (§ "the band"), since that was specifically
for differentiating the Board of Trustees FROM hof.php's surrounding
content — contact.php's sections already sit on `.cl-page-section`'s plain
white, and there's no "surrounding content" to differentiate from.

Not every contact.php row is a named person, though (Spring Training
Inquiries' bare "General"/"Sponsorships" labels, Arizona Office of Tourism's
description+link rows) — those render as .cl-contact-resource instead (this
section, below the board rules), sharing the same grid and the same
icon-link markup, just without a photo circle or the name/title/org stack a
non-person row has nothing to put there.
-------------------------------------------------------------------------- */

/* --cl-surface-accent (sand-100) — the same "secondary surface" token the
   filter chips already use, here at section scale so the band reads as a
   deliberate change of surface rather than a continuation of the white page. */
.cl-board {
	background: var(--cl-surface-accent);
}

.cl-board__grid {
	display: grid;
	gap: var(--cl-space-6) var(--cl-space-5);
	grid-template-columns: 1fr;
	list-style: none;
	margin: 0;
	padding: 0;
}
@media (min-width: 576px) { .cl-board__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 992px) { .cl-board__grid { grid-template-columns: repeat(3, 1fr); } }

.cl-board__card {
	display: flex;
	gap: var(--cl-space-4);
	align-items: flex-start;
}

/* Rounded rectangle, not a circle (Michael, 2026-08-21: "the circle profile
   images don't quite work... try something else on brand") — matches the
   rounded-RECTANGLE photo language the rest of the site's cards already use
   (.cl-partner-card, .cl-directory-card, .cl-store-card), none of which are
   circular. object-position:top is the other half of his ask: every source
   photo here is a 450×600 portrait with the head in the top third, so a
   center-crop (the default) was cutting into hair/forehead as it squared the
   frame off — biasing the crop to the top keeps the head whole and trims the
   chest/shoulders instead. 8.5×10.5rem is Michael's own size, tested in his
   own browser at large screens (2026-08-21 follow-up: "that looks good") —
   close to but not exactly the 450:600 source ratio, so cover still trims a
   little off the sides too; the top-bias is what matters for heads. */
.cl-board__photo {
	width: 8.5rem;
	height: 10.5rem;
	flex-shrink: 0;
	border-radius: var(--cl-radius-md);
	object-fit: cover;
	object-position: top;
	background: var(--cl-surface);
}

.cl-board__body > :first-child { margin-top: 0; }

.cl-board__name {
	margin: 0;
	font-size: var(--cl-text-lead);
	font-weight: var(--cl-weight-bold);
	color: var(--cl-text-brand);
}
.cl-board__title {
	margin: 0;
	font-size: var(--cl-text-ui);
	font-weight: var(--cl-weight-semibold);
	color: var(--cl-text);
}
.cl-board__org {
	margin: 0 0 var(--cl-space-3);
	font-size: var(--cl-text-ui);
	/* 🐛 NOT --cl-text-secondary — measured, not assumed: axe caught this at
	   4.34:1, short of AA, on all six cards. --cl-gray-500 is documented as
	   4.71 on white (tokens.css §2), but .cl-board sits on --cl-surface-accent
	   (sand-100, #f9f5f0) — very slightly darker than white, and enough to
	   drop a colour with no headroom below the line. --cl-gray-700 clears it
	   with real margin rather than trading one near-miss for another. */
	color: var(--cl-gray-700);
}

/* Icon-only, so each link carries its accessible name via aria-label in the
   markup rather than visible text — three icons per card reads cleanly at
   this size; three icon+label rows the way §10j-iii's contact links do it
   would crowd a card this narrow. */
.cl-board__links {
	display: flex;
	gap: var(--cl-space-3);
	margin-top: var(--cl-space-3);
	font-size: 1.1rem;
}
.cl-board__links a {
	color: var(--cl-text-secondary);
}
.cl-board__links a:hover,
.cl-board__links a:focus-visible {
	color: var(--cl-text-brand);
}

/* ---- CONTACT.PHP's resource rows — .cl-contact-resource ----
   A bare label+link ("General" → email) or a description+CTA (Arizona
   Office of Tourism) has no photo and no name/title/org to anchor an
   icon-only link to, so unlike .cl-board__links these stay LABELLED —
   the same "Visit Arizona"/"Fill Out Request Form" text the old markup
   showed, just on-brand instead of the hardcoded red icon it used to be. */
.cl-contact-resource__title {
	margin:      0;
	font-size:   var(--cl-text-lead);
	font-weight: var(--cl-weight-bold);
	color:       var(--cl-text-brand);
}
.cl-contact-resource__desc {
	margin:    var(--cl-space-1) 0 var(--cl-space-3);
	font-size: var(--cl-text-ui);
	/* Same token .cl-board__org uses, for the same reason: --cl-text-secondary
	   measures short of AA here, --cl-gray-700 clears it with real margin. */
	color:     var(--cl-gray-700);
}
.cl-contact-resource__links {
	display:        flex;
	flex-direction: column;
	gap:            var(--cl-space-2);
	margin-top:     var(--cl-space-3);
}
.cl-contact-resource__links a {
	display:         inline-flex;
	align-items:     center;
	gap:             var(--cl-space-2);
	width:           fit-content;
	font-size:       var(--cl-text-ui);
	font-weight:     var(--cl-weight-semibold);
	color:           var(--cl-text-brand);
	text-decoration: none;
}
.cl-contact-resource__links a:hover {
	color:           var(--cl-primary-hover);
	text-decoration: underline;
}
.cl-contact-resource__links a:focus-visible {
	outline:       none;
	border-radius: var(--cl-radius-sm);
	box-shadow:    var(--cl-focus-ring);
}
.cl-contact-resource__links i { color: inherit; }


/* --------------------------------------------------------------------------
10l. THE HALL OF FAME PROFILE — hof-profile.php

The individual inductee page. Michael, 2026-08-14: bring hof.php's grass band up
top — but as a compact masthead: only the kicker, a shortened "Hall of Fame"
<h1>, and the HoF seal to their right, "so we get that little green stripe."
Below it, the 2027 interior language — a portrait-left plaque hero, the .cl-seam
divider, and a stat/achievement grid — replacing the pre-reskin Bootstrap-column
markup this page shipped with (centred rows, .hofIMG, .hof-stat, .logo-divider).

🔑 THE INDUCTEE NAME IS AN <h2>, NOT THE <h1>. The band <h1> is now the static
   "Hall of Fame" (Michael's call), so the person is the primary CONTENT heading
   one level down, and the two career sub-headings are <h3>. That is the honest
   outline for a page whose title names the collection and whose body names one
   member of it.

⚠️ THE TILES ARE .cl-hof-stat, NOT §7d's .cl-stat, AND THE REASON IS CONTRAST.
   .cl-stat paints its figure in amber on the glass fill — a pairing that only
   clears AA-large because it sits on the green economic-impact band. These tiles
   sit on the WHITE interior page, where amber-500 is 3.22:1 and fails outright;
   the figure is --cl-text-brand (green, 8.64) instead. Same shape, surface-correct
   colour — the §7c note on .cl-card vs .cl-glass is this same call one level up.

📌 THE AMBER TICK IS THE THROUGH-LINE. The seam (§10c), the accent under the
   inductee's name, and the mark above each stat figure are all the same 3px amber
   rule — the header/footer is-current underline, reused so the page reads as one
   system rather than three separate ornaments.
-------------------------------------------------------------------------- */

/* --- The masthead: kicker + <h1> exactly where hof.php's landing band puts
   them, plus the HoF seal on the right hanging past the grass into the white.

   🔑 THE HEADINGS STAY IN NORMAL FLOW — .cl-page-head__inner is the landing
   page's inner minus the lede, so the kicker and <h1> land in the identical spot
   (Michael, 2026-08-14: they must not move down). The seal is absolutely
   positioned instead of being a flex sibling, so it CANNOT push the headings
   down or grow the band; it only reserves a right-hand column via the inner's
   padding so the <h1> never runs under it.

   ⚠️ overflow: visible OVERRIDES §10a's `overflow: clip` — deliberately, and it
   is the whole point. Michael wants the seal's bottom to cross the grass edge
   into the content area; a clipped band would slice it off at the seam. The
   grass <img> and scrim are inset:0 at 100%×100%, so they still cannot spill —
   the seal is the only child that overflows, which is the intent. */
.cl-page-head--masthead { overflow: visible; }

/* Reserve the seal's column so the headings never collide with it. The values
   track the seal widths in the media queries below (seal + one gap). */
.cl-page-head--masthead .cl-page-head__inner {
	padding-inline-end: calc(130px + var(--cl-space-6));
}

.cl-page-head__seal {
	position: absolute;
	right:    var(--cl-block-pad-x);
	/* Anchored below the band's floor so it hangs into the white — "over the
	   bottom edge of the grass a little bit." */
	bottom:   calc(-1 * var(--cl-space-5));
	width:    130px;
	height:   auto;
	z-index:  1;   /* above the scrim (which carries no z-index of its own) */
}

/* 225px on large screens — the exact size hof.php's landing band renders the
   seal at (§10a, .cl-page-head__lede-logo), per Michael's "same size as on the
   landing page." The reserved column grows to match. */
@media (min-width: 992px) {
	.cl-page-head__seal { width: 225px; }
	.cl-page-head--masthead .cl-page-head__inner {
		padding-inline-end: calc(225px + var(--cl-space-7));
	}
}

/* On the phone the kicker and <h1> run the FULL width (Michael, 2026-08-14 —
   the reserved column was cramping "HALL OF FAME" onto two lines). The seal is
   no longer beside them: it drops into the bottom-right corner of the band,
   small, overrunning the grass, sitting BELOW the headings rather than next to
   them — so it needs no reserved column and cannot squeeze the text. */
@media (max-width: 575.98px) {
	.cl-page-head--masthead .cl-page-head__inner { padding-inline-end: 0; }
	/* Hidden on the phone — Michael, 2026-08-14: it wasn't sitting right and the
	   masthead reads fine without it, kicker + <h1> running the full width. The
	   seal stays on tablet/desktop (this rule is scoped to the phone breakpoint).
	   The landing band's own logo is a different element (§10a) and unaffected. */
	.cl-page-head__seal { display: none; }
}


/* --- The plaque hero: portrait left; identity, facts AND bio in the right
   column so the bio's left edge lines up with the name and born text above it,
   and its measure runs to the right margin (Michael, 2026-08-14). --- */
.cl-hof-hero {
	display:               grid;
	grid-template-columns:  1fr;
	gap:                   var(--cl-space-6);
	align-items:           start;
}
/* From tablet up the portrait is a fixed-ish left rail (capped at 300px so a
   tall CMS crop cannot dominate) spanning both rows of the right column; the
   meta sits in row 1 and the bio flows beneath it in row 2, beside the
   portrait — which is what fills the whitespace the meta alone used to leave. */
@media (min-width: 768px) {
	.cl-hof-hero {
		grid-template-columns: minmax(200px, 300px) 1fr;
		column-gap:            var(--cl-space-7);
		row-gap:               var(--cl-space-6);
	}
	.cl-hof-hero__portrait { grid-column: 1; grid-row: 1 / span 2; align-self: start; }
	.cl-hof-hero__meta     { grid-column: 2; grid-row: 1; }
	.cl-hof-hero .cl-hof-bio {
		grid-column: 2;
		grid-row:    2;
		margin-top:  0;          /* the grid row-gap supplies the space now */
		max-width:   none;       /* run to the right margin, not the 68ch measure */
	}
}

.cl-hof-hero__portrait {
	margin:        0;
	border-radius: var(--cl-radius-lg);
	overflow:      clip;
	background:    var(--cl-sand-100);   /* shows through a portrait narrower than its frame */
	border:        1px solid var(--cl-card-hairline);
	box-shadow:    var(--cl-shadow-2);
}
.cl-hof-hero__portrait img {
	display: block;
	width:   100%;
	height:  auto;
}

.cl-hof-hero__eyebrow {
	color:         var(--cl-text-brand);   /* the kicker carries no colour of its own — §7b */
	margin-bottom: var(--cl-space-2);
}

.cl-hof-hero__name {
	margin:      0;
	font-family: var(--cl-font-display);
	font-size:   var(--cl-text-h2);
	line-height: var(--cl-leading-tight);
	color:       var(--cl-text);
}

/* The amber accent — same mark as the seam and the stat tiles (see the §10l
   header note). */
.cl-hof-hero__tick {
	display:    block;
	width:      var(--cl-seam-tick-w);
	height:     var(--cl-seam-tick-h);
	background: var(--cl-seam-tick-color);
	margin:     var(--cl-space-4) 0;
}

/* The fact rows (Inducted / Born / Died) as a <dl>. */
.cl-hof-facts {
	display: grid;
	gap:     var(--cl-space-4);
	margin:  0;
}
.cl-hof-facts > div { margin: 0; }
/* One small-caps label shape, shared by the <dl>'s terms and the team block's
   heading so "Born" and "Team Affiliations" read as the same kind of label. */
.cl-hof-facts dt,
.cl-hof-facts__label {
	font-size:      0.75rem;
	font-weight:    var(--cl-weight-bold);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color:          var(--cl-text-secondary);
	margin:         0 0 var(--cl-space-1);
}
.cl-hof-facts dd {
	margin:      0;
	font-size:   var(--cl-text-base);
	color:       var(--cl-text);
	line-height: var(--cl-leading-snug);
}

/* Team affiliations — pill chips in the brand tint, sitting under the facts in
   the identity column rather than as their own section: they say who the player
   WAS, alongside born/inducted, not what they achieved. */
.cl-hof-facts__teams { margin-top: var(--cl-space-4); }
.cl-hof-teams {
	display:    flex;
	flex-wrap:  wrap;
	gap:        var(--cl-space-2);
	list-style: none;
	margin:     0;
	padding:    0;
}
.cl-hof-team {
	font-size:     var(--cl-text-sm);
	font-weight:   var(--cl-weight-semibold);
	color:         var(--cl-text-brand);   /* 8.64 on the green-100 chip */
	background:    var(--cl-green-100);
	border:        1px solid var(--cl-green-150);
	border-radius: var(--cl-radius-pill);
	padding:       0.35em 0.85em;
	line-height:   1.2;
}

/* The bio, full width beneath the hero, capped at the reading measure like every
   other body block (§10e). */
.cl-hof-bio {
	margin-top: var(--cl-space-6);
}
.cl-hof-bio > :last-child { margin-bottom: 0; }


/* The seam here divides two blocks INSIDE one .cl-page-section (bio ▸ stats),
   not two sections that each own 4rem of padding — so §10c's `margin: 0` would
   leave it hugging both. This is the one place the seam supplies its own rhythm;
   scoped to #profile so §10c's between-sections contract is untouched. */
#profile .cl-seam { margin-block: var(--cl-space-7); }

/* --- The stats / achievements grid --- */
.cl-hof-stats {
	display:               grid;
	grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
	gap:                   var(--cl-space-4);
	list-style:            none;
	margin:                var(--cl-space-5) 0 0;
	padding:               0;
}

/* Each tile is a .cl-card (white + hairline + radius) in the markup; this adds
   the internal stack and the amber tick. */
.cl-hof-stat {
	display:        flex;
	flex-direction: column;
	gap:            var(--cl-space-2);
}
.cl-hof-stat::before {
	content:       "";
	display:       block;
	width:         2rem;
	height:        var(--cl-seam-tick-h);
	background:    var(--cl-seam-tick-color);
	margin-bottom: var(--cl-space-1);
}
.cl-hof-stat__value {
	font-family:          var(--cl-font-display);
	font-weight:          var(--cl-weight-bold);
	font-size:            clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
	line-height:          1.05;
	color:                var(--cl-text-brand);   /* green, not §7d's amber — see header note */
	font-variant-numeric: tabular-nums;
}
.cl-hof-stat__label {
	font-size:   var(--cl-text-sm);
	color:       var(--cl-text-secondary);
	line-height: var(--cl-leading-snug);
}


/* ==========================================================================
STADIUM.PHP — WP-2.10 step 5b tooling round, Michael 2026-08-18. Three rows on
the plain white page (the raised card he liked came OUT of the main content —
"not consistent with the rest of the website" — and is reused for the ballpark
nav in row 3): row 1 is a 3-column logo · description · links; row 2 is the
2026-recap video placeholder beside the map, each in an on-brand matte frame;
row 3 is all ten ballparks as a nav inside the raised card.
========================================================================== */

/* ---- ROW 1: the stadium logo ----
   Michael, 2026-08-18: "make the logo a little bigger" — this is now the left
   column of a three-column row (logo · description · links), which is what
   bought the room. Capped so a wide SVG mark can't outgrow its column but a
   tall one gets real presence; centred within the narrow column so it reads as
   a mark, not a flush-left image. */
.cl-page-logo {
	display:    block;
	width:      100%;
	max-width:  180px;
	height:     auto;
}
@media (max-width: 767.98px) {
	/* Below the 3-col breakpoint the column is full width; keep the mark from
	   ballooning across it. */
	.cl-page-logo { max-width: 140px; }
}

/* ---- ROW 2: the on-brand media frame ----
   Michael, 2026-08-18: "add a frame around the map... similar to our hof or
   dugout landing items. No hover, but something that creates a border that is
   on brand." The site's own frame language is the beige page matte
   (--cl-surface-card, tokens.css §ledger: "the card matte — a 10px frame, no
   text") over the sand hairline — reused here as a small padded surround so the
   map and the video placeholder each sit in a concentric matte, the same look
   the interior photo figures and landing cards carry. No :hover, by request. */
.cl-media-frame {
	padding:       var(--cl-space-2);
	background:    var(--cl-surface-card);
	border:        1px solid var(--cl-card-hairline);
	border-radius: var(--cl-radius-lg);
}

/* The video placeholder — no CMS field for this yet (Michael, 2026-08-18: "I
   may be able to get videos for the 2026 recap for each stadium"), so this is
   an honest empty state, not a fake thumbnail. Sits inside a Bootstrap
   `.ratio` box exactly like the map beside it: `.ratio > *` gives this element
   position:absolute + 100% width/height for free, so flex alone centres the
   icon and caption. The matte frame around it supplies the border now, so this
   just carries the sunken fill that reads as "empty, coming soon." */
.cl-media-placeholder {
	display:         flex;
	flex-direction:  column;
	align-items:     center;
	justify-content: center;
	gap:             var(--cl-space-2);
	text-align:      center;
	color:           var(--cl-text-secondary);
	background:      var(--cl-surface-sunken);
	border-radius:   var(--cl-radius-md);
	padding:         var(--cl-space-4);
}
.cl-media-placeholder p {
	margin:    0;
	font-size: var(--cl-text-sm);
}

/* The ballpark hero photo — Michael, 2026-09-03: the 2026 recap videos aren't
   happening, so a still photo fills this slot. It sits in the same 16:9 `.ratio`
   box as the map, so Bootstrap gives it position:absolute + 100%/100%;
   object-fit:cover crops the photo to the frame rather than stretching it. */
.cl-media-photo {
	width:         100%;
	height:        100%;
	object-fit:    cover;
	border-radius: var(--cl-radius-md);
}

/* The per-park map's mount point. `--cl-surface-sunken` shows while the Maps
   tiles are still loading — same idea as the video placeholder's fill, so the
   two halves of this row read as one system before either has content. */
.cl-stadium-map {
	border-radius: var(--cl-radius-md);
	overflow:      clip;
	background:    var(--cl-surface-sunken);
}

/* ---- ROW 3: the ballpark nav, in the raised card ----
   Michael, 2026-08-18: the raised-card element he liked, reused here (off the
   main content, which went plain) to hold all ten ballparks as a nav —
   "logos and names as a ballpark nav element." The card is `.cl-card
   .cl-card--raised` in markup; this only adds the internal layout. */
.cl-ballpark-nav-card {
	padding: var(--cl-card-pad);
}
.cl-ballpark-nav__heading {
	font-family:    var(--cl-font-display);
	font-weight:    var(--cl-weight-bold);
	font-size:      var(--cl-text-h4, 1.25rem);
	letter-spacing: -0.01em;
	color:          var(--cl-text-brand);
	margin:         0 0 var(--cl-space-5);
}
/* A responsive grid of ten tiles. Michael, 2026-08-18: "fit all 10 on one row
   on large screens" — so this is an EXPLICIT track count per breakpoint, not
   auto-fill: two across on a phone, five on a tablet, and all ten in a single
   row from 992px up. Ten 1fr tracks in the ~1156px card interior give each tile
   ~100px, which clears the 88px chip; the longest names wrap to a few lines and
   the grid keeps every tile's chip top-aligned. */
.cl-ballpark-nav {
	list-style: none;
	margin:     0;
	padding:    0;
	display:               grid;
	grid-template-columns: repeat(2, 1fr);
	gap:                   var(--cl-space-4);
}
/* 🐛 min-width:0 ON THE GRID ITEMS, OR A TRACK GROWS PAST ITS 1fr AND OVERFLOWS.
   A grid item defaults to `min-width: auto`, which refuses to shrink below its
   content's min size — so the you-are-here tile, whose three-line name is the
   widest content in the row, pushed its own track ~4px past the 1fr share and
   the whole ten-track row overran the card by ~38px (measured; the page didn't
   scroll only because the card clipped it). Zeroing it lets the track hold its
   1fr and the name wrap inside it. Same grid-item trap §10f documents. */
.cl-ballpark-nav > li,
.cl-ballpark-nav__item { min-width: 0; }
@media (min-width: 576px) {
	.cl-ballpark-nav { grid-template-columns: repeat(5, 1fr); }
}
/* All ten in one row — Michael, 2026-08-18. ⚠️ AT 1200px, NOT 992px: the raised
   card's inner width tracks the window below the ~1320px site cap, so at 992 a
   ten-track row leaves each tile ~76px — narrower than an 88px chip, which
   clips the tenth tile off the card edge (seen on the render). At 1200 the card
   interior is ~1036px, giving ~96px tracks that clear the shrunk 72px chip and
   the longest single name-word with room. Between 992 and 1199 it stays the
   five-across tablet grid. The chip and gap shrink here so ten fit without the
   names crushing. */
@media (min-width: 1200px) {
	.cl-ballpark-nav {
		grid-template-columns: repeat(10, 1fr);
		gap: var(--cl-space-2);
	}
	.cl-ballpark-nav__item { padding: var(--cl-space-2) var(--cl-space-1); }
	.cl-ballpark-nav__logo { width: 72px; height: 72px; }
	.cl-ballpark-nav__logo img { width: 52px; height: 52px; }
}
/* Each tile: a white logo chip (the same white-surface-under-dark-artwork call
   .cl-footer__team makes, §3) above the park name. The whole tile is the link. */
.cl-ballpark-nav__item {
	display:         flex;
	flex-direction:  column;
	align-items:     center;
	gap:             var(--cl-space-3);
	text-align:      center;
	padding:         var(--cl-space-3);
	border-radius:   var(--cl-radius-md);
	text-decoration: none;
	color:           var(--cl-text);
	transition: transform var(--cl-duration-fast) var(--cl-ease),
	            box-shadow var(--cl-duration-fast) var(--cl-ease),
	            background var(--cl-duration-fast) var(--cl-ease);
}
.cl-ballpark-nav__logo {
	display:         flex;
	align-items:     center;
	justify-content: center;
	width:           88px;
	height:          88px;
	border-radius:   var(--cl-radius-md);
	background:      var(--cl-white);
	box-shadow:      0 0 0 1px var(--cl-card-hairline);
}
.cl-ballpark-nav__logo img {
	width:      64px;
	height:     64px;
	object-fit: contain;
}
.cl-ballpark-nav__name {
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-semibold);
	line-height: var(--cl-leading-snug);
}
.cl-ballpark-nav__item:hover {
	transform:  translateY(-2px);
	background:  var(--cl-surface-sunken);
	box-shadow: var(--cl-card-lift-hover);
}
.cl-ballpark-nav__item:focus-visible {
	transition: none;
	box-shadow: var(--cl-focus-ring);
}
/* You-are-here. The name goes brand-green and the logo chip takes an amber ring
   — the same "state is not colour-only" pairing the footer team chip uses
   (§3), and the markup carries aria-current="page" alongside it (WCAG 1.4.1). */
.cl-ballpark-nav__item.is-current {
	cursor: default;
}
.cl-ballpark-nav__item.is-current .cl-ballpark-nav__name {
	color: var(--cl-text-brand);
}
.cl-ballpark-nav__item.is-current .cl-ballpark-nav__logo {
	box-shadow: 0 0 0 2px var(--cl-amber-500);
}


/* 🎨 TEAM SOCIAL, RELOCATED INTO THE GRASS BAND — WP-3.5 (Michael, 2026-09-04:
   move the social icons up into the glass lede). The team.php action row that
   used to hold them (old §10m) was removed entirely the same day ("remove that
   entire row, no need"), so this is now the ONLY home for the team's social set.
   Re-tuned for the dark band: the lede text is --cl-on-brand
   (light over the scrim, §5i), so the icons inherit that light colour instead of
   the on-white brand green, brightening to white on hover. It sits in the lede's
   right column, under the fun-facts blurb + the "Spring Training home at …"
   line — a small top margin sets it apart from the tagline above it. Icons are a
   touch smaller than the old action-row set (1.5rem vs 1.6rem) to sit right at
   lede scale. 3.80:1 on the scrim clears the 3:1 UI-component bar; each link
   keeps its platform+team accessible name (team.php). */
.cl-page-head__social {
	display:     flex;
	align-items: center;
	gap:         0;   /* the icons sit close as a set; each link's padding is the gap */
	font-size:   1.5rem;
	line-height: 1;
	margin-top:  var(--cl-space-2);
}
.cl-page-head__social a {
	display:          inline-flex;
	align-items:      center;
	justify-content:  center;
	padding:          var(--cl-space-1);
	color:            var(--cl-on-brand);
	text-decoration:  none;   /* icon links carry no underline (Michael, 2026-09-04) */
	transition:       color var(--cl-duration-fast) var(--cl-ease),
	                  transform var(--cl-duration-fast) var(--cl-ease);
}
.cl-page-head__social a:hover {
	color:     var(--cl-white);
	transform: translateY(-2px);
}
.cl-page-head__social a:focus-visible {
	transition:    none;
	outline:       none;
	border-radius: var(--cl-radius-sm);
	box-shadow:    var(--cl-focus-ring);
}


/* ===========================================================================
   §11. SCHEDULE PAGE (WP-3.3) — list + calendar views

   The flagship 2027 page. Server-rendered from the api/v1/schedule.php contract
   (season auto-detect → Games2027 placeholder), progressively enhanced. Parts
   live here: the data-derived SEASON BANNER (§11a), the DATE GROUPS that carry
   the day headers (§11b), the reusable GAME ROW (§11c) — one row component
   shared by schedule / team / my-schedule — the FILTER BAR (§11d) + its CHIPS
   (§11e) + the SAVED-GAMES PILL (§11f), the List|Calendar VIEW TOGGLE (§11g,
   phase 5), the full-season CALENDAR month grid itself (§11h, phase 5), and
   its Fancybox DAY POPUP (§11i, phase 5 revision).

   Distinct from §8c's `.cl-schedule-card` (the homepage Opening-Week teaser): a
   teaser is a single-line matchup, this is the real page with away-over-home,
   status, a heart, and a Tickets control. Different job, different component.
   =========================================================================== */

/* ---- 11a. SEASON BANNER ----
   Three states, ALL derived from the loaded schedule, NEVER a hardcoded date
   (the rule ported straight from the app's SeasonBannerView):
     · upcoming → "{year} Spring Training starts {date}" + N days to first pitch
     · ended    → "That's a wrap on {year} Spring Training"
     · inSeason → not rendered at all (PHP emits nothing)
   The countdown number is the loud element; everything else is supporting. */
.cl-season-banner {
	display:        flex;
	flex-wrap:      wrap;
	align-items:    center;
	gap:            var(--cl-space-4) var(--cl-space-6);
	background:     var(--cl-green-500);
	border-radius:  var(--cl-radius-lg);
	padding:        var(--cl-space-5) var(--cl-space-6);
	color:          var(--cl-white);            /* 8.64 on green-500 */
}

.cl-season-banner__text { flex: 1 1 16rem; min-width: 0; }

.cl-season-banner__kicker {
	margin:         0 0 var(--cl-space-1);
	font-size:      var(--cl-kicker-size-sm);
	font-weight:    var(--cl-weight-bold);
	letter-spacing: var(--cl-kicker-tracking-sm);
	text-transform: uppercase;
	color:          var(--cl-amber-500);        /* 4.62 on green-500 */
}

.cl-season-banner__title {
	margin:      0;
	font-family: var(--cl-font-display);
	font-weight: var(--cl-weight-bold);
	font-size:   var(--cl-text-h4);
	line-height: 1.15;
	color:       var(--cl-white);
}

.cl-season-banner__sub {
	margin:    var(--cl-space-2) 0 0;
	font-size: var(--cl-text-lead);
	color:     var(--cl-gray-350);              /* 11.70 on black; reads calm on green */
}

/* The countdown. A big amber number + a small unit label under it. */
.cl-season-banner__count {
	flex:        0 0 auto;
	text-align:  center;
	line-height: 1;
}
.cl-season-banner__days {
	display:     block;
	font-family: var(--cl-font-display);
	font-weight: var(--cl-weight-extrabold);
	font-size:   clamp(2.5rem, 1.8rem + 3vw, 3.75rem);
	color:       var(--cl-amber-500);
	font-variant-numeric: tabular-nums;
}
.cl-season-banner__days-label {
	display:        block;
	margin-top:     var(--cl-space-1);
	font-size:      var(--cl-text-sm);
	font-weight:    var(--cl-weight-bold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color:          var(--cl-white);
}

/* ---- 11b. DATE GROUPS (collapsible) ----
   A full season is ~220 games, which as a flat list is an endless page. So each
   date is a native <details> disclosure: the day bar is the <summary>, the games
   sit inside. Everything is still SERVER-RENDERED and stays in the DOM when
   collapsed (crawlable), and the whole thing works with NO JS — <details> is a
   browser primitive. The smart-default day is open on load; the Expand-all
   control is there for anyone who wants the whole season in one scroll. */
.cl-sched { margin: 0; }

.cl-sched__controls {
	display:         flex;
	justify-content: flex-end;
	margin-bottom:   var(--cl-space-3);
}

.cl-sched__group {
	background:    var(--cl-white);
	border:        1px solid var(--cl-sand-200);
	border-radius: var(--cl-radius-md);
	margin-bottom: var(--cl-space-3);
	overflow:      hidden;
}

/* The day bar = the clickable summary. The default disclosure triangle is
   removed in favour of our own chevron, which rotates when the day is open. */
.cl-sched__daybar {
	list-style:  none;
	cursor:      pointer;
	display:     flex;
	align-items: center;
	gap:         var(--cl-space-3);
	padding:     var(--cl-space-3) var(--cl-space-4);
	background:  var(--cl-sand-100);
}
.cl-sched__daybar::-webkit-details-marker { display: none; }
.cl-sched__daybar::marker { content: ''; }
/* Ring drawn with outline-offset inside the group's overflow:hidden, or a
   box-shadow ring would be clipped at the rounded corners. */
.cl-sched__daybar:focus-visible {
	outline:        2px solid var(--cl-focus-outer);
	outline-offset: -2px;
}
.cl-sched__group[open] .cl-sched__daybar { border-bottom: 1px solid var(--cl-sand-200); }

.cl-sched__date {
	font-family: var(--cl-font-display);
	font-weight: var(--cl-weight-bold);
	font-size:   var(--cl-text-h5);
	color:       var(--cl-text-brand);       /* 8.64 on white */
}
.cl-sched__count {
	margin-left: auto;
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-semibold);
	color:       var(--cl-text-secondary);   /* 4.71 on sand-100 */
	white-space: nowrap;
}
.cl-sched__chev {
	color:      var(--cl-brown-400);
	transition: transform var(--cl-duration-fast) var(--cl-ease);
}
.cl-sched__group[open] .cl-sched__chev { transform: rotate(180deg); }

/* "TODAY" / "NEXT GAMES" tag on the smart-default anchor day. */
.cl-sched__today-tag {
	font-size:      var(--cl-text-xs);
	font-weight:    var(--cl-weight-bold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color:          var(--cl-white);
	background:     var(--cl-orange-700);       /* AA 5.67 */
	border-radius:  var(--cl-radius-pill);
	padding:        0.15rem var(--cl-space-3);
}

.cl-sched__games {
	list-style: none;
	margin:     0;
	padding:    0 var(--cl-space-4);
}

/* ---- 11c. GAME ROW ----
   Away over home (home bolded), logos at the left of each line; the right side
   carries time, venue short name, and a status badge; heart + Tickets are their
   own controls. Flex-wrap does the reflow: on a wide row the meta pushes right
   and the actions sit at the end; when the row runs out of width (phone, or a
   big browser text-zoom) the matchup takes the full width and meta + actions
   drop to a second line — the same stacked shape the app falls back to at
   accessibility text sizes, where a two-column row shreds into single letters. */
.cl-game {
	display:       flex;
	flex-wrap:     wrap;
	align-items:   center;
	gap:           var(--cl-space-3) var(--cl-space-4);
	margin:        0;
	padding:       var(--cl-space-3) 0;
	border-top:    1px solid var(--cl-sand-200);
}
.cl-sched__games .cl-game:first-child { border-top: 0; }

/* Past games: dimmed, no scores — decision #10. The whole row is muted; the
   Final badge (§below) carries the state to screen readers, colour never alone. */
.cl-game--final { opacity: 0.62; }

.cl-game__matchup {
	flex:      1 1 14rem;
	min-width: 0;
	display:   grid;
	gap:       var(--cl-space-2);
}
.cl-game__team {
	display:     flex;
	align-items: center;
	gap:         var(--cl-space-2);
	min-width:   0;
}
.cl-game__logo {
	width:      1.75rem;   /* 28px — the app's 24pt logo, a hair larger for the web */
	height:     1.75rem;
	object-fit: contain;
	flex:       0 0 auto;
}
/* Home team reads bold; the away team sits in the row's normal weight. */
.cl-game__name {
	font-size:     var(--cl-text-base);
	color:         var(--cl-ink);
	overflow:      hidden;
	text-overflow: ellipsis;
	white-space:   nowrap;
}
.cl-game__team--home .cl-game__name { font-weight: var(--cl-weight-bold); }
.cl-game__at {
	flex:        0 0 auto;
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-bold);
	color:       var(--cl-text-secondary);
	text-transform: lowercase;
}

/* SPLIT badge — decision #6. Sits on the team that is splitting its squad. */
.cl-game__split {
	flex:           0 0 auto;
	font-size:      var(--cl-text-xs);
	font-weight:    var(--cl-weight-bold);
	letter-spacing: 0.06em;
	color:          var(--cl-green-600);
	background:     var(--cl-green-100);
	border-radius:  var(--cl-radius-sm);
	padding:        0.1rem 0.4rem;
}

.cl-game__meta {
	flex:        0 0 auto;
	margin-left: auto;                  /* pushes right on a wide row */
	display:     flex;
	align-items: center;
	gap:         var(--cl-space-3);
	text-align:  right;
}
.cl-game__time {
	font-size:   var(--cl-text-base);
	font-weight: var(--cl-weight-bold);
	color:       var(--cl-text-brand);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}
.cl-game__venue {
	font-size:     var(--cl-text-sm);
	font-weight:   var(--cl-weight-semibold);
	color:         var(--cl-gray-700);
	max-width:     10rem;
	overflow:      hidden;
	text-overflow: ellipsis;
	white-space:   nowrap;
}

/* Status badges. Final = muted grey (past); cancelled/postponed = brand orange
   ("needs attention") — the latter light up when the CMS grid can set status. */
.cl-game__badge {
	font-size:      var(--cl-text-xs);
	font-weight:    var(--cl-weight-bold);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	border-radius:  var(--cl-radius-sm);
	padding:        0.15rem 0.45rem;
	white-space:    nowrap;
}
.cl-game__badge--final    { color: var(--cl-gray-700); background: var(--cl-gray-200); }
.cl-game__badge--attention{ color: var(--cl-white);    background: var(--cl-orange-700); }

/* HOME / AWAY — team-page only (asset-schedule-view.php, WP-3.5). Renders ONLY
   when the schedule is locked to a team, marking that team's role in each
   matchup. Same pill sizing as the status badge; it leads the meta cluster (by
   the venue/time, since Home = plays at its own park). Home reads in brand green,
   Away in a muted neutral so the two are distinguishable at a glance. */
.cl-game__homeaway {
	flex:           0 0 auto;
	font-size:      var(--cl-text-xs);
	font-weight:    var(--cl-weight-bold);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	border-radius:  var(--cl-radius-sm);
	padding:        0.15rem 0.45rem;
	white-space:    nowrap;
}
.cl-game__homeaway--home { color: var(--cl-white);    background: var(--cl-primary); }
.cl-game__homeaway--away { color: var(--cl-gray-700); background: var(--cl-gray-200); }

.cl-game__actions {
	flex:        0 0 auto;
	display:     flex;
	align-items: center;
	gap:         var(--cl-space-3);
}

/* Save — its own control, its own accessible name. A bookmark (not a heart:
   the heart is the app's mark, and the two products stay deliberately distinct).
   Outline bookmark by default → solid + brand green once the game is in the plan.
   ⚠️ WP-3.3 phase 2 renders the control; persistence to localStorage is phase 4
   (saved games → /my-schedule.php). The toggle here is visual-only for review. */
.cl-game__save {
	appearance:  none;
	border:      0;
	background:  none;
	cursor:      pointer;
	font-size:   1.25rem;
	line-height: 1;
	padding:     var(--cl-space-1);
	color:       var(--cl-sand-600);
	transition:  color var(--cl-duration-fast) var(--cl-ease),
	             transform var(--cl-duration-fast) var(--cl-ease);
}
.cl-game__save:hover { color: var(--cl-green-500); transform: translateY(-1px); }
.cl-game__save[aria-pressed="true"] { color: var(--cl-green-500); }
.cl-game__save:focus-visible {
	transition:    none;
	outline:       none;
	border-radius: var(--cl-radius-sm);
	box-shadow:    var(--cl-focus-ring);
}

/* Tickets — the amber CTA, small. (Per-stadium URL + UTM wiring is phase 6.) */
.cl-game__tickets { white-space: nowrap; }

/* Reflow: once a row can't hold all three cells, the matchup takes the full
   width and meta + actions share the line beneath it. */
@media (max-width: 575.98px) {
	.cl-game__matchup { flex-basis: 100%; }
	.cl-game__meta    { margin-left: 0; }
	.cl-game__actions { margin-left: auto; }
	.cl-game__venue   { max-width: 7rem; }
}


/* ---- 11d. FILTER BAR (WP-3.3 phase 3) ----
   Inline (not off-canvas — that stays for Dugout / HOF / Directory), and sticky
   on scroll so the controls stay reachable down a long list. Team + Stadium are
   native <select>s and a real hide-past checkbox inside a GET <form>, so filters
   work with NO JS and produce shareable, crawlable ?team= / ?stadium= URLs; JS
   only upgrades them to submit-on-change. */
.cl-filterbar {
	/* One shared control height so every control's box bottom — and its text
	   baseline — lines up across the bar, with the two select labels floating
	   above. This is what keeps the count on the same line as "Hide past games". */
	--cl-filter-h: 2.75rem;

	position:      sticky;
	top:           0;
	z-index:       20;
	display:       flex;
	flex-wrap:     wrap;
	align-items:   flex-end;
	gap:           var(--cl-space-3) var(--cl-space-4);
	margin-bottom: var(--cl-space-4);
	padding:       var(--cl-space-4);
	background:    var(--cl-sand-100);
	border:        1px solid var(--cl-sand-300);
	border-radius: var(--cl-radius-md);
}

.cl-filterbar__field { display: flex; flex-direction: column; gap: var(--cl-space-2); min-width: 0; }
.cl-filterbar__label {
	padding-left:   2px;
	font-size:      var(--cl-text-xs);
	font-weight:    var(--cl-weight-bold);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color:          var(--cl-text-secondary);
}

/* Selects: native chrome removed for a consistent box + our own chevron, so the
   two dropdowns match each other in every browser. */
.cl-filterbar__select {
	appearance:         none;
	-webkit-appearance: none;
	height:             var(--cl-filter-h);
	font-family:        var(--cl-font-body);
	font-size:          var(--cl-text-sm);
	font-weight:        var(--cl-weight-semibold);
	color:              var(--cl-ink);
	background-color:   var(--cl-white);
	background-image:   url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%2374675a' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
	background-repeat:  no-repeat;
	background-position: right var(--cl-space-3) center;
	border:             1px solid var(--cl-sand-350);
	border-radius:      var(--cl-radius-sm);
	padding:            0 var(--cl-space-6) 0 var(--cl-space-3);   /* room for the chevron */
	min-width:          11rem;
	cursor:             pointer;
}
.cl-filterbar__select:hover { border-color: var(--cl-sand-500); }
.cl-filterbar__select:focus-visible {
	outline:      none;
	border-color: var(--cl-green-500);
	box-shadow:   var(--cl-focus-ring);
}

/* Hide-past — a real checkbox so it works without JS; JS persists it per device.
   Same height as the selects so it sits on their baseline, no nudge hacks. */
.cl-filterbar__check {
	display:     inline-flex;
	align-items: center;
	gap:         var(--cl-space-2);
	height:      var(--cl-filter-h);
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-semibold);
	color:       var(--cl-ink);
	cursor:      pointer;
}
.cl-filterbar__check input { width: 1.1rem; height: 1.1rem; accent-color: var(--cl-green-500); cursor: pointer; }

/* The no-JS submit; hidden once the JS controller wires change → fetch-and-swap
   (it adds .is-enhanced). No-JS keeps a real submit button. */
.cl-filterbar__go { align-self: flex-end; }
.cl-filterbar.is-enhanced .cl-filterbar__go { display: none; }

/* Count of what's showing — pushed right, same height so it shares the baseline. */
.cl-filterbar__count {
	margin-left: auto;
	height:      var(--cl-filter-h);
	display:     inline-flex;
	align-items: center;
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-semibold);
	color:       var(--cl-text-secondary);
	white-space: nowrap;
}

/* ---- 11e. ACTIVE FILTER CHIPS ----
   Each active filter as a removable pill; the ✕ links to the same page with that
   one filter dropped, so it works with no JS too. */
.cl-filterchips {
	display:       flex;
	flex-wrap:     wrap;
	align-items:   center;
	gap:           var(--cl-space-2);
	margin-bottom: var(--cl-space-4);
}
.cl-filterchips__label {
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-semibold);
	color:       var(--cl-text-secondary);
}
.cl-filterchip {
	display:         inline-flex;
	align-items:     center;
	gap:             var(--cl-space-2);
	font-size:       var(--cl-text-sm);
	font-weight:     var(--cl-weight-semibold);
	color:           var(--cl-white);
	background:      var(--cl-green-500);
	border-radius:   var(--cl-radius-pill);
	padding:         0.2rem 0.4rem 0.2rem var(--cl-space-3);
	text-decoration: none;
}
.cl-filterchip:hover { background: var(--cl-green-600); color: var(--cl-white); }
.cl-filterchip__x {
	display:         inline-flex;
	align-items:     center;
	justify-content: center;
	width:           1.15rem;
	height:          1.15rem;
	border-radius:   var(--cl-radius-pill);
	background:      rgb(255 255 255 / 18%);
	font-size:       var(--cl-text-xs);
	line-height:     1;
}
.cl-filterchip:focus-visible { outline: none; box-shadow: var(--cl-focus-ring); }
.cl-filterchips__clear {
	font-size:       var(--cl-text-sm);
	font-weight:     var(--cl-weight-bold);
	color:           var(--cl-text-brand);
	text-decoration: underline;
	margin-left:     var(--cl-space-2);
}

/* ---- 11d-mobile. Under 576px the controls simply stack full-width ----
   Same controls, one location — Team, Stadium, Hide-past, then the count, each on
   its own row. 16px selects so iOS doesn't zoom the page on focus and the option
   list stays legible. */
@media (max-width: 575.98px) {
	.cl-filterbar__field {
		flex:      1 1 100%;
	}
	.cl-filterbar__select {
		width:     100%;
		min-width: 0;
		height:    3rem;         /* comfortable touch target */
		font-size: 1rem;         /* 16px — legible options, no iOS zoom */
	}
	.cl-filterbar__select option { font-size: 1rem; }
	.cl-filterbar__check {
		flex:      1 1 100%;
		height:    auto;
		font-size: 1rem;
	}
	.cl-filterbar__go    { flex: 1 1 100%; align-self: stretch; }
	.cl-filterbar__count {
		flex:        1 1 100%;
		height:      auto;
		margin-left: 0;
	}
}

/* ---- 11f. SAVED-GAMES PILL (WP-3.3 phase 4) ----
   Entry point into My Schedule, living in the filter bar next to the count
   (SCHEDULE-SPEC §3 diagram). Hidden by default — js/schedule-favorites.js
   un-hides it once at least one game is saved; a visitor with nothing saved,
   or no JS, has nothing to follow it to. */
.cl-filterbar__saved {
	display:         inline-flex;
	align-items:     center;
	gap:             var(--cl-space-2);
	height:          var(--cl-filter-h);
	padding:         0 var(--cl-space-4);
	border-radius:   var(--cl-radius-pill);
	background:      var(--cl-green-500);
	color:           var(--cl-white);              /* 8.64 on green-500 */
	font-size:       var(--cl-text-sm);
	font-weight:     var(--cl-weight-semibold);
	text-decoration: none;
	white-space:     nowrap;
	transition:      background var(--cl-duration-fast) var(--cl-ease);
}
.cl-filterbar__saved:hover,
.cl-filterbar__saved:focus-visible { background: var(--cl-green-600); color: var(--cl-white); }
.cl-filterbar__saved:focus-visible { outline: none; box-shadow: var(--cl-focus-ring); }
.cl-filterbar__saved i { color: var(--cl-amber-500); }
.cl-filterbar__saved-arrow { color: var(--cl-gray-350); font-weight: var(--cl-weight-normal); }
@media (max-width: 575.98px) {
	.cl-filterbar__saved { flex: 1 1 100%; justify-content: center; height: auto; padding: var(--cl-space-3) var(--cl-space-4); }
}

/* ---- 11g. VIEW TOGGLE — List | Calendar (WP-3.3 phase 5) ----
   SCHEDULE-SPEC §3 diagram. A segmented control, but the two options are
   plain links to real ?view= URLs, not a JS-only switch — a view change is a
   deliberate mode switch (unlike a filter tweak mid-scroll), so a real
   navigation to the top of the results is the right behaviour here, not a
   fetch-and-swap. aria-current="page" marks the active option (there's no
   ARIA "pressed link" role; a link to the current state is exactly what
   aria-current describes). */
.cl-viewtoggle {
	display:       inline-flex;
	align-items:   center;
	height:        var(--cl-filter-h);
	padding:       0.25rem;
	gap:           0.15rem;
	background:    var(--cl-sand-200);
	border-radius: var(--cl-radius-pill);
}
.cl-viewtoggle__btn {
	display:         inline-flex;
	align-items:     center;
	gap:             var(--cl-space-2);
	height:          100%;
	padding:         0 var(--cl-space-4);
	border-radius:   var(--cl-radius-pill);
	font-size:       var(--cl-text-sm);
	font-weight:     var(--cl-weight-semibold);
	color:           var(--cl-text-secondary);
	text-decoration: none;
	white-space:     nowrap;
	transition:      background var(--cl-duration-fast) var(--cl-ease),
	                 color      var(--cl-duration-fast) var(--cl-ease);
}
.cl-viewtoggle__btn:hover { color: var(--cl-ink); }
.cl-viewtoggle__btn[aria-current="page"] {
	background: var(--cl-white);
	color:      var(--cl-text-brand);
	box-shadow: 0 1px 2px rgb(0 0 0 / 12%);
}
.cl-viewtoggle__btn:focus-visible { outline: none; box-shadow: var(--cl-focus-ring); }
@media (max-width: 575.98px) {
	.cl-viewtoggle       { flex: 1 1 100%; height: 3rem; }
	.cl-viewtoggle__btn  { flex: 1 1 50%; justify-content: center; }
}

/* ---- 11h. CALENDAR VIEW — full-season month grid (WP-3.3 phase 5, revised
   on Michael's first-round feedback) ----
   SCHEDULE-SPEC §1 decision #7: a full-season month grid (spans whatever the
   season's own first/last dates cover — Feb+March today, derived not
   hardcoded). Counts are read from the SAME filtered $byDate the list uses
   (schedule.php), so the two views never disagree.

   🔑 BUILT BY WEEK, NOT ONE FLAT GRID. Two reasons: (1) schedule.php's
   $buildCalendarMonths already drops any week with zero games, so there's
   nothing to hide here — a "row with no games" simply never reaches this
   markup; (2) each week is its OWN 7-col grid, so CSS Grid's default
   row-stretch gives every cell in that week equal height for free, while a
   heavier week (longer matchup lists) is free to be taller than its
   neighbours — no JS, no fixed aspect-ratio fighting the content. */
.cl-cal { display: flex; flex-direction: column; gap: var(--cl-space-6); }

.cl-cal__month-title {
	margin:      0 0 var(--cl-space-3);
	font-family: var(--cl-font-display);
	font-weight: var(--cl-weight-bold);
	font-size:   var(--cl-text-h5);
	color:       var(--cl-text-brand);
}

.cl-cal__weekdays {
	display:               grid;
	grid-template-columns: repeat(7, 1fr);
	margin-bottom:         var(--cl-space-2);
	font-size:             var(--cl-text-xs);
	font-weight:           var(--cl-weight-bold);
	letter-spacing:        0.06em;
	text-transform:        uppercase;
	color:                 var(--cl-text-secondary);
	text-align:            center;
}

.cl-cal__week {
	display:               grid;
	grid-template-columns: repeat(7, 1fr);
	align-items:           stretch;   /* every cell in the row matches the tallest */
	gap:                   var(--cl-space-2);
}
.cl-cal__week + .cl-cal__week { margin-top: var(--cl-space-2); }

/* Cells no longer force a square — a day's height is now whatever its content
   (day number, optional matchup lines) needs, and the week's own row-stretch
   (above) keeps every cell in that row equal regardless. */
.cl-cal__cell {
	position:        relative;
	display:         flex;
	flex-direction:  column;
	gap:             var(--cl-space-1);
	min-height:      2.75rem;
	padding:         var(--cl-space-2) var(--cl-space-2) 1.375rem;  /* bottom pad reserves the count badge's corner */
	border-radius:   var(--cl-radius-sm);
	background:      var(--cl-sand-100);
	color:           var(--cl-ink);
	text-decoration: none;
}
.cl-cal__cell--empty { background: none; visibility: hidden; }

.cl-cal__daynum {
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-semibold);
	color:       var(--cl-text-secondary);
	text-align:  center;
}

/* Today gets a brand ring — the calendar's equivalent of the list's own
   "Today" tag (§11b), just drawn as an outline since there's no daybar here. */
.cl-cal__cell--today { box-shadow: inset 0 0 0 2px var(--cl-green-500); }
.cl-cal__cell--today .cl-cal__daynum { color: var(--cl-text-brand); font-weight: var(--cl-weight-bold); }

/* A day WITH games is a real Fancybox trigger (ONE per day, however many
   games it lists — see the PHP note on why that matters for the gallery
   group), with the same on-brand hover/focus lift the directory/dugout cards
   use (`--cl-card-lift-hover`). */
.cl-cal__cell--has-games {
	background: var(--cl-white);
	border:     1px solid var(--cl-sand-300);
	cursor:     pointer;
	transition: transform  var(--cl-duration-fast) var(--cl-ease),
	            box-shadow var(--cl-duration-fast) var(--cl-ease);
}
.cl-cal__cell--has-games:hover {
	transform:  translateY(-2px);
	box-shadow: var(--cl-card-lift-hover);
}
.cl-cal__cell--has-games:focus-visible { outline: none; box-shadow: var(--cl-focus-ring); }
.cl-cal__cell--has-games.cl-cal__cell--today { border-color: var(--cl-green-500); }

/* The decorative "Away @ Home" preview lines (Michael: short team names, one
   game per line, the square free to grow). aria-hidden on the PHP side — the
   trigger's own aria-label already summarises the day, and full detail with
   real accessible names lives in the popup. Wraps rather than truncating, so
   a long matchup grows the cell instead of clipping (the week's row-stretch
   then carries that height to its neighbours, which is the point).
   🔑 Tight rhythm on purpose (Michael: "reduce the space between games by at
   least half") — a near-1.0 line-height plus a 1px gap, rather than the
   text's normal reading leading, since these are dense list rows, not prose. */
.cl-cal__gamelines {
	margin:     0;
	padding:    0;
	list-style: none;
	display:    flex;
	flex-direction: column;
	gap:        1px;
}
.cl-cal__gameline {
	margin-bottom:  0.2rem;   /* styles-custom.css:118 sets a global `li { margin-bottom: 1rem }` — override explicitly rather than inherit it */
	font-size:      0.7rem;
	line-height:    1.1;
	color:          var(--cl-text);
	overflow-wrap:  break-word;
}
.cl-cal__at {
	margin:      0 0.2em;
	color:       var(--cl-text-secondary);
	font-weight: var(--cl-weight-semibold);
}

.cl-cal__count {
	position:       absolute;
	bottom:         3px;
	right:          4px;
	min-width:      1.1rem;
	line-height:    1.1rem;
	padding:        0 0.25rem;
	font-size:      0.65rem;
	font-weight:    var(--cl-weight-bold);
	text-align:     center;
	color:          var(--cl-white);
	background:     var(--cl-orange-700);
	border-radius:  var(--cl-radius-pill);
}

/* Below phone width a cell this small can't hold matchup lines legibly —
   Michael's ask: hide them and let the red count badge itself be the trigger
   (it already is — the whole cell, badge included, is one link/button on
   every breakpoint, so nothing else has to change here but what's visible).
   🔑 SQUARE ON PURPOSE (Michael's follow-up: "the day squares should all be
   square. Looks better. Some empty space is ok there."). `aspect-ratio: 1/1`
   derives the height from the grid column's own (equal, `1fr`) width, so
   every cell in every row comes out the same size with no JS and no fixed
   px — the empty space below the day number/badge is the intended trade. */
@media (max-width: 575.98px) {
	.cl-cal__week      { gap: 3px; }
	.cl-cal__cell      { aspect-ratio: 1 / 1; min-height: 0; padding: var(--cl-space-1) 2px 1.125rem; border-radius: 4px; }
	.cl-cal__gamelines { display: none; }
	.cl-cal__daynum    { font-size: var(--cl-text-xs); }
	.cl-cal__count     { font-size: 0.6rem; min-width: 1rem; line-height: 1rem; }
}

/* ---- 11i. CALENDAR DAY POPUP — Fancybox v3 (WP-3.3 phase 5 revision) ----
   Same "hidden div lifted into its own dialog" pattern as the partner/trip
   popups (§10g) and the legacy schedule calendar before it
   (asset-schedule-logic.php) — footer.php already loads the engine for both.
   SCOPED to `.cl-cal-modal`, not a mainClass, for the same reason §10g gives:
   this Fancybox build never applies mainClass to the container, so a
   two-class selector on the content element itself is the reliable hook.
   Every day sharing `data-fancybox="cl-cal-day"` is what gives Fancybox its
   own Next/Prev between days — nothing custom to build or maintain here. */
.fancybox-content.cl-cal-modal {
	--cl-modal-pad: var(--cl-space-6);
	position:      relative;
	width:         min(36rem, calc(100vw - 2rem));
	max-width:     none;
	padding:       var(--cl-modal-pad);
	background:    var(--cl-surface);
	border:        1px solid var(--cl-sand-300);
	border-radius: var(--cl-radius-lg);
	box-shadow:    var(--cl-shadow-3);
}
.cl-cal-modal__scroll {
	max-height: calc(100vh - 6rem);
	overflow-y: auto;
}
.cl-cal-modal__title {
	margin:      0 0 var(--cl-space-4);
	font-family: var(--cl-font-display);
	font-weight: var(--cl-weight-bold);
	font-size:   var(--cl-text-h4);
	color:       var(--cl-text-brand);
}
/* Reuses §11c's .cl-game rows verbatim — only the list wrapper needs a rule,
   to drop the first row's top hairline the way .cl-sched__games already does. */
.cl-cal-modal__games .cl-game:first-child { border-top: 0; }

/* Close × — no photo to sit on here (unlike §10g's partner hero), so a light
   sand disc reads better than the dark translucent one that popup uses. */
.cl-cal-modal .fancybox-close-small {
	top:            var(--cl-space-3);
	right:          var(--cl-space-3);
	width:          2rem;
	height:         2rem;
	padding:        0;
	color:          var(--cl-text-secondary);
	background:     var(--cl-sand-100);
	border-radius:  var(--cl-radius-pill);
	opacity:        1;
	transition:     background-color 0.15s ease;
}
.cl-cal-modal .fancybox-close-small:hover { background: var(--cl-sand-200); }

@media (max-width: 575.98px) {
	.fancybox-content.cl-cal-modal { --cl-modal-pad: var(--cl-space-5); }
}

/* Next / Previous — Fancybox's own gallery navigation, shown once every day
   popup shares the "cl-cal-day" group (see the PHP note). Same disc treatment
   as §10g's partner-modal arrows; :has() scoping for the same reason. */
.fancybox-container:has(.cl-cal-modal) .fancybox-navigation .fancybox-button {
	width:         3rem;
	height:        3rem;
	padding:       0.75rem;
	color:         var(--cl-white);
	background:    rgb(20 20 20 / 45%);
	border-radius: var(--cl-radius-pill);
	transition:    background-color 0.15s ease;
}
.fancybox-container:has(.cl-cal-modal) .fancybox-navigation .fancybox-button:hover,
.fancybox-container:has(.cl-cal-modal) .fancybox-navigation .fancybox-button:focus-visible {
	background: var(--cl-primary);
}


/* ===========================================================================
   §12. MY SCHEDULE PAGE (WP-3.3 phase 4) — /my-schedule.php

   The trip plan: favourites ARE the plan, one Save tap, no second concept
   (SCHEDULE-SPEC #9). The page is entirely client-rendered (js/my-schedule.js)
   because what's saved lives only in the visitor's own localStorage — PHP
   ships the full season as data and three pre-built states (loading is
   implicit, empty §12b, content §12a); JS picks which one is visible. Rows
   reuse §11b/§11c (.cl-sched / .cl-game) untouched, so a saved game looks
   identical here and on schedule.php.
   =========================================================================== */

/* ---- 12a. SUMMARY STATS — Games · Stadiums · Dates ----
   Three plain tiles, not cards: this is a quick readout, not a set of
   destinations to click into. */
.cl-myschedule__note {
	display:      flex;
	align-items:  center;
	gap:          var(--cl-space-2);
	margin:       0 0 var(--cl-space-5);
	font-size:    var(--cl-text-sm);
	color:        var(--cl-text-secondary);
}
.cl-myschedule__note i { color: var(--cl-sand-600); }

/* Print / Save-as-PDF toolbar (A4). Right-aligned above the stats; hidden in
   print (my-schedule.php's @media print block). */
.cl-myschedule__toolbar {
	display:         flex;
	justify-content: flex-end;
	margin-bottom:   var(--cl-space-4);
}
.cl-myschedule__print i { margin-right: var(--cl-space-2); }

/* Print-only masthead + footer (branding, QR, disclaimer). Screen-hidden; the
   my-schedule.php @media print block reveals and lays them out. */
.cl-myschedule__printhead,
.cl-myschedule__printfoot { display: none; }

.cl-myschedule__stats {
	display:       flex;
	flex-wrap:     wrap;
	gap:           var(--cl-space-4);
	margin-bottom: var(--cl-space-6);
}
.cl-myschedule__stat {
	flex:          1 1 9rem;
	display:       flex;
	flex-direction: column;
	align-items:   center;
	gap:           var(--cl-space-1);
	padding:       var(--cl-space-5) var(--cl-space-4);
	background:    var(--cl-surface);
	border:        1px solid var(--cl-card-hairline);
	border-radius: var(--cl-radius-lg);
	text-align:    center;
}
.cl-myschedule__stat-icon {
	font-size: var(--cl-text-lead);
	color:     var(--cl-text-brand);
}
.cl-myschedule__stat-num {
	font-family:          var(--cl-font-display);
	font-weight:          var(--cl-weight-extrabold);
	font-size:            var(--cl-text-h3);
	color:                var(--cl-ink);
	font-variant-numeric: tabular-nums;
	line-height:          1.1;
}
.cl-myschedule__stat-label {
	font-size:      var(--cl-text-sm);
	font-weight:    var(--cl-weight-semibold);
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color:          var(--cl-text-secondary);
}

/* ---- 12b. EMPTY STATE ----
   Season-aware per SCHEDULE-SPEC §4: after the season, say the next schedule
   will appear here rather than a flat "nothing here." Otherwise a plain nudge
   back to the full schedule. Both variants ship in the HTML (server picks
   which text) and js/my-schedule.js only toggles visibility. */
.cl-myschedule__empty { padding: var(--cl-space-6) 0; }


/* ===========================================================================
§13 — MAP.PHP, WP-3.3 rebuild (the ballpark & Gila River locations map).

The map is the hero: one full-bleed stage running edge-to-edge of the content,
with every control floating ON it. Three moving parts:
  · §13a  the PRESENTED-BY line — the GRRC sponsor ad, relocated INTO the
          .cl-glass lede (compact, clearly labelled, on a white chip because
          the grass band is dark and the logo is black-on-transparent).
  · §13b  the STAGE + CANVAS — full-bleed, tall.
  · §13c  the floating RAIL — a glass panel with a [Ballparks | Gila River]
          segmented toggle over a list of logo chips (the ballpark-nav visual
          language Michael liked, laid out as rows to fit the rail).
  · §13d  the MARKER + attached POPUP — one DOM node per pin; the popup
          (server-built .cl-iw, §13g) reveals on hover.
  · §13e  the auth-failure FALLBACK.
Smooth camera + two-way hover sync live in js/locations-map.js. Pins for BOTH
types always show; the toggle only changes which list you browse in the rail.
=========================================================================== */

/* ---- 13a. PRESENTED-BY (inside the .cl-glass lede) ---- */
.cl-map-presenter {
	display:     flex;
	align-items: center;
	gap:         var(--cl-space-3);
	flex-wrap:   wrap;
	margin-top:  var(--cl-space-4);
}
.cl-map-presenter__eyebrow {
	font-size:      var(--cl-text-xs);
	font-weight:    var(--cl-weight-semibold);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	opacity:        0.85; /* inherits the lede's light text over the grass */
}
/* The white chip — same white-surface-under-dark-artwork idiom as the footer /
   ballpark-nav logos, so the black GRRC mark reads against the dark band. */
.cl-map-presenter__link {
	display:       inline-flex;
	align-items:   center;
	background:    var(--cl-white);
	border-radius: var(--cl-radius-md);
	padding:       var(--cl-space-2) var(--cl-space-4);
	box-shadow:    var(--cl-shadow-1);
	transition:    transform var(--cl-duration-fast) var(--cl-ease),
	               box-shadow var(--cl-duration-fast) var(--cl-ease);
}
.cl-map-presenter__link:hover {
	transform:  translateY(-2px);
	box-shadow: var(--cl-shadow-2);
}
.cl-map-presenter__link:focus-visible {
	outline:    none;
	box-shadow: var(--cl-focus-ring);
}
.cl-map-presenter__logo {
	display:    block;
	height:     70px;
	width:      auto;
	max-width:  100%;
}
/* Large screens: sit the whole presenter row against the right edge of the
   glass lede (WP-3.3 review). */
@media (min-width: 992px) {
	.cl-map-presenter { justify-content: flex-end; }
}

/* ---- 13b. STAGE + CANVAS ---- */
.cl-map-section { margin: 0; }
.cl-map-stage {
	position:   relative;
	width:      100%;
	background: var(--cl-surface-sunken);
}
.cl-map-canvas {
	width:      100%;
	height:     clamp(520px, 72vh, 860px);
	background: var(--cl-surface-sunken);
}

/* ---- 13c. FLOATING BALLPARK RAIL (minimal — the map is the hero) ----
   WP-3.3 review round 3: a dark BALLPARKS cap on top and a dark RESET cap on the
   bottom stay put while the name list scrolls between them. Names only. */
.cl-map-rail {
	position:        absolute;
	top:             var(--cl-space-5);
	left:            var(--cl-space-5);
	z-index:         3;
	width:           min(190px, calc(100% - var(--cl-space-6) * 2));
	max-height:      calc(100% - var(--cl-space-5) * 2);
	display:         flex;
	flex-direction:  column;
	background:      var(--cl-glass-fill-strong);
	-webkit-backdrop-filter: blur(8px);
	backdrop-filter: blur(8px);
	border:          1px solid var(--cl-glass-hairline);
	border-radius:   var(--cl-radius-lg);
	box-shadow:      var(--cl-shadow-3);
	overflow:        hidden;
}
/* The two dark caps — always visible; the list scrolls between them. */
.cl-map-rail__head,
.cl-map-rail__foot {
	flex:       0 0 auto;
	background: var(--cl-green-800);
	color:      var(--cl-white);
}
.cl-map-rail__head {
	font-family:    var(--cl-font-display);
	font-weight:    var(--cl-weight-bold);
	font-size:      0.9em;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	text-align:     center;
	padding:        var(--cl-space-2) 12px;
}
.cl-map-rail__foot {
	display:     flex;
	padding:     var(--cl-space-2);
}
.cl-map-rail__body {
	flex:                1 1 auto;
	overflow-y:          auto;
	overscroll-behavior: contain;
	padding:             var(--cl-space-1) 0;
}
/* 🐛 The site-wide `li { margin-bottom: 1rem }` (styles-custom.css) leaks into
   any new <li> — it would gap these rows out. Zero it here. */
.cl-map-list > li { margin-bottom: 0; }

/* Reset control — lives in the dark foot cap, always visible. */
.cl-map-reset {
	appearance:      none;
	border:          1px solid rgba(255, 255, 255, 0.45);
	background:      transparent;
	cursor:          pointer;
	font:            inherit;
	font-size:       var(--cl-text-xs);
	font-weight:     var(--cl-weight-semibold);
	letter-spacing:  0.06em;
	text-transform:  uppercase;
	color:           var(--cl-white);
	white-space:     nowrap;
	width:           100%;
	padding:         5px var(--cl-space-3);
	border-radius:   var(--cl-radius-pill);
	transition:      background var(--cl-duration-fast) var(--cl-ease);
}
.cl-map-reset:hover { background: rgba(255, 255, 255, 0.14); }
.cl-map-reset:focus-visible { outline: none; box-shadow: var(--cl-focus-ring); }

/* The chip list — name-only rows; the whole row is the button. */
.cl-map-list {
	list-style: none;
	margin:     0;
	padding:    0;
	display:    flex;
	flex-direction: column;
}
.cl-map-chip {
	width:           100%;
	appearance:      none;
	border:          0;
	background:      transparent;
	cursor:          pointer;
	font:            inherit;
	text-align:      left;
	display:         block;
	padding:         4px 12px;
	border-radius:   0;
	color:           var(--cl-text);
	transition:      background var(--cl-duration-fast) var(--cl-ease);
}
.cl-map-chip__name {
	font-size:   var(--cl-text-sm);
	font-weight: var(--cl-weight-semibold);
	line-height: 1.05;
}
.cl-map-chip:hover {
	background: rgba(255, 255, 255, 0.55);
}
.cl-map-chip:focus-visible {
	outline:    none;
	box-shadow: var(--cl-focus-ring);
}
/* Linked = the pin this chip belongs to is the active one (two-way sync). The
   solid white fill (vs. transparent) is the non-colour cue; the name also goes
   brand-green, with an amber edge marker. */
.cl-map-chip.is-linked {
	background: var(--cl-white);
	box-shadow: inset 3px 0 0 var(--cl-amber-500);
}
.cl-map-chip.is-linked .cl-map-chip__name { color: var(--cl-text-brand); }

/* ---- 13c-2. GILA RIVER SPONSOR SPOTS (right edge) ----
   WP-3.3 review round 2: rectangles, not circles — wider so the wordmark logos
   read, shorter so the four take less vertical room. */
.cl-map-sponsors {
	position:        absolute;
	top:             var(--cl-space-5);
	right:           var(--cl-space-5);
	z-index:         3;
	display:         flex;
	flex-direction:  column;
	gap:             var(--cl-space-3);
	align-items:     flex-end;
}
.cl-map-spot {
	appearance:      none;
	border:          0;
	padding:         2px var(--cl-space-3);
	cursor:          pointer;
	width:           150px;
	height:          70px;
	border-radius:   var(--cl-radius-md);
	background:      var(--cl-white);
	box-shadow:      var(--cl-shadow-2), 0 0 0 1px var(--cl-card-hairline);
	display:         flex;
	align-items:     center;
	justify-content: center;
	overflow:        hidden;
	transition:      transform var(--cl-duration-fast) var(--cl-ease),
	                 box-shadow var(--cl-duration-fast) var(--cl-ease);
}
.cl-map-spot__logo {
	max-width:  100%;
	max-height: 76%;
	object-fit: contain;
}
.cl-map-spot__fallback {
	font-size:   var(--cl-text-xs);
	font-weight: var(--cl-weight-semibold);
	text-align:  center;
	color:       var(--cl-text-brand);
}
.cl-map-spot:hover,
.cl-map-spot.is-linked {
	transform:  scale(1.05);
	box-shadow: var(--cl-shadow-3), 0 0 0 2px var(--cl-amber-500);
}
.cl-map-spot:focus-visible {
	outline:    none;
	box-shadow: var(--cl-focus-ring);
}

/* ---- 13d. MARKER + attached hover POPUP ---- */
.cl-map-marker {
	position: relative;
	display:  block;
	width:    36px;
	height:   36px;
}
.cl-map-marker__icon {
	display:          block;
	width:            36px;
	height:           36px;
	transform-origin: 50% 100%;
	transition:       transform var(--cl-duration-fast) var(--cl-ease),
	                  filter var(--cl-duration-fast) var(--cl-ease);
	cursor:           pointer;
}
.cl-map-marker.is-active .cl-map-marker__icon {
	transform: scale(1.28);
	filter:    drop-shadow(0 3px 6px rgba(0, 0, 0, 0.35));
}
/* The popup floats above the pin; hidden until the marker is active. */
.cl-map-pop {
	position:       absolute;
	bottom:         calc(100% + 12px);
	left:           50%;
	transform:      translate(-50%, 6px);
	opacity:        0;
	pointer-events: none;
	transition:     opacity var(--cl-duration-fast) var(--cl-ease),
	                transform var(--cl-duration-fast) var(--cl-ease);
	z-index:        5;
}
.cl-map-marker.is-active .cl-map-pop {
	opacity:        1;
	transform:      translate(-50%, 0);
	pointer-events: auto;
}
/* Little caret connecting the popup to its pin. */
.cl-map-pop::after {
	content:     "";
	position:    absolute;
	left:        50%;
	bottom:      -6px;
	transform:   translateX(-50%);
	border:      7px solid transparent;
	border-bottom: 0;
	border-top-color: var(--cl-white);
	filter:      drop-shadow(0 2px 1px rgba(0, 0, 0, 0.08));
}

/* ---- 13g. INFO WINDOW — dugout-preview shape (header image + body) ----
   WP-3.3 review: header IMAGE band on top, then a compact body. The logo is the
   title (no repeated name text), which keeps the whole card short. */
.cl-iw {
	width:         min(288px, 84vw);
	background:    var(--cl-white);
	border-radius: var(--cl-radius-lg);
	overflow:      hidden;
	border:        1px solid var(--cl-card-hairline);
	box-shadow:    var(--cl-card-lift-hover);
	text-align:    center;
}
.cl-iw__media {
	height:     124px;
	background: var(--cl-surface-sunken) var(--iw-media) center / cover no-repeat;
}
.cl-iw__body {
	display:         flex;
	flex-direction:  column;
	align-items:     center;
	gap:             var(--cl-space-2);
	padding:         var(--cl-space-4) var(--cl-space-4) var(--cl-space-4);
}
/* Logo is the title (no repeated name). Sizes per WP-3.3 review: ballpark
   marks (roughly square) go big; the wide Gila River wordmarks a touch smaller
   so they don't overrun the card width. */
.cl-iw__logo {
	max-width:  84%;
	object-fit: contain;
}
.cl-iw--stadium .cl-iw__logo { max-height: 96px; }
.cl-iw--sponsor .cl-iw__logo { max-height: 70px; }
.cl-iw__name {
	font-family: var(--cl-font-display);
	font-weight: var(--cl-weight-bold);
	font-size:   var(--cl-text-sm);
	color:       var(--cl-text-brand);
}
/* "Spring Training Home of the …" — the homepage's ballpark tagline, info6. */
.cl-iw__home {
	margin:      0;
	font-size:   var(--cl-text-xs);
	font-weight: var(--cl-weight-semibold);
	line-height: var(--cl-leading-snug);
	color:       var(--cl-text-secondary);
}
.cl-iw__actions {
	display:         flex;
	flex-wrap:       wrap;
	justify-content: center;
	gap:             6px;
	margin-top:      var(--cl-space-1);
}

/* ---- 13e. AUTH-FAILURE FALLBACK ---- */
.cl-map-fallback {
	position:      absolute;
	inset:         0;
	z-index:       2;
	display:       flex;
	align-items:   center;
	justify-content: center;
	padding:       var(--cl-space-6);
	background:    var(--cl-surface-sunken);
}
.cl-map-fallback__msg {
	max-width:   40ch;
	text-align:  center;
	font-weight: var(--cl-weight-semibold);
	color:       var(--cl-text);
	margin:      0;
}

/* ---- 13h. RESPONSIVE — dock the rail under the map on small screens ----
   The sponsor spots stay floating on the map (top-right), just smaller and
   stacked from the top rather than distributed to half-height. */
@media (max-width: 991px) {
	.cl-map-stage {
		display:        flex;
		flex-direction: column;
	}
	.cl-map-canvas { height: clamp(360px, 56vh, 560px); }
	.cl-map-rail {
		position:      static;
		width:         auto;
		max-height:    none;
		margin:        0;
		border-radius: 0;
		border-left:   0;
		border-right:  0;
		box-shadow:    none;
		backdrop-filter: none;
		-webkit-backdrop-filter: none;
		background:    var(--cl-surface);
	}
	/* Show all ten ballparks — no inner scroll between the two caps on mobile
	   (Michael, 2026-09-03). The rail is in normal document flow here, so the
	   page scrolls instead of a nested scroller. */
	.cl-map-rail__body { max-height: none; overflow-y: visible; }
	.cl-map-sponsors {
		top:             var(--cl-space-3);
		right:           var(--cl-space-3);
		height:          auto;
		gap:             var(--cl-space-2);
		justify-content: flex-start;
	}
	.cl-map-spot { width: clamp(112px, 34vw, 150px); height: 54px; }
}
/* Small phones: trim the popup so it still clears the pin (which sits ~2/3 down
   a short canvas) without the top clipping. */
@media (max-width: 575px) {
	.cl-iw__media { height: 96px; }
	.cl-iw--stadium .cl-iw__logo { max-height: 72px; }
	.cl-iw--sponsor .cl-iw__logo { max-height: 54px; }
}



/* ============================================================================
   §14 — HOMEPAGE ANNOUNCEMENT MODAL  (announcement-modal.php · js/announcement.js)
   A static/we-manage dismissible notice on the homepage. Copy + on/off live in
   php/anamorphics-cms-inits.php ($announcement* vars). NOT a CMS surface.

   RESTING STATE IS HIDDEN AND HARMLESS: visibility:hidden/opacity:0 until
   js/announcement.js adds .is-open. So no markup on the page can trap focus or
   cover content unless the script ran — the no-JS / blocked-JS safety net.
   z-index sits just BELOW the consent banner (consent.css = 1080) so the
   legally-required consent bar is never covered when both appear on a first visit.
   ============================================================================ */
.cl-announce {
	position: fixed;
	inset: 0;
	z-index: 1070;
	display: grid;
	place-items: center;
	padding: var(--cl-space-4);
	opacity: 0;
	visibility: hidden;
	transition: opacity .25s ease, visibility .25s ease;
}
.cl-announce.is-open {
	opacity: 1;
	visibility: visible;
}

.cl-announce__backdrop {
	position: absolute;
	inset: 0;
	background: rgb(20 20 20 / 62%);
	cursor: pointer;
}

.cl-announce__dialog {
	position: relative;
	z-index: 1;
	width: min(560px, 100%);
	max-height: calc(100dvh - (var(--cl-space-6) * 2));
	overflow-y: auto;
	background: var(--cl-surface);
	color: var(--cl-text);
	border-radius: var(--cl-radius-lg);
	box-shadow: var(--cl-shadow-3);
	padding: var(--cl-space-7) var(--cl-space-6) var(--cl-space-6);
	transform: translateY(12px);
	transition: transform .25s ease;
}
.cl-announce.is-open .cl-announce__dialog {
	transform: none;
}

.cl-announce__close {
	position: absolute;
	top: var(--cl-space-3);
	right: var(--cl-space-3);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;               /* WCAG 2.2 AA target size */
	height: 44px;
	padding: 0;
	border: 0;
	background: transparent;
	color: var(--cl-text-secondary);
	font-size: 1.25rem;
	line-height: 1;
	cursor: pointer;
	border-radius: var(--cl-radius-sm);
	transition: color .15s ease, background-color .15s ease;
}
.cl-announce__close:hover,
.cl-announce__close:focus-visible {
	color: var(--cl-text);
	background: var(--cl-surface-sunken);
}

.cl-announce__kicker {
	margin: 0 0 var(--cl-space-2);
	color: var(--cl-orange-700);
}

.cl-announce__heading {
	margin: 0 0 var(--cl-space-3);
	color: var(--cl-text);
	font-size: var(--cl-text-h3);
	line-height: 1.15;
}

.cl-announce__body {
	margin: 0 0 var(--cl-space-5);
	color: var(--cl-text);
}
.cl-announce__body :last-child { margin-bottom: 0; }

.cl-announce__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--cl-space-3);
	align-items: center;
}

@media (prefers-reduced-motion: reduce) {
	.cl-announce,
	.cl-announce__dialog {
		transition: none;
	}
	.cl-announce__dialog { transform: none; }
}

@media (max-width: 575px) {
	.cl-announce__dialog {
		padding: var(--cl-space-6) var(--cl-space-4) var(--cl-space-4);
	}
	.cl-announce__actions .btn { width: 100%; }
}
