/* Dalton Baseball Club — brand system */
/* The hidden attribute must actually hide things.
   Its only backing is the browser default `display:none`, which any class rule
   with `display:` beats — `.btn{display:inline-flex}` did exactly that, leaving
   a 6U GameChanger button on screen pointing nowhere despite being marked
   hidden. Two spot fixes for this used to exist further down the file
   (#login-row[hidden] and .tab-chip[hidden]); this replaced both. */
[hidden]{display:none!important}

/* Scroll lock behind anything that covers the page: the splash, the open
   hamburger, and every mountOverlay dialog (lightbox, calendar popup,
   TeamReach, fan submit). assets/preloader.js injects the same rule inline,
   because the splash needs it before any stylesheet has loaded — but /admin
   ships no preloader, and a rule the nav and every dialog depend on cannot
   live only in a file half the site does not load (sweep A#8).
   overscroll-behavior stops a drag from chaining to the document underneath. */
html.daltonbaseballclub-lock{overflow:hidden!important}
html.daltonbaseballclub-lock,html.daltonbaseballclub-lock body{overscroll-behavior:none}

/* Keyboard escape hatch past the injected header; visible only when focused. */
.skip-link{
  position:absolute;left:-9999px;top:0;z-index:var(--z-toast);
  background:var(--red);color:var(--on-red);padding:10px 18px;border-radius:0 0 var(--r-sm) 0;
  font-family:var(--cond);font-weight:600;letter-spacing:var(--tr-label);text-transform:uppercase;
}
.skip-link:focus{left:0}
/* The ticker's seamless-loop duplicate: invisible to layout and to readers. */
.tk-dup{display:contents}

/* Every <details> in this file hides the UA marker and draws its own
   affordance (▸/▾ pseudo-content, a rotated chevron, or a boxed +/-). The
   reset was copied five times; it belongs once, here. Safari needs the
   ::-webkit- pseudo-element, everything else honours list-style on summary. */
summary{cursor:pointer;list-style:none}
summary::-webkit-details-marker{display:none}

/* Visually-hidden but announced — e.g. the page's real <h1> where the hero
   art already carries the heading visually. */
.vh{
  position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0;
}

/* Only ever shown with JS disabled — #app-header renders nothing then, so
   this is the one way a no-JS visitor finds the rest of the site. */
.noscript-nav{
  display:block;padding:var(--s-3) var(--s-4);background:var(--surface-2);color:var(--text-1);
}
.noscript-nav a{color:var(--text-1);margin-right:var(--s-3)}

*{box-sizing:border-box}
html{
  background:#0B0B0D;
  background:var(--ink);
  /* Phone browsers boost text in blocks as they are laid out unless told
     not to; on the home page that read as the screen "jumping sizes" on
     scroll. The layout is sized for phones already. */
  -webkit-text-size-adjust:100%;text-size-adjust:100%;
  /* Keeps the page from shifting sideways when the loader drops overflow:hidden
     and the vertical scrollbar appears. */
  scrollbar-gutter:stable;
  /* Ink under any transient paint so a tab change never flashes white. */
  color-scheme:dark;
  /* Never fake a weight. Anton ships one face (400) and every h1/h2/h3/b set
     in it asked for 700 — the browser's default heading weight — so Chrome
     drew the real outline and then thickened it by stroking, which is what
     synthetic bold IS. The result is a smeared edge on every page-hero title,
     section heading and score, worst on a Windows PC, where the owner read
     the whole desktop site as blurry (2026-09-26). With weight synthesis off a
     700 request on a one-weight family renders the real 400 outline, crisp;
     Barlow and Barlow Condensed requests land on the nearest weight they
     actually ship. Italic and small-caps synthesis stay: an <i> in a score
     line is meant to lean, and a slant is not a smear. */
  font-synthesis:style small-caps;
  font-synthesis-weight:none;
}
/* Soft-nav swaps <main> in place — no cross-document View Transitions.
   Auto VT was an intermittent white-flash source between snapshots. */
@view-transition{navigation:none}
/* `html{zoom:1.15}` used to live here to make the desktop layout read larger.
   zoom scales the rendered box tree but NOT the media-query viewport, so
   between 1100px and 1265px the page laid out as if it were 956-1100px wide
   while the 960px/1240px nav squeezes never fired; every vh/vw length inside
   the zoomed subtree was multiplied by 1.15 (only .hero-full compensated, by
   dividing by 1.15), and site.js's own width branch and getBoundingClientRect
   reads landed in a different coordinate space than the CSS. Sizes are honest
   now: heading/hero type is clamp()-driven, so raise THOSE if desktop reads
   small again — never the whole document. */
/* No -webkit-font-smoothing here, deliberately. It used to say `antialiased`,
   which switches WebKit off subpixel rendering and onto grayscale. On a light
   page that reads as "cleaner"; on this one it reads as blurry, because every
   surface is near-black and almost all type is white. Grayscale AA thins a
   light-on-dark glyph — the stem loses the subpixel coverage that was holding
   its weight — so condensed uppercase display type, which is most of the
   headings, came out soft-edged and underweight. Letting the browser keep its
   default gives those stems their weight back. Do not re-add it to make a
   screenshot look tidier: the sites are dark, and this is the case the
   property is worst at. */
body{
  margin:0;background:#0B0B0D;background:var(--ink);color:var(--white);
  font-family:var(--body);line-height:1.55;
}
main,#app-header,#app-footer{background:#0B0B0D;background:var(--ink)}
a{color:var(--white)}
img{max-width:100%;display:block}
.wrap{max-width:var(--wrap);margin:0 auto;padding:0 var(--gutter)}
/* Every deep link, not three of them. #main-content (the skip link's own
   target), every section id and every anchor site.js scrolls to used to land
   UNDER the sticky bar — 63px of the target covered on desktop, 104px on a
   phone — because exactly three elements (.sp-join, #intake, #order-form)
   carried their own scroll-margin-top and nothing else did. One rule, so a new
   id can't forget it. */
[id]{scroll-margin-top:calc(var(--chrome-h,var(--header-h)) + 12px)}
/* And for everything WITHOUT an id — a calendar chip, a photo thumb, a footer
   link reached with Shift+Tab — the same clearance from the sticky bar (R238). */
html{scroll-padding-top:calc(var(--chrome-h,var(--header-h)) + 12px)}

/* ---------- header ---------- */
/* #app-header is empty until site.js injects the nav. Without a reserved height
   the hero/content sits flush to the top under the loader, then jumps down when
   the sticky bar paints — the shift people see right as the splash clears.
   chrome-boot may raise min-height from sessionStorage (daltonbaseballclub-chrome-h). */
/* --header-h is the RESERVED height, so hero heights and anchor offsets stay
   in step with the band instead of each hard-coding 58/70/110px of their own.
   Its desktop value lives in tokens.css; the stacked value lives at the top of
   mobile.css, which is the only cross-sheet contract between the two layouts.
   Reserving too little handed every first-time mobile visitor a 54-99px
   downward jump the moment the header painted, on the hero, the calendar and
   the scores page. chrome-boot still overrides this from daltonbaseballclub-chrome-h once a
   real measurement exists. */
/* position:sticky lives HERE, not on the .site-header site.js injects inside.
   A sticky element can only travel inside its containing block, and this
   wrapper is exactly the header's own height (min-height below, plus site.js
   pinning it to the measured offsetHeight) — so the sticky range was zero and
   the bar scrolled away like a static one, while .lv-tlbar and .gd-save-alert
   both position themselves as though it had stayed. */
#app-header{
  min-height:var(--header-h);
  position:sticky;top:0;z-index:var(--z-header);
}
/* The header is a stacking context (position + its own z-index above), so no
   z-index on anything painted inside it — the desktop "More" card, the
   hamburger sheet — can ever climb past its own --z-header (50), no matter
   what those elements declare themselves. Every sticky bar OUTSIDE the header
   (.coach-bar, .adm-sticky, .lv-tlbar) is correctly on --z-chrome (60), above
   --z-header on purpose (see .lv-tlbar's own comment) so it clears the header
   when nothing is open — which then means it paints over the header's own
   open menus on every page. Scoped to while a menu is open, so the header's
   resting z-index (and the --z-chrome-clears-it relationship those bars rely
   on) is unchanged: raise the whole stacking context past --z-chrome, rather
   than the menu's own z-index, which a capped ancestor makes a no-op. */
#app-header:has(.nav-more-menu.is-open),
#app-header:has(.nav-links.is-open){
  z-index:var(--z-overlay);
}
#app-footer{min-height:180px}
.site-header{
  border-bottom:1px solid var(--hairline);background:rgb(var(--hdr-rgb,11 11 13) / .92);backdrop-filter:blur(8px);
}
.nav{display:flex;align-items:stretch;flex-wrap:wrap}
.nav-brand{
  display:flex;align-items:center;padding:10px var(--s-4);text-decoration:none;
  border-right:1px solid var(--hairline);
  font-family:var(--display);font-size:var(--t-lg);letter-spacing:var(--tr-tight);color:var(--hdr-text-hi,var(--text-1));text-transform:uppercase;
}
.nav-brand img{height:38px;width:auto;aspect-ratio:800/215}
/* Condensed uppercase. Wave B put the whole UI into sentence case and the
   owner's verdict on it was "I hate the lowercase look" — so caps are the
   house voice again, here and on every button, tab, chip, label and table
   header. What does NOT come back with them is the 47 hand-typed trackings:
   every restored rule takes --tr-label (.12em), and a section eyebrow takes
   --tr-eyebrow (.18em). Two tracking values for the whole uppercase UI, from
   tokens, so check-tokens can hold the line. The labels themselves never
   changed — PAGES still writes them as "Order Gear", "Join Us". */
/* The row is 62px by construction, not by accident: --header-h:63px used to be
   a back-calculation of "18px padding over a 23.25px line box, plus the 1px
   hairline", so any type change silently moved the whole site's sticky offset.
   min-height owns it now, and the type is free to change. */
.nav-links a{
  /* --t-base, not --t-md: the tab row shipped at a hard 15px for its whole life
     and Wave B's re-scale put it on the 17px step, which read as the whole page
     being zoomed in. 15px is what --t-base is. */
  font-family:var(--cond);font-weight:600;font-size:var(--t-base);
  letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--hdr-text,var(--text-2));text-decoration:none;padding:0 13px;min-height:62px;
  display:flex;align-items:center;gap:7px;
  border-bottom:3px solid transparent;
}
.nav-links a:hover{color:var(--hdr-text-hi,var(--text-1))}
.nav-links a[aria-current="page"]{color:var(--hdr-text-hi,var(--text-1));border-bottom-color:var(--red)}
/* "More" disclosure. Seven links is the entire desktop row — the same seven
   every day, game day or not; Social, Order Gear (a club with it on) and
   Fundraisers (while one runs) sit in a card under this button. Eleven flat
   tabs was what forced the old graduated squeeze down to 11.5px type. On a
   page inside the card the button takes that page's name (site.js
   labelNavMore), which is longer than "More", so it may not be a fixed width. */
.nav-more{
  font-family:var(--cond);font-weight:600;font-size:var(--t-base);
  letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--hdr-text,var(--text-2));background:transparent;border:0;border-bottom:3px solid transparent;
  padding:0 13px;min-height:62px;display:flex;align-items:center;gap:var(--s-2);cursor:pointer;
}
.nav-more::after{
  content:"";width:6px;height:6px;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:translateY(-2px) rotate(45deg);transition:transform var(--dur-1) var(--ease-out);
}
.nav-more:hover,.nav-more[aria-expanded="true"]{color:var(--hdr-text-hi,var(--text-1))}
.nav-more:focus-visible{outline:2px solid var(--hdr-text-hi,var(--text-1));outline-offset:-2px}
.nav-more[aria-expanded="true"]::after{transform:translateY(1px) rotate(-135deg)}
/* The current page can be inside the menu — the row still has to show where
   you are, so site.js mirrors aria-current onto the button as .is-current. */
.nav-more.is-current{color:var(--hdr-text-hi,var(--text-1));border-bottom-color:var(--red)}
.nav-more-menu a:last-child{border-bottom:0}
/* "Where I am" inside a menu card is a filled row, not a 3px rule bolted to
   the left edge of a row that also had a bottom border. One idiom, and it is
   the same --surface-active the pressed state uses. */
.nav-more-menu a[aria-current="page"]{background:var(--hdr-tint,var(--surface-active));color:var(--hdr-text-hi,var(--text-1));box-shadow:none}
@media (prefers-reduced-motion:reduce){.nav-more::after{transition:none}}
.live-dot{width:8px;height:8px;border-radius:50%;background:var(--hl,var(--red));animation:pulse 1.6s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.25}}
@media (prefers-reduced-motion:reduce){.live-dot{animation:none}}
/* Crossfades and slide tracks count as motion too: the hero rotator, the
   gallery carousel and /live's swipe track all animated straight through
   prefers-reduced-motion because only the ticker/rotator/marquee were listed. */
@media (prefers-reduced-motion:reduce){
  .hero-rotator img,.carousel img,.lv-thumb,.lv-swipe-track{transition:none}
  .lv-swipe-track.lv-peek{animation:none}
  html{scroll-behavior:auto}
}

/* ---------- type & sections ---------- */
h1,h2,h3{text-wrap:balance}
.display{font-family:var(--display);text-transform:uppercase;line-height:var(--lh-display);letter-spacing:var(--tr-tight);margin:0}
/* One step down from a hero .display — the "next up" headline on the home
   page, where the game line has to sit under the section heading. */
.display-md{font-size:var(--d-sect)}
/* ONE eyebrow idiom, not seven.
   The audit counted seven spellings of the same thing — .eyebrow, .kicker,
   .meta, .sc-kicker, .sp-eyebrow, .cd-label, .ro-kick — each with its own
   size, tracking and colour, so "the small caps line above a heading" looked
   different on every page. There is one now, plus a quiet variant:
     .eyebrow  — red, opens a section. Exactly ONE per section region.
     .kicker   — muted, sits above a card or dialog heading.
   Both are condensed 600, uppercase, --tr-eyebrow. Everything else that used
   to be its own label class is aliased onto these further down its own page
   block, so a size change here reaches all of them. */
.eyebrow,.kicker{
  font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);
  letter-spacing:var(--tr-eyebrow);text-transform:uppercase;line-height:var(--lh-tight);
}
.eyebrow{color:var(--link,var(--red-text));margin:0 0 var(--s-3)}
.kicker{color:var(--text-3)}
/* Symmetric padding-block. `padding:76px 0 0` was top-only, so the gap between
   any two sections was whichever one happened to come next — six different
   section paddings across the site, and a section followed by the footer had
   no bottom at all. Three paddings now, and a section owns both its edges. */
.section{padding-block:var(--sec-md)}
.section--sm{padding-block:var(--sec-sm)}
.section--lg{padding-block:var(--sec-lg)}
.section h1,.section h2{font-family:var(--display);font-size:var(--d-sect);text-transform:uppercase;letter-spacing:var(--tr-tight);line-height:var(--lh-display);margin:0 0 var(--s-2)}
/* Running copy inside a .section — /privacy and /terms are eighteen <h2>s of
   policy prose — wants air above it and a smaller step than a section opener. */
.section .prose h2{font-size:var(--t-2xl);margin:var(--s-6) 0 var(--s-3)}
.prose{max-width:var(--measure-body)}
/* The 404's one deliberately enormous number. It is the only thing on that
   page, so it gets the hero ramp rather than a clamp of its own. */
.display-hero{font-size:var(--d-hero);line-height:var(--lh-display)}
.section .sub{color:var(--text-2);margin:0 0 var(--s-5);max-width:var(--measure-body)}
/* The left rail. A section's eyebrow, heading and lead start on the same x —
   the thing every club site does and this one did not: 32 centred elements on
   the home page sat next to left-aligned boxes. Centring survives only where
   it is a decision (the hero, the crest, a scoreline, the submit-done card),
   and those say so with .center. */
.section > .eyebrow,.section > h2,.section > .sub,.section > .slash{margin-inline:0}
.slash{height:8px;width:140px;background:var(--red);transform:skewX(-24deg)}

/* ---------- hero ---------- */
/* The wordmark keeps a shadow — it is artwork over a photograph and needs to
   separate from it — but a tight, dark one that reads as contact with the
   image, not the 28px glow that read as a slide-deck "text effect". */
.hero-logo{width:min(860px,96%);height:auto;margin:10px 0 0;filter:drop-shadow(0 4px 12px rgba(0,0,0,.55))}
/* ---------- the drawn lockup ----------
   A club with no wordmark of its own has its name SET, in the product's own
   faces, rather than typed out by the image toolchain in whatever bold face
   the build machine happened to have. scripts/new-client.mjs decides which of
   the two a tree gets; here the block is dormant, because this club's hero IS
   the image above and the markup ships `hidden`.

   The same three words are what rally/lib/lockup.js draws into the printed
   wordmark and the social card, so the page and the press kit are one design.
   Sizes come off the display ramp: --d-hero is the ramp the hero wordmark's
   own text fallback already uses, so the lockup lands at the size the hero was
   always built around, phone to 1920. */
.hero-lockup[hidden]{display:none}
.hero-lockup{
  display:flex;flex-direction:column;align-items:center;gap:6px;margin:10px 0 0;
  max-width:100%;text-align:center;line-height:var(--lh-display);
  filter:drop-shadow(0 4px 12px rgba(0,0,0,.55));
}
/* --red, not --red-text. The lighter tint exists for body copy and is NOT
   recoloured per client (it is a fixed salmon), so a club with purple or green
   colours would get a salmon eyebrow here while their printed wordmark used
   their own. The eyebrow is a mark, not prose: it takes the club's colour, the
   same one rally/lib/lockup.js draws it in. */
.hero-lockup .lk-eyebrow{
  font-family:var(--cond);font-weight:600;text-transform:uppercase;
  font-size:var(--t-lg);letter-spacing:var(--tr-eyebrow);color:var(--red);
}
.hero-lockup .lk-eyebrow:empty,.hero-lockup .lk-tag:empty{display:none}
.hero-lockup .lk-word{
  font-family:var(--display);font-weight:400;text-transform:uppercase;
  font-size:var(--d-hero);letter-spacing:var(--tr-tight);color:var(--text-1);
  /* The name is the one thing on the page that may not wrap into two ragged
     rows, so it shrinks to the viewport instead. */
  max-width:100%;overflow-wrap:break-word;
}
.hero-lockup .lk-tag{
  font-family:var(--cond);font-weight:600;text-transform:uppercase;
  font-size:var(--t-md);letter-spacing:var(--tr-label);color:var(--on-red);
  background:var(--red);border-radius:var(--r-xs);padding:2px 12px;margin-top:4px;
}
/* The styles, as the generator stamps them on the block. Varsity is the
   double keyline of the drawn mark, restated in text-shadow so the page and
   the raster read as the same thing; block is the name and nothing else. */
.hero-lockup[data-style="varsity"] .lk-word{
  -webkit-text-stroke:4px var(--red);paint-order:stroke fill;
  text-shadow:0 0 0 var(--red),4px 4px 0 var(--ink);
}
.hero-lockup[data-style="block"] .lk-word{letter-spacing:var(--tr-tight)}
.hero-lockup[data-style="block"] .lk-tag{background:var(--panel);color:var(--text-1)}
.hero-lockup[data-style="speed"]{transform:skewX(-12deg)}
.hero-lockup[data-style="speed"] .lk-word{color:var(--text-1)}
.hero-lockup[data-style="script"] .lk-word{
  font-family:"Kaushan Script",var(--display);text-transform:none;
  letter-spacing:var(--tr-0);text-shadow:4px 5px 0 rgba(0,0,0,.45);
}
.hero-lockup[data-style="brush"] .lk-word{
  font-family:"Bangers",var(--display);transform:rotate(-2.5deg);
  -webkit-text-stroke:3px var(--ink);paint-order:stroke fill;
}
/* Shadow: a hard drop behind the name, as the drawn mark's deep-colour copy;
   Outline: the name hollow, one stroke in the text colour and nothing inside,
   so the hero's ground shows through the letters. Both inert until a block
   wears them (scripts/new-client.mjs stamps data-style from the spec). */
.hero-lockup[data-style="shadow"] .lk-word{text-shadow:.05em .06em 0 var(--ink)}
.hero-lockup[data-style="outline"] .lk-word{
  color:transparent;-webkit-text-stroke:3px var(--text-1);paint-order:stroke;
}
/* Splash: the name in the pull-out loading screen's own letters.

   The face is Archivo at 125% width, a variable woff2 that the loader already
   ships with every site (assets/loaders/archivo-latin.woff2) — ONE file, read
   by this rule and by the loader's own @font-face (assets/loaders/pullout.js),
   which asks for the very same URL; vercel.json serves it public and immutable
   for a year, so a browser that has both declarations fetches it once. The family
   name is the loader's too, and so are the numbers it draws the name with:
   weight 800, stretch 125%, tracking .16em (--tr-splash, tokens.css).

   Nothing here downloads unless an element wears the face. A club on any other
   style has this rule and never uses it; a club on this one adds a <link
   rel="preload"> to its home page (scripts/new-client.mjs, 5n), which no other
   club gets, so the name does not wait for the stylesheet to find the file.
   `swap`, as every face on the site: a name drawn late beats a blank one.

   "Archivo Splash Fallback" is Arial Bold scaled onto this face's widths and
   its line box (size-adjust 131% is the mean of eighteen club-shaped names, each
   within 3%; 67% and 16% are Archivo's own 0.878 and 0.210 ascent and descent
   divided by it), the same trick as the three faces in tokens.css, so the swap
   changes the letters and not where anything sits. Where there is no Arial the
   stack falls through to the display face, then sans-serif: a failed download
   still draws the name.

   Only the NAME is set in it. The eyebrow above and the age chip below stay in
   the condensed support face, like every other label on the page (the nav, the
   buttons, the season line). They were tried in the loader's own small line
   (the same face at weight 500, tracked .5em): beside a name this heavy that
   reads as thin capitals spread so far apart the town stops being a word, and
   the chip's text outgrows its chip. */
@font-face{font-family:"Archivo Splash";font-style:normal;font-weight:100 900;font-stretch:125%;font-display:swap;
  src:url(/assets/loaders/archivo-latin.woff2) format("woff2")}
@font-face{font-family:"Archivo Splash Fallback";font-style:normal;font-weight:800;
  src:local("Arial Bold"),local("Arial-BoldMT"),local("Helvetica Neue Bold"),local("Helvetica-Bold"),local("Liberation Sans Bold"),local("Roboto Bold");
  size-adjust:131%;ascent-override:67%;descent-override:16%;line-gap-override:0%}
.hero-lockup[data-style="splash"] .lk-word{
  font-family:"Archivo Splash","Archivo Splash Fallback",var(--display),sans-serif;
  font-weight:800;font-stretch:125%;font-synthesis-weight:none;
  /* 60% of the hero ramp, so the name lands where the hero has always been set
     (phone to 1920) and no new ramp is born. The face is about 2.3 times as wide
     as Anton per em once it is tracked, so the name wraps at its space on a
     phone; the second term is the ceiling that keeps a nine-letter word whole
     in the column on the narrowest phones (320 and 360), where 60% would not. */
  font-size:min(calc(var(--d-hero)*.6),calc((100vw - 2*var(--gutter))/9.6));
  letter-spacing:var(--tr-splash);
  /* Tracking is paid AFTER each letter, the last one included, so a centred
     line sits half a track too far left. The loader gives its name the same
     padding on the same side for the same reason (.pl-txt). */
  padding-left:var(--tr-splash);
  /* The net under the size rule above, for one word longer than nine letters
     (a coach can type forty into brandWord, and Rally HQ derives the word from
     the club's name: Thunderbolts, Diamondbacks, Roughnecks). The base rule's
     break-word is not enough, and this is why: break-word lets an overlong word
     break only AFTER its box is sized, and does not count in the box's
     min-content, so .hero-content (a flex item whose .wrap margin switches off
     stretch) grew to the whole unbroken word, the hero's overflow:hidden cut
     the last letters off and the buttons and the tagline ran off the screen
     with the column. `anywhere` counts the break opportunities in min-content,
     so the column stays the screen's width and the word wraps inside it. A
     mid-word break is ugly and a clipped club name is a defect; browsers
     without `anywhere` (Safari before 15.4) keep the base rule's break-word.
     Nine letters and under never reach it. (scenario AF types Thunderbolts.) */
  overflow-wrap:anywhere;
}
/* The header, when the hero is a drawn lockup: the crest plus the word, because
   a lockup with an eyebrow and a chip is unreadable at 38px. Dormant here —
   this club's header is still its wordmark image. */
/* A fixed 38px box the crest is CONTAINED in: .nav-brand img above reserves
   the wordmark's 297/114, which would stretch a crest, and a crest is not
   reliably square either. object-fit does the fitting and the box does the
   reserving, so nothing shifts when the file lands.
   38px, the height of the wordmark it stands in for, because on a phone the
   brand IS the bar: the nav links are behind the hamburger, so the row is the
   mark plus mobile.css's 12px padding, and --header-h (63px, and 107px with a
   Next Up row) is reserved against that arithmetic. At 30px the bar came out
   8px shorter than the page had reserved and everything under it jumped up
   when the real chrome painted — which is the shift the reserve exists to
   prevent. On a desktop the row is floored at 62px by .nav-links a, so the
   mark's height does not decide it there. */
.nav-brand .nav-crest{height:38px;width:38px;aspect-ratio:auto;object-fit:contain}
.nav-brand .nav-word{
  font-family:var(--display);font-size:var(--t-lg);letter-spacing:var(--tr-tight);
  /* The same token .nav-brand itself takes, not the fixed cream: a coach who
     sets a light header bar in Coaches Corner gets dark text on it, and the
     club's own name was the one thing in the bar that stayed cream and
     vanished (1.07:1 on white). */
  text-transform:uppercase;color:var(--hdr-text-hi,var(--text-1));margin-left:8px;
}
.hero-h1{font-size:var(--d-hero)}
.hero-h1 .knock,.knock{color:var(--red)}
.accolade{
  display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:36px;
  font-family:var(--cond);font-weight:600;text-transform:uppercase;
  border:0;background:none;padding:0;
}
.accolade .acc-kicker{font-size:var(--t-2xs);letter-spacing:var(--tr-eyebrow);color:var(--link,var(--red-text))}
/* `text-shadow:0 2px 18px` is gone. Shadowed text over a photo is the single
   most recognisable slide-deck signature, and the hero's own three-stop
   .hero-shade is already doing the separation this was standing in for. */
.accolade .acc-line{
  display:flex;align-items:center;gap:var(--s-5);
  font-size:var(--t-md);letter-spacing:var(--tr-eyebrow);color:var(--text-1);
}
.accolade .acc-line i{width:110px;height:1px;flex:none;background:linear-gradient(90deg,transparent,rgba(244,243,240,.45))}
.accolade .acc-line i:last-child{background:linear-gradient(270deg,transparent,rgba(244,243,240,.45))}
/* The accolade line holds its filled height from the first paint when the last visit
   saw an accolade. It is two empty spans in the markup and is filled once the page's
   data lands, so it used to grow from 8px to about 51px a moment after first paint and
   push what is under it down (the platform strip at the foot of the hero, 43px, wherever
   the hero is as tall as its own content) or re-centre the copy above it. chrome-boot.js
   puts html.daltonbaseballclub-has-accolade on before first paint, and says when and when not; each
   empty span then holds one hidden character of its own font and size, which is exactly
   the line box the filled text takes, so no height is written here to drift from the
   type scale. A filled span is no longer :empty, so the placeholder goes by itself; the
   star is the kicker's own glyph, so a fallback face for it counts the same on both
   sides. The box is stretched to the content's width for the same reason: shrink-wrapped
   it would grow from the width of one star to the width of the words, and the browser
   scores that sideways movement as a shift of the accolade itself. One line is what it
   holds: an accolade long enough to wrap still adds its extra rows when it lands. Here,
   in the sheet every width loads, because the reserve has no width of its own. */
html.daltonbaseballclub-has-accolade .accolade{align-self:stretch}
html.daltonbaseballclub-has-accolade .accolade .acc-kicker:empty::before{content:"\2605";visibility:hidden}
html.daltonbaseballclub-has-accolade .accolade .acc-line:empty::before{content:"\00a0";visibility:hidden}

/* ---------- full-screen rotating hero ---------- */
.hero-full{
  position:relative;overflow:hidden;
  min-height:calc(100svh - var(--chrome-h,var(--header-h)));
  /* Column, not row: .affil-strip is the hero's second in-flow child now (it
     used to be position:absolute;bottom:16px inside a content-sized box, which
     put it 2px on top of the tagline and 62px below the fold on a 390px phone
     — measured W9/mobile F1). In flow it cannot overlap anything and the hero
     grows by exactly its height instead of silently overflowing. */
  display:flex;flex-direction:column;align-items:stretch;justify-content:center;
  /* No hairline under the hero: with the hero filling the viewport it sat
     exactly on the fold, a thin line under the platform badges. */
  padding:64px 0 56px;
  /* Ink base + optional opener from chrome-boot / early settings paint.
     Never stock hero-bg.webp — coach list or bundled featured only. */
  background-color:var(--ink);
  background-image:var(--home-hero-image, none);
  background-position:var(--home-hero-pos, center 40%);
  background-size:cover;
  background-repeat:no-repeat;
}
/* z-index:0 makes the rotator its own stacking context, so the frames'
   z-indexes below never climb over .hero-shade or the copy. */
.hero-rotator{position:absolute;inset:0;z-index:0}
.hero-rotator img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity 1.4s var(--ease-out);
}
.hero-rotator img.active{opacity:1;z-index:2}
/* The frame on its way out stays fully opaque underneath the one fading in.
   Fading both at once put the hero's own background — the opener — on screen
   at the midpoint of every change: a flash of the main photo between
   pictures. site.js drops .is-leaving once the incoming fade has ended. */
.hero-rotator img.is-leaving{opacity:1;z-index:1;transition:none}
/* First paint: no fade-in from a wrong underlying photo. Once the reel has
   turned (site.js marks frames .reel) the opener crossfades like any other. */
.hero-rotator img.active:first-child:not(.reel){transition:none}
.hero-shade{
  position:absolute;inset:0;
  /* Dark shade only. The red radial that used to sit at the top centre read
     as the loader's glow lingering after the splash, so it is gone. */
  background:linear-gradient(180deg, rgba(6,6,8,.74) 0%, rgba(9,9,11,.60) 45%, rgba(11,11,13,.92) 100%);
}
/* margin-block:auto centres the copy in whatever space is left AND leaves the
   affiliate strip on the hero's bottom edge, where it sat before Wave A took it
   out of position:absolute. On a phone there is no free space, the autos
   collapse to 0, and the stack behaves exactly as it does now — no overlap, no
   overflow. */
.hero-content{position:relative;z-index:2;margin-block:auto;display:flex;flex-direction:column;align-items:center;text-align:center}
.hero-content .btn-row{margin-top:24px;min-height:48px}
.hero-content .accolade{margin-top:24px}
.hero-content .tagline{margin:18px 0 0}
.affil-strip{
  position:relative;z-index:2;flex:none;
  display:flex;justify-content:center;align-items:center;gap:20px 36px;flex-wrap:wrap;
  margin-top:var(--s-7);padding:0 22px;opacity:.92;
}
.affil-strip img{
  height:32px;width:auto;display:block;
  /* Without this the marks sink into the hero photo and read smaller than they
     measure. It was dropped with the hero's red wash; the wash was the problem,
     not the shadow. */
  filter:drop-shadow(0 2px 10px rgba(0,0,0,.55));
}
.affil-strip img[alt="GameChanger"]{height:17px}
/* Two labelled groups in one row (owner, 2026-10-01): who built the site, then
   the tools it works with. The row used to be seven company logos and nothing
   else, which a reader takes for the people paying for the team. A label is a
   full-width first line inside its group, so a group that has to wrap (the
   badges, on a narrow screen) keeps its label above the first row without a
   second wrapper element. Condensed 600 uppercase at --t-2xs in --text-2, the
   same register as every other small label on the hero.
   The label is width:0 with a min-width of the whole line: that is what makes
   it a full-width line when the group lays out and a zero-width item when the
   group is measured, so a group is exactly as wide as its logos (a label's own
   width would otherwise be counted into the row, and the one-line layout would
   want 1350px instead of 1140). The negative side margins, half the gap each,
   cancel the flex gap that zero-width item would still be given, and being
   equal they leave the line centred exactly where the group's own is. */
.affil-group{--affil-gap:36px;display:flex;justify-content:center;align-items:center;flex-wrap:wrap;gap:0 var(--affil-gap)}
.affil-label{
  flex:0 0 100%;width:0;min-width:100%;margin:0 calc(-0.5 * var(--affil-gap)) 2px;
  display:flex;justify-content:flex-start;white-space:nowrap;
  font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);line-height:1.2;
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--text-2);
}
/* The one thin rule between the groups, a border on the second so it is
   exactly as tall as the group. mobile.css drops it where the groups stack. */
.affil-with{padding-left:36px;border-left:1px solid rgba(244,243,240,.28)}
/* The maker's mark is a brand file and its guidelines say no shadow, outline
   or recolour (brand/README.md section 6). The shadow above is for third-party
   marks that sink into the photo; this one is white on the hero's dark shade. */
.affil-strip img[alt="Rally"]{filter:none}
/* 32x43 was the smallest target on the home page (W9/mobile F22): the <a> was
   display:block and shrink-wrapped a 32px-wide logo. 44px minimum, both axes. */
.affil-strip a{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;transition:transform var(--dur-1) var(--ease-out);
}
@media (hover:hover){.affil-strip a:hover{transform:translateY(-2px)}}
/* announcement + season inside the nav bar */
.nav-alert{display:flex;align-items:center;gap:10px;flex:1;min-width:0;padding:0 16px}
/* :empty is not enough on its own: the header template interpolates the Next
   Up slot across three source lines, so an absent game still leaves a
   newline+indent text node behind and the row rendered at its full reserved
   height for ~200ms before patchHeader wiped it — the 44px jump on every
   public page. site.js sets this class, which no whitespace can defeat. */
.nav-alert.is-empty{display:none}
.na-tag{
  flex:none;background:var(--hl,var(--red));color:var(--on-hl,var(--on-red));padding:2px 9px;transform:skewX(var(--skew));
  font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-label);text-transform:uppercase;
}
.na-text{
  font-family:var(--cond);font-weight:600;font-size:var(--t-sm);letter-spacing:var(--tr-tight);color:var(--hdr-text-hi,var(--white));
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;
}
a.na-text{text-decoration:none;transition:color var(--dur-1)}
a.na-text:hover{color:var(--hdr-text-hi,var(--link,var(--red-text)))}
/* game + announcement share the slot, crossfading every 7s. The items are the
   rotation's DIRECT children: Next up's lines (.na-text) and, at game time, the
   live alert and the coach's announcement (.na-item, site.js gameSlotHtml). What
   is nested inside an item is laid out by the item and is never pulled out and
   animated on its own — the alert's own words are a .na-text too. */
.na-rot{position:relative;flex:1;min-width:0;align-self:stretch;display:flex;align-items:center}
.na-rot > .na-text,.na-rot > .na-item{position:absolute;left:0;max-width:100%;animation:naSwap 14s ease-in-out infinite}
.na-rot > .na-text:nth-child(2),.na-rot > .na-item:nth-child(2){animation-delay:-7s}
@keyframes naSwap{
  0%,44%{opacity:1;visibility:visible}
  50%,94%{opacity:0;visibility:hidden}
  100%{opacity:1;visibility:visible}
}
/* three items (5U game · 10U game · announcement): 21s cycle, 7s each */
.na-rot[data-n="3"] > .na-text{animation:naSwap3 21s ease-in-out infinite}
.na-rot[data-n="3"] > .na-text:nth-child(2){animation-delay:-14s}
.na-rot[data-n="3"] > .na-text:nth-child(3){animation-delay:-7s}
@keyframes naSwap3{
  0%,29%{opacity:1;visibility:visible}
  34%,95%{opacity:0;visibility:hidden}
  100%{opacity:1;visibility:visible}
}
/* At game time the rotation is two items, the live alert first and the coach's
   announcement second, on the same 14s cycle as Next up's two lines: each is on
   screen for 7s, and an item is born with its animation at the start of its
   cycle, so after any build the alert is the one showing and is never off for
   longer than one 7s turn. An item is one flex row filling the slot (an
   absolutely positioned child of the rotation); the alert's own links keep the
   layout they have on their own, and the announcement keeps its "Next up" tag.
   Its words may take a second line before they lose their end, like the alert's:
   a rain delay is cut at the wrong place by an ellipsis. */
.na-item{top:0;right:0;bottom:0;display:flex;align-items:center;gap:10px;min-width:0}
.na-item-note .na-text{
  flex:0 1 auto;white-space:normal;line-height:1.15;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;
}
/* The keyboard stops the turning: while one of the alert's links holds keyboard
   focus the alert stays up and the announcement waits, and the rotation starts
   over at the alert when focus goes. (An item is invisible for its half of the
   cycle and its links cannot take focus then; one that has focus must not
   vanish from under it.) A pointer's focus does not count: :focus-visible. */
.nav-alert:has(:focus-visible) .na-rot > .na-item-alert{animation:none}
.nav-alert:has(:focus-visible) .na-rot > .na-item-note{animation:none;visibility:hidden}
@media (prefers-reduced-motion:reduce){
  .na-rot > .na-text,.na-rot > .na-item,.na-rot[data-n="3"] > .na-text{animation:none}
  .na-rot > .na-text:nth-child(n+2),.na-rot > .na-item:nth-child(n+2){display:none}
}
.na-season{
  flex:none;display:flex;align-items:center;gap:8px;align-self:stretch;
  padding:0 18px;border-left:1px solid var(--hl-line,var(--hairline));
  font-family:var(--display);font-size:var(--t-base);letter-spacing:var(--tr-tight);text-transform:uppercase;color:var(--hdr-text-hi,var(--white));
}
.na-season i{
  font-style:normal;font-family:var(--cond);font-weight:600;font-size:var(--t-xs);
  letter-spacing:var(--tr-eyebrow);text-transform:uppercase;color:var(--hdr-text,var(--dim));
}
/* No season set yet: a chip that reads only "Season" is a label with its
   answer missing, so it is not drawn (site.js paintSeasonChip). Two classes
   outrank the layout sheets' one, whichever loads last. */
.na-season.is-empty{display:none}

/* The live alert, in the Next up slot at game time (site.js headerSlot): a LIVE
   line with the game and its score and a Watch link, or "Today" on a tournament
   game day. Two links and nothing else, so it is the height of Next up's row.
   The tag, the score and Watch never give way; the words in the middle do, but
   by wrapping onto a second line before they ever lose their end — on a game
   day that end is "Tournament schedule", the part that says where the link
   goes, and one line of an ellipsis cut it off at every width below 1440. Two
   lines of --t-sm are 30px, inside the 44px band the phone gives the slot and
   inside the bar on desktop; past two lines the clamp ends it with an ellipsis. */
.na-live{
  flex:1 1 auto;min-width:0;align-self:stretch;
  display:flex;align-items:center;gap:10px;
  color:var(--hdr-text-hi,var(--white));text-decoration:none;
}
.na-live .na-text{
  flex:0 1 auto;white-space:normal;line-height:1.15;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;
}
.na-live:hover .na-text{color:var(--hdr-text-hi,var(--link,var(--red-text)))}
.na-live:focus-visible,.na-watch:focus-visible{outline:2px solid var(--hdr-text-hi,var(--text-1));outline-offset:-2px}
.na-tag-live{display:inline-flex;align-items:center;gap:6px}
.na-tag-live .live-dot{flex:none;width:7px;height:7px;background:var(--on-hl,var(--on-red))}
.na-score{
  flex:none;font-family:var(--cond);font-weight:600;font-size:var(--t-base);
  letter-spacing:var(--tr-label);font-variant-numeric:tabular-nums;color:var(--hdr-text-hi,var(--white));
}
.na-watch{
  flex:none;align-self:center;display:inline-flex;align-items:center;min-height:30px;padding:0 var(--s-3);
  border:1px solid var(--hdr-line,var(--hairline-strong));border-radius:var(--r-sm);
  color:var(--hdr-text-hi,var(--text-1));text-decoration:none;
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);text-transform:uppercase;
}
.na-watch::after{content:"\2192";margin-left:var(--s-2)}
@media (hover:hover){.na-watch:hover{background:var(--hdr-tint,var(--surface-hover))}}

/* Coach login: ONE anchor in the bar (site.js renderChrome), styled by width.
   Here, and on desktop, it is a compact outline button with the words at the
   far right, after the season chip; mobile.css turns the same anchor into a
   round icon-only 44x44 target immediately left of Menu. It is not a nav row —
   not in the More card, not in the phone drawer. */
.nav-coach{
  flex:none;align-self:center;display:inline-flex;align-items:center;justify-content:center;gap:var(--s-2);
  min-height:36px;margin:0 var(--s-4) 0 var(--s-3);padding:0 var(--s-3);
  border:1px solid var(--hdr-line,var(--hairline-strong));border-radius:var(--r-sm);background:transparent;
  color:var(--hdr-text,var(--text-2));text-decoration:none;white-space:nowrap;
  font-family:var(--cond);font-weight:600;font-size:var(--t-sm);letter-spacing:var(--tr-label);text-transform:uppercase;
  transition:color var(--dur-1) var(--ease-out),background-color var(--dur-1) var(--ease-out),border-color var(--dur-1) var(--ease-out);
}
.nav-coach:hover{color:var(--hdr-text-hi,var(--text-1));border-color:var(--text-3);background:var(--hdr-tint,var(--surface-hover))}
.nav-coach:active{background:var(--hdr-tint,var(--surface-active))}
.nav-coach:focus-visible{outline:2px solid var(--hdr-text-hi,var(--text-1));outline-offset:2px}
/* The glyph is for the icon-only form. */
.nav-coach-icon{display:none;flex:none}
@media (prefers-reduced-motion:reduce){.nav-coach{transition:none}}

/* ---------- auto-scrolling score ticker (home hero strip) ---------- */
/* Absolute over the hero, but ALWAYS painted (index.html no longer ships
   [hidden]). Late un-hide used to drop a 42px bar over the opener and read
   as the picture jumping. Empty track stays quiet; chips fill in place. */
.score-ticker{
  position:absolute;top:0;left:0;right:0;z-index:3;overflow:hidden;white-space:nowrap;
  height:42px;min-height:42px;max-height:42px;
  /* No backdrop-filter. It sat over a photo, not over text, so the blur bought
     nothing readable — but the strip never stops moving, so the sticky bar's
     OWN blur directly above it was re-rasterised every frame, forever, even on
     an idle page (W9/desktop F3: p99 wheel frame 50.0ms shipped vs 16.8ms with
     both blurs off). A flat fill at .82 is indistinguishable on the hero. */
  background:rgba(11,11,13,.82);
  border-bottom:1px solid rgba(42,42,48,.7);
  /* Chips used to be guillotined mid-word at both edges — the first thing a
     visitor read on a cold load was "UG 30  L 8-11 …" (W9/mobile F17). They
     fade out instead. */
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 24px,#000 calc(100% - 24px),transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 24px,#000 calc(100% - 24px),transparent 100%);
}
.ticker-track{
  display:inline-flex;align-items:center;gap:26px;
  padding:11px 0 11px 26px;min-height:42px;box-sizing:border-box;
  /* No will-change: a RUNNING animation is composited anyway; the declaration
     only pins a permanent layer for a strip that is off-screen for 95% of the
     session (W9/perf L16). */
  animation:tickerScroll 55s linear infinite;
}
.ticker-track:empty{animation:none}
@media (hover:hover){
  .score-ticker:hover .ticker-track{animation-play-state:paused}
}
.score-ticker:focus-within .ticker-track{animation-play-state:paused}
/* Touch screens keep the marquee: an earlier fix switched it off there
   because a moving link slid out from under the finger, and the owner's
   report was simply "the scores bar isn't scrolling". site.js pauses the
   strip on touch instead (wireTouchPause), which is what hover does for a
   mouse. */
.ticker-track.is-paused{animation-play-state:paused}
/* Off-screen / background-tab pause (site.js wireMarqueePause). Its own class,
   not .is-paused, so a finger lifting off the strip cannot restart a marquee
   that scrolled out of view, and scrolling back cannot cancel a touch pause. */
.ticker-track.is-away,.sponsor-scroll-track.is-away{animation-play-state:paused}
@keyframes tickerScroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
/* Reduced motion: a still strip that can be scrolled, and without the loop's
   duplicate half — a fixed 42px overflow:hidden band showed only the first
   screenful of chips and hid the rest, the per-squad records among them (R181). */
@media (prefers-reduced-motion:reduce){
  .ticker-track{animation:none}
  .score-ticker{overflow-x:auto;-webkit-overflow-scrolling:touch}
  .tk-dup{display:none}
}
.tk-chip{
  display:inline-flex;align-items:baseline;gap:8px;
  font-family:var(--cond);font-weight:600;font-size:var(--t-base);letter-spacing:var(--tr-0);color:var(--white);
}
.tk-chip i{font-style:normal;color:var(--dim);font-size:var(--t-2xs);letter-spacing:var(--tr-eyebrow);text-transform:uppercase}
/* --red on the hero strip is 3.13:1 at 14.5px; --red-text is 4.79:1. */
.tk-chip .win{color:var(--ok)}
/* A club record GameChanger could only half answer: the chip says so in the
   muted weight, not in the score weight, so "Unavailable" never reads as a score. */
.tk-chip-unknown b{color:var(--dim);font-weight:600}
.tk-chip .loss{color:var(--red-text)}
.tk-sep{color:var(--red)}
/* The wordmark was a flat 660px at every width: 46% of a 1440 screen and 35%
   of a 1920 one, which is why the 1920 hero read as a small column floating in
   258px of dead air above and 460px below (W9/desktop F7). It scales now —
   300px on a 390px phone, ~662px at 1440 (the width that already worked),
   820px at 1920. The 92% floor keeps it off the gutters on the narrowest
   phones. The dead `.hero-logo{width:min(860px,96%)}` above never applied. */
/* The 820px ceiling is the one term a club may disagree with, so it is the one
   term that is a variable (lib/hero.js logoCap, 320-1200, set by site.js from
   the saved settings). The 92% floor and the 46vw viewport term stay the
   stylesheet's: no saved number may push the mark into the gutters or shrink
   it out of sight on a phone. Unset — every club that has not touched it —
   resolves to the same 820px this rule has always used. */
.hero-content .hero-logo{width:min(92%,clamp(300px,46vw,var(--hero-logo-cap,820px)))}
/* 66ch at a flat 17.5px was three lines at 1440 AND at 1920 — the measure
   never grew with the screen. Fewer characters, larger type, two lines. */
.hero-content .tagline{font-size:var(--t-md);max-width:58ch}
/* The dues / sponsor strip that sat under the hero is gone: the four calls
   share the hero's button row again (index.html), two per row on a phone. */
[data-page="home"] .site-footer{margin-top:0}

/* ---------- page hero: ONE component, every page ---------- */
/* Every tab hero used to be its own ~25-line copy of this block — same
   padding, same wash, same title/lead/body sizes — differing only in the
   photo, the radial origin and (on /shop) a lighter veil. Ten copies meant a
   spacing fix landed on nine pages and drifted on the tenth (the lead was
   42ch on two pages and 44ch on eight). The look lives here; each page sets
   its photo and focal point and nothing else.

   The one thing ten copies of a selector list could not fix was the RAIL.
   `.wrap` carries `margin:0 auto`, and an auto CROSS-AXIS margin on a flex
   item makes that item shrink-wrap its content and centre itself — so every
   hero except /sponsors (which alone said `width:100%`) floated its copy in
   the middle of the band, landing on a different x per page because the x was
   whatever the longest line happened to measure: 337 on /schedule, 358 on
   /photos, 448 on /store, 478 on /scores, against the rail's 136 (W9-D).
   .page-hero-inner pins it back: one rail, every page, every width.

   --page-hero-image / --page-hero-pos are the RUNTIME overrides that
   Coaches Corner → Media → Page headers publishes (chrome-boot.js,
   site.js, admin-media.js); --hero-img / --hero-pos are the built-in
   fallbacks, so the var() chain is page override → built-in default.
   Reserving the min-height floor keeps Calendar → Scores → Roster soft
   navigations from collapsing/expanding the first viewport band. */
.page-hero{
  --hero-img:url("/assets/img/social-hero.webp");
  --hero-pos:center 42%;
  /* ONE wash for every header, /sponsors' — the band that reads best with the
     type on the rail. Nine pages each nudged the radial origin (8%, 72%, 85%,
     90%) and /shop ran a veil of its own, which is how "the same component"
     still managed to look like nine components. */
  --hero-wash:radial-gradient(1000px 460px at 72% -12%,rgb(var(--red-rgb) / .18),transparent 55%);
  --hero-veil:linear-gradient(180deg,rgba(6,6,8,.72) 0%,rgba(11,11,13,.68) 45%,rgba(11,11,13,.88) 100%);
  /* The side wash, tuned for the left-aligned type these heroes actually
     carry. The full-screen home hero has a proper three-stop wash
     (.hero-shade); the page heroes had only the vertical veil, so
     "Games, tournament weekends, and practices for every squad…" ran across a
     row of players' heads at 16px and "RECORDS. EVERY GAME." overlapped an
     opposing team in blue (W9/mobile F11). Darkest where the words are,
     nearly clear on the right where the photo should still read. */
  --hero-side:linear-gradient(90deg,rgba(8,8,10,.72) 0%,rgba(8,8,10,.34) 46%,rgba(8,8,10,.06) 100%);
  position:relative;
  /* desktop F11: at 1920 this was a 300px letterbox with the title pinned to
     its bottom edge and 1360px of centred column under it. The content sits on
     the left rail and the band grows with the screen instead of staying a
     strip. padding-block, symmetric, like every other section. */
  padding-block:var(--sec-sm);
  border-bottom:1px solid var(--hairline);
  min-height:clamp(220px,34vh,420px);
  display:flex;flex-direction:column;justify-content:flex-end;
  /* The cover photo and its washes are clipped to the band. */
  overflow:hidden;
  /* Cover backgrounds: keep browser default crisp sampling (no blur filters). */
  image-rendering:auto;
  background:
    var(--hero-veil),
    var(--hero-wash),
    var(--hero-side),
    var(--page-hero-image, var(--hero-img)) var(--page-hero-pos, var(--hero-pos)) / cover no-repeat;
}
/* The rail. width:100% is load-bearing — see the note above. */
.page-hero-inner{
  position:relative;z-index:2;width:100%;
  display:flex;flex-wrap:wrap;align-items:flex-end;
  gap:var(--s-5) clamp(20px,4vw,40px);
}
/* A column with a gap rather than a stack of margins: /store and /order ship
   their lead and button row `hidden` until settings say orders are open, and a
   margin on a display:none element still has to be zeroed by hand somewhere.
   A gap simply is not there, so a header with two children ends the same
   distance above the band's edge as one with four. */
.page-hero-copy{
  flex:1 1 320px;min-width:0;max-width:840px;
  display:flex;flex-direction:column;gap:var(--s-3);
}
/* No vertical margins inside the column — the gap is the spacing, and a
   stray margin (.eyebrow's 12px, /live's .lv-wx 12px) would double it. */
.page-hero-copy>*{margin-block:0}
.page-hero-title{
  font-family:var(--display);font-size:var(--d-page);line-height:var(--lh-display);
  text-transform:uppercase;letter-spacing:var(--tr-tight);margin:0;
}
.page-hero-lead{
  color:var(--text-2);font-size:var(--t-md);line-height:1.45;
  max-width:var(--measure-lead);margin:0;
}
.page-hero-lead a{color:var(--white)}
.page-hero .btn-row{margin:var(--s-3) 0 0}
/* Roster's live count, under the lead. */
.page-hero-meta{
  margin:0;
  font-family:var(--cond);font-weight:600;font-size:var(--t-sm);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--silver);
}
/* The note under a hero's button row (/schedule's calendar feed line). It used
   to sit in a floated card beside the copy — the only hero that put its
   buttons anywhere but under the lead, which is exactly what read as "way out
   to the side". No margin: the copy column's gap is the spacing. */
.page-hero-hint{margin:0;font-size:var(--t-xs);line-height:1.4;color:var(--dim)}
.page-body,.sc-body,.ro-body,.ph-body,.soc-body,.jn-body,.sh-body,.og-body,.adm-body,.cal-body{padding-block:var(--sec-md)}
/* Per-page: the photo and its focal point. Nothing else lives here — a rule
   that is not --hero-img/--hero-pos is the drift this block exists to stop. */
.soc-hero{--hero-img:url("/assets/img/social-hero.webp");--hero-pos:center 38%}
.sc-hero{--hero-img:url("/assets/img/scores-hero.webp")}
.cal-hero{--hero-img:url("/assets/img/calendar-hero.webp")}
.ro-hero{--hero-img:url("/assets/img/roster-hero.webp");--hero-pos:center 45%}
.ph-hero{--hero-img:url("/assets/img/photos-hero.webp");--hero-pos:center 48%}
.jn-hero{--hero-img:url("/assets/img/join-hero.webp");--hero-pos:center 40%}
/* store-hero.webp was byte-identical to calendar-hero.webp (745,378 B each,
   same md5) — one image, two names, 1.5 MB. /store points at the calendar
   photo now and the duplicate is gone. */
.og-hero{--hero-img:url("/assets/img/calendar-hero.webp")}
.sh-hero{--hero-img:url("/assets/img/shop-hero.webp");--hero-pos:center 58%}
.sp-hero{--hero-img:url("/assets/img/sponsors-hero.webp");--hero-pos:center 40%}
/* /live and /dues had no band at all: a bare .section, so their header sat on
   the rail at the section type ramp while the other eleven shouted from a
   photo. Same component, same rail, their own photo. */
.lv-hero{--hero-img:url("/assets/img/scores-hero.webp");--hero-pos:center 42%}
.du-hero{--hero-img:url("/assets/img/roster-hero.webp");--hero-pos:center 45%}

.ro-board{min-height:260px}
.mcal{min-height:380px}
/* Same floor, same reason: scores.html ships #sc-panels empty and the panel
   renderer fills it after the fetch, which grew the page 484->819px half a
   second in (CLS 0.274 on desktop). */
#sc-panels{min-height:420px}

/* ---------- social page ---------- */
/* The feed itself lives in assets/social.css (.sf-*) — /social is one dated
   feed now, so the four platform panels and the old Facebook page card that
   used to be styled here are gone. This page wrapper stays because the hero
   above still needs it. */
.soc-page{overflow-x:clip}
/* ---------- underline tabs + their panels (one component) ---------- */
/* The same tab bar was written twice — .soc-tab (named for the social page it
   no longer serves; Photos, Shop, the squad switchers and Coaches Corner all
   reuse it) and .tab —
   with two different active-class conventions, plus identical
   show/hide pairs for the panels. Both names and BOTH conventions stay live
   here because the JS on each page writes its own; .tab/.tab-panel is the
   name new markup should use. */
.tabs,.soc-tabs{display:flex;gap:var(--s-1);flex-wrap:wrap;margin:0;border-bottom:1px solid var(--hairline)}
/* Uppercase here too — a tab bar is a row of labels, and labels shout on this
   site. The old .14em is now --tr-label like every other one, so "ALL SQUADS ·
   5U · 6U · 10U" sits a step below the page title on tracking as well as size
   rather than competing with it. */
.tab,.soc-tab{
  font-family:var(--cond);font-weight:600;font-size:var(--t-md);letter-spacing:var(--tr-label);text-transform:uppercase;
  background:none;border:none;color:var(--text-2);cursor:pointer;
  padding:var(--s-3) var(--s-4);border-bottom:2px solid transparent;border-radius:var(--r-sm) var(--r-sm) 0 0;
  transition:color var(--dur-1) var(--ease-out),background-color var(--dur-1) var(--ease-out);
}
@media (hover:hover){.tab:hover,.soc-tab:hover{color:var(--text-1);background:var(--surface-hover)}}
.tab.is-active,.tab.active,
.soc-tab.active,.soc-tab.is-active{color:var(--text-1);border-bottom-color:var(--red)}
.tab:focus-visible,.soc-tab:focus-visible{outline:2px solid var(--text-1);outline-offset:2px}
/* A squad filter tab underlines in its own squad's colour (--sq, written onto
   the button by site.js); "All squads" has none and keeps the club red. */
.soc-tab[data-ro-squad].active,.soc-tab[data-cal-squad].active{border-bottom-color:var(--sq,var(--red))}
.tab-panel,.apanel,.spanel{display:none}
.tab-panel.is-active,.tab-panel.active,
.apanel.active,.spanel.active{display:block}
.muted-note{color:var(--dim)}

/* ---------- scores page ---------- */
.sc-page{overflow-x:clip}
#scoreboard:empty{display:none}
/* Game-day scoreboard — the site's own look: panel, brand red, display-font
   numbers, same family as the record strip and countdown cells. */
.sb{
  display:block;margin:0 0 22px;padding:20px 22px 18px;text-align:center;
  background:var(--surface-2);border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);border-left:2px solid var(--red);
  text-decoration:none;color:var(--white);
}
.sb:focus-visible{background:var(--panel-2)}
@media (hover:hover){.sb:hover{background:var(--panel-2)}}
.sb-eyebrow{
  display:flex;align-items:center;justify-content:center;gap:10px;
  font-family:var(--cond);font-weight:600;font-size:var(--t-sm);letter-spacing:var(--tr-eyebrow);
  text-transform:uppercase;color:var(--link,var(--red-text));margin:0 0 12px;
}
.sb-row{
  display:flex;align-items:center;justify-content:center;gap:clamp(14px,4vw,30px);
}
.sb-team{
  flex:1;min-width:0;font-family:var(--display);text-transform:uppercase;
  font-size:var(--t-lg);line-height:1.15;
}
.sb-team:first-child{text-align:right}
.sb-team:last-child{text-align:left}
.sb-score{
  font-family:var(--display);font-size:var(--d-page);line-height:1;
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
.sb-score i{font-style:normal;color:var(--dim);font-size:.55em;vertical-align:.14em;padding:0 6px}
.sb-meta{display:block;margin:12px 0 0;font-size:var(--t-sm);color:var(--silver)}
.sb-meta u{text-decoration:none;color:var(--link,var(--red-text));font-weight:600}
.g-scrim{
  font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-0);
  color:var(--dim);border:0;border-radius:var(--r-xs);padding:2px 6px;
}
/*
 * Scores page: Overall and Season as collapsed <details> panels — the
 * page's primary control now, so the head needs a real hover/focus state
 * and a keyboard-visible outline, not just a click target.
 */
.sc-panels{display:flex;flex-direction:column;gap:18px}
/*
 * Squad chip row — same idiom as Tournament HQ's .lv-schip switcher
 * (assets/live.js): the chip IS the tab, carrying both this squad's overall
 * record (b) and its record for whatever season is selected below (span),
 * so filtering the panels to one squad never loses the two-squad
 * comparison the old side-by-side header gave for free.
 */
.sc-chips{display:flex;flex-wrap:wrap;gap:8px}
/* A filter, not a banner: `flex:1 1 150px` stretched each chip to 628 x 56px
   at 1440 to hold "6U 0-2 / Fall 2026 · 0-2" — a 150px control blown up 4.2x
   (W9/desktop F5). Content-sized, left-aligned, capped. */
.sc-schip{
  flex:0 0 auto;min-width:168px;max-width:260px;min-height:44px;
  display:flex;flex-direction:column;gap:3px;align-items:flex-start;text-align:left;
  background:var(--surface-2);border:0;border-left:2px solid transparent;border-radius:var(--r-sm);
  padding:10px 14px;cursor:pointer;color:var(--silver);
  font-family:var(--cond);-webkit-tap-highlight-color:transparent;
  transition:background-color var(--dur-1),border-color var(--dur-1),color var(--dur-1);
}
@media (hover:hover){.sc-schip:hover{background:var(--surface-3)}}
.sc-schip:focus-visible{outline:2px solid var(--white);outline-offset:2px}
.sc-schip.on{
  background:var(--surface-3);
  border-left-color:var(--sq,var(--red));color:var(--text-1);
}
.sc-schip b{font-family:var(--display);font-size:var(--t-md);letter-spacing:var(--tr-tight);line-height:1;color:inherit}
.sc-schip b i{
  font-style:normal;font-size:var(--t-xs);margin-left:7px;font-family:var(--cond);
  font-variant-numeric:tabular-nums;opacity:.78;
}
.sc-schip span{
  font-size:var(--t-xs);letter-spacing:var(--tr-tight);color:var(--dim);
  font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;
}
.sc-schip.on span{color:var(--silver)}
.sc-panel{
  border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);background:var(--surface-2);
  padding:0;overflow:hidden;
}
/* 108.3px of summary for the string "Overall  5-5-0" on a phone, and at 1440 a
   1264 x 90px bar with ~1,000px of nothing between the title and the record
   (W9/mobile F7, desktop F5). The record sits next to the title now and the
   bar is ~58px tall — the disclosure is a control, not a hero band. */
.sc-panel-head{
  position:relative;list-style:none;cursor:pointer;
  display:flex;flex-wrap:wrap;align-items:center;gap:6px 20px;
  padding:17px 48px 17px 20px;
  background:var(--surface-3);
  transition:background-color var(--dur-1) var(--ease-out);
}
@media (hover:hover){.sc-panel-head:hover{background:var(--surface-hover)}}
.sc-panel-head:focus-visible{outline:2px solid var(--white);outline-offset:-3px}
/* flex:0 0 auto — `1 1 auto` was what pushed the record 1,000px away.
   The selector has to be `h2.sc-panel-title`: the class alone is (0,1,0) and
   lost to `.section h2{font-size:clamp(28px,4.5vw,40px)}` at (0,1,1), which is
   why the panel title rendered at 40px and the "20-26px" written here was
   never what shipped. */
h2.sc-panel-title{
  flex:0 0 auto;
  font-family:var(--display);font-size:var(--d-sect);
  text-transform:uppercase;letter-spacing:var(--tr-tight);line-height:1.1;margin:0;
}
.sc-panel-recs{display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center}
.sc-panel-rec{
  display:inline-flex;align-items:baseline;gap:7px;
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);
  text-transform:uppercase;color:var(--dim);
}
.sc-panel-rec b{color:var(--text-2);letter-spacing:var(--tr-label)}
.sc-panel-rec i{
  font-style:normal;font-family:var(--display);font-size:var(--t-lg);letter-spacing:var(--tr-tight);
  color:var(--text-1);font-variant-numeric:tabular-nums;
}
.sc-panel-rec.sc-panel-none{color:var(--dim)}
.sc-panel-head::after{
  content:"";position:absolute;right:22px;top:50%;width:9px;height:9px;
  border-right:2px solid var(--silver);border-bottom:2px solid var(--silver);
  transform:translateY(-70%) rotate(45deg);
  transition:transform var(--dur-2) var(--ease-out);
}
.sc-panel[open] .sc-panel-head::after{transform:translateY(-30%) rotate(-135deg)}
.sc-panel-body{padding-top:2px}
/* A tapped panel went from 110.3px to 2,137.0px in ONE frame — 2.4 phone
   screens dumped into the page with transition-duration:0s, which shoved the
   two panels below it off the screen (W9/mobile F7). `interpolate-size` is
   what lets a height animate to `auto`; `interpolate-size` is inherited, so it
   is set on the panel rather than on :root. Browsers without it keep today's
   instant open — no fallback max-height, because a wrong guess clips a season
   with 40 games in it. */
@supports (interpolate-size:allow-keywords){
  .sc-panel{interpolate-size:allow-keywords}
  .sc-panel::details-content{
    block-size:0;overflow:hidden;
    transition:block-size var(--dur-3) var(--ease-out),content-visibility var(--dur-3) allow-discrete;
  }
  .sc-panel[open]::details-content{block-size:auto}
  @media (prefers-reduced-motion:reduce){
    .sc-panel::details-content{transition:none}
  }
}
.sc-grp{border-top:1px solid var(--hairline)}
.sc-grp-head{padding:20px 24px 4px}
.sc-grp-title{
  font-family:var(--display);font-size:var(--d-sect);
  text-transform:uppercase;letter-spacing:var(--tr-tight);margin:0 0 12px;line-height:1;
}
.sc-grp-head .record-strip{margin:0}
.sc-block{padding:22px 24px 24px}
.sc-grp-actions{margin-top:18px}
.sc-page .game-row{background:var(--ink)}
.sc-page .empty{margin-top:0}
.sc-page .empty a{color:var(--white)}

/* ---------- calendar page ---------- */
.cal-page{overflow-x:clip}
.cal-legend{
  list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:10px 18px;
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--silver);
}
.cal-legend i{
  display:inline-block;width:14px;height:8px;margin-right:8px;vertical-align:middle;
  border-left:3px solid transparent;
}
/* Scoped to .cal-legend so these beat .cal-legend i's border-left on
   specificity instead of on !important. */
.cal-legend .cal-leg-game{background:var(--red-deep);border-left-color:var(--red)}
.cal-legend .cal-leg-tourn{background:#4a3d1c;border-left-color:var(--gold)}
.cal-legend .cal-leg-prac{background:#3a3a44;border-left-color:#9aa0ab}
/* No `:empty{display:none}` here any more: the card is never empty. It ships
   its own "nothing yet" line in schedule.html and renderCountdown puts that
   line back whenever there is no game, so the slot holds one box whatever the
   schedule says — an empty box that fills after the load event is the shift
   this page was the worst case for. The floor that box reserves is in
   assets/desktop.css, the width where the card is above the fold. */
.cal-next{
  margin:0 0 28px;padding:22px 24px 20px;border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);
  background:linear-gradient(135deg,rgb(var(--red-rgb) / .12) 0%,var(--panel) 42%);
  border-left:2px solid var(--red);
}
.cal-next .eyebrow{margin-bottom:8px}
.cal-next .display{margin:0 0 4px}
.cal-next .countdown{margin-top:16px}
/* The card with no game in it: the same eyebrow and the same display line as
   the card with one, muted, and no countdown under it — so the box reads as
   this card in its empty state rather than as a card that failed to load. */
.cal-next .cd-none{margin:0;color:var(--text-2)}
.cal-page .cd-cell{
  background:var(--ink);border-color:#32323a;min-width:78px;
}
.cal-toolbar{
  margin:0 0 18px;display:flex;flex-wrap:wrap;align-items:center;
  justify-content:space-between;gap:12px 20px;
}
.cal-squads{margin:0}
.cal-toolbar .cal-legend{margin:0}
.cal-subscribe-note{margin-top:28px;max-width:64ch}
.cal-subscribe-note .form-note{margin:0 0 8px}

/* ---------- the fold: a collapsed card with a one-line summary ---------- */
/* Collapsed by default — the schedule's calendar is the page's main view, and the
   summary line carries the count so nobody opens it to find out it's empty. /sponsors
   folds its comparison table the same way. .fold is the name new markup uses;
   .sched-fold is the schedule's own spelling of it. */
.fold,.sched-fold{border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);background:var(--surface-2);margin:28px 0 0}
.fold>summary,.sched-fold>summary{
  cursor:pointer;list-style:none;padding:16px 20px;
  font-family:var(--cond);font-weight:600;font-size:var(--t-base);letter-spacing:var(--tr-label);text-transform:uppercase;
}
.fold>summary::before,.sched-fold>summary::before{content:"\25B8  ";color:var(--red)}
.fold[open]>summary::before,.sched-fold[open]>summary::before{content:"\25BE  "}
@media (hover:hover){.fold>summary:hover,.sched-fold>summary:hover{background:rgb(var(--red-rgb) / .07)}}
.fold>summary:focus-visible,.sched-fold>summary:focus-visible{outline:2px solid var(--red);outline-offset:-2px}
.fold>div,.sched-fold>div{padding:0 16px 16px}
.sched-fold .game-row:last-child{margin-bottom:0}

/* ---------- daltonbaseballclub calendar ---------- */
.mcal{
  border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);background:var(--surface-2);margin:0;
  box-shadow:0 18px 50px rgba(0,0,0,.28);
}
.cal-head{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:18px 20px;border-bottom:1px solid var(--hairline);
  background:linear-gradient(180deg,var(--panel-2) 0%,var(--panel) 100%);
}
.cal-head-mid{display:flex;flex-direction:column;align-items:center;gap:8px;min-width:0}
.cal-title{
  font-family:var(--display);font-size:var(--d-sect);
  text-transform:uppercase;letter-spacing:var(--tr-tight);line-height:1;
}
.cal-today{
  appearance:none;background:transparent;border:0;border-radius:var(--r-sm);color:var(--silver);
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);text-transform:uppercase;
  padding:8px 12px;cursor:pointer;transition:border-color var(--dur-1),color var(--dur-1),background var(--dur-1);
}
@media (hover:hover){.cal-today:hover{border-color:var(--red);color:var(--white)}}
.cal-today:focus-visible{outline:2px solid var(--white);outline-offset:2px}
.cal-nav{
  font-family:var(--cond);font-weight:600;font-size:var(--t-lg);color:var(--white);
  background:var(--surface-2);border:0;border-radius:var(--r-sm);cursor:pointer;padding:10px 18px;
  transition:background-color var(--dur-1) var(--ease-out);
}
@media (hover:hover){.cal-nav:hover{background:var(--surface-3)}}
.cal-nav:focus-visible{outline:2px solid var(--white);outline-offset:2px}
/* minmax(0,1fr), not 1fr: a bare 1fr can't shrink below its content, so one
   long nowrap chip would stretch its whole column and warp the grid. */
.cal-grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:1px;background:var(--line)}
.cal-dow span{
  background:var(--ink);padding:11px 6px;text-align:center;
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-eyebrow);text-transform:uppercase;color:var(--dim);
}
.cal-days{border-top:1px solid var(--hairline)}
/* Lighter than the page ink on purpose: the practice chips are gray, and gray
   on near-black was a camouflage pattern, not a calendar. */
.cal-cell{background:var(--panel-3);min-height:104px;padding:8px;font-size:var(--t-xs);transition:background var(--dur-1)}
.cal-cell.weekend{background:#1a1a20}
.cal-cell.has-ev{background:var(--panel-4)}
.cal-cell.cal-pad{background:var(--ink)}
/* --silver, not --dim: these numbers sit on --panel-3/--panel-4, which are
   lighter than --ink and --panel, and --dim measures only 4.08:1 / 3.88:1
   there. --silver is 7.2:1 / 6.86:1. */
.cal-cell>b{
  font-family:var(--cond);font-weight:600;font-size:var(--t-sm);color:var(--silver);
  display:inline-flex;align-items:center;justify-content:center;
  width:26px;height:26px;margin:0 0 6px;border-radius:50%;
}
.cal-cell.today{background:rgb(var(--red-rgb) / .07)}
.cal-cell.today>b{background:var(--hl,var(--red));color:var(--on-hl,var(--on-red))}
/* Chips are buttons now — same look, plus the affordances a button owes. */
button.cal-ev{font:inherit;border:0;text-align:left;width:100%;cursor:pointer}
@media (hover:hover){button.cal-ev:hover{filter:brightness(1.2);transform:translateY(-1px)}}
/* The ring in --text-1, not --red: the default red was 2.68:1 on an event cell
   and a dark brand colour disappears entirely (R124). */
button.cal-ev:focus-visible{outline:2px solid var(--text-1);outline-offset:1px}
.cal-pop{text-align:left;max-width:480px}
.cal-pop .meta{text-align:center}
.cal-pop h3{text-align:center;margin-bottom:14px}
.cal-pop-rows{display:flex;flex-direction:column;gap:8px;margin-top:6px}
.cal-pop-row{display:flex;gap:14px;font-size:var(--t-base)}
.cal-pop-row i{flex:0 0 92px;font-style:normal;color:var(--dim);font-family:var(--cond);font-weight:600;letter-spacing:var(--tr-label);text-transform:uppercase;font-size:var(--t-xs);padding-top:2px}
.cal-dir{color:inherit;text-decoration:underline;text-underline-offset:3px}
.cal-pop-row span{color:var(--silver)}
.cal-ev{
  display:block;margin-bottom:4px;padding:5px 7px;line-height:1.3;
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-tight);
  background:var(--red-deep);color:var(--on-deep,#fff);border-left:2px solid var(--red);border-radius:var(--r-xs);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  transition:filter var(--dur-1),transform var(--dur-1);
}
.cal-ev.cal-ev-hidden{display:none}
.cal-cell.expanded .cal-ev.cal-ev-hidden{display:block}
.cal-more{
  display:block;width:100%;margin-top:2px;padding:3px 6px;border:0;background:transparent;
  color:var(--silver);font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-label);
  text-transform:uppercase;text-align:left;cursor:pointer;
}
.cal-more:hover{color:var(--white)}
.cal-more:focus-visible{outline:2px solid var(--red);outline-offset:1px}
.cal-ev.practice{background:#3a3a44;border-left-color:#9aa0ab;color:#e3e5e9}
/* Holidays: plain muted label — no filled chip like practices/games. */
.cal-ev.holiday{
  background:transparent;border-left-color:transparent;color:#9aa3b0;
  font-weight:500;padding-left:2px;padding-right:2px;
}
@media (hover:hover){button.cal-ev.holiday:hover{filter:none;transform:none;color:#c0c6d0}}
button.cal-ev.holiday:focus-visible{outline-offset:2px}
/* Tournaments in gold: the weekend we're playing in, not a team we play. */
.cal-ev.tournament{background:#4a3d1c;border-left-color:var(--gold);color:#f2e5c4}
/* Whose entry this is, before anything else on the chip.

   The colour comes from --sq, which site.js sets on the chip itself from
   settings.squads (with the squad's built-in token as the var()'s fallback).
   There used to be one rule per squad id here, which is a deploy for every
   squad the club adds — and three ids was already two more than the CSS could
   know about. */
.cal-sq{
  display:inline-block;padding:0 4px;margin-right:2px;
  background:var(--sq,rgba(255,255,255,.14));border-radius:var(--r-xs);
  font-size:var(--t-xs);letter-spacing:var(--tr-tight);color:var(--on-sq,#fff);
}
.cal-ev .cal-sq + .cal-sq{margin-left:0}
.cal-sq i{font-style:normal}
.cal-sqs{display:inline}
.cal-lbl{display:inline}

/* ---------- admin photo upload ---------- */
.upload-zone{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;
  border:0;border-radius:var(--r-md);background:var(--surface-1);
  box-shadow:inset 0 0 0 1px var(--hairline-strong);padding:38px 24px;text-align:center;
  font-size:var(--t-base);color:var(--silver);cursor:pointer;transition:border-color var(--dur-1),background var(--dur-1);
}
@media (hover:hover){.upload-zone:hover{border-color:var(--red);background:#17070b}}
/* The file input inside is visually hidden, so the zone is the only thing a
   keyboard user can see take focus (R127). */
.upload-zone:focus-within{outline:2px solid var(--white);outline-offset:2px}
.upload-zone.drag{border-color:var(--red);background:#1d070c;border-style:solid}
/* A DIRECT child (>), :first-child alone: a drop zone's own heading is
   always its first element (every caller in admin.html), but one of them
   (Photos' own zone) also has a second, inline <b> mid-sentence ("the
   active album") for a single emphasised word — a bare `.upload-zone b`
   (a descendant selector) caught that one too and rendered it as its own
   display:block, --t-xl line. `:first-child` alone was not enough by
   itself either: admin.html now wraps that sentence in its own <span> so
   .upload-zone's own display:flex (flex-direction:column) cannot blockify
   the inline <b> the way it blockifies every element that IS one of its
   direct children (an unrelated CSS mechanism from this selector — a flex
   item's own display is always blockified, regardless of what display it
   was actually given) — but a `<b>` that is the first element inside that
   NEW span is also, technically, ITS OWN parent's first-child, so without
   the `>` here this rule matched it there too (both found 2026-09-28).
   Nothing outside admin.html uses this class, so this narrowing cannot
   move a pixel on the public site. */
.upload-zone > b:first-child{display:block;font-family:var(--display);text-transform:uppercase;font-size:var(--t-xl);letter-spacing:var(--tr-tight);color:var(--white)}
.row-del{
  background:var(--ink);border:0;border-radius:var(--r-sm);color:var(--silver);cursor:pointer;
  font-size:var(--t-base);line-height:1;padding:8px 10px;
}
@media (hover:hover){.row-del:hover{color:var(--on-red);background:var(--red);border-color:var(--red)}}
.game-row .row-del{margin-left:12px}
.org-tag{
  display:inline-block;margin-left:7px;padding:1px 6px;vertical-align:middle;
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--silver);border:0;border-radius:var(--r-xs);
}

/* ---------- buttons ---------- */
/* ONE button, three variants, two sizes.
   It used to be one look for every job: skewX(-8deg), 16px condensed CAPS at
   .12em on a 1px grey box, 49-55px tall depending on which sheet last touched
   it. /schedule shipped two of them side by side at 371px each, so
   "Subscribe to Calendar" and "Download .ics" carried identical weight and the
   page had no first action. A parallelogram is a 1990s sports-graphic tic —
   fine on .na-tag and .slash, where it reads as energy; on every button on a
   parents' sign-up form it reads as WordArt. The skew and the counter-skewed
   <span> wrapper are both gone; --skew now has exactly three consumers.
   The CAPS are not gone — the owner asked for them back after Wave B, and a
   button is exactly the kind of label this site shouts. What the component
   keeps from Wave B is everything structural: one shape, three variants, two
   sizes, a 48px height and --r-sm corners, so /schedule still has a first
   action and a second one rather than two identical 371px slabs. */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s-2);
  text-decoration:none;cursor:pointer;
  font-family:var(--cond);font-weight:600;font-size:var(--t-base);
  letter-spacing:var(--tr-label);text-transform:uppercase;
  min-height:48px;padding:0 var(--s-5);border-radius:var(--r-sm);
  border:1px solid var(--hairline-strong);color:var(--text-1);background:var(--surface-2);
  transform:none;
  transition:background-color var(--dur-1) var(--ease-out),
             border-color var(--dur-1) var(--ease-out),
             box-shadow var(--dur-1) var(--ease-out);
}
.btn>span{display:inline-flex;transform:none;align-items:center;gap:var(--s-2)}
/* Gated on a real pointer. iOS Safari LATCHES :hover after a tap, so a tapped
   button kept its hover surface until something else was tapped — which is a
   large part of what "cemented" describes (W9/mobile F5). */
@media (hover:hover){.btn:hover{background:var(--surface-3)}}
/* Primary carries the club red and a 1px inner light edge, which is what makes
   a filled button read as lit rather than as a red rectangle. */
.btn.primary{background:var(--btn-bg,var(--red));border-color:transparent;color:var(--btn-text,var(--on-red));box-shadow:inset 0 1px 0 rgba(255,255,255,.14)}
@media (hover:hover){.btn.primary:hover{background:var(--btn-bg-hover,var(--red-hover));box-shadow:inset 0 1px 0 rgba(255,255,255,.14),0 6px 18px -6px rgb(var(--btn-glow,var(--red-rgb)) / .55)}}
/* The variant the site was missing: a real second action. */
.btn.secondary{background:var(--surface-2);border-color:var(--hairline-strong);color:var(--text-1)}
@media (hover:hover){.btn.secondary:hover{background:var(--surface-3);border-color:var(--hairline-strong)}}
.btn.ghost{background:none;border-color:transparent;color:var(--text-2);padding-inline:var(--s-3)}
@media (hover:hover){.btn.ghost:hover{background:var(--surface-hover);color:var(--text-1)}}
.btn.sm{min-height:38px;padding:0 var(--s-4);font-size:var(--t-sm)}
.btn.disabled,.btn:disabled,.btn[disabled]{opacity:.45;pointer-events:none;cursor:default}
.btn:focus-visible{outline:2px solid var(--text-1);outline-offset:3px}
.btn-row{display:flex;flex-wrap:wrap;gap:var(--s-3);margin-top:var(--s-6)}
/* Dialog button rows: centred under centred copy, and close to it. */
.btn-row.center{justify-content:center;margin-top:var(--s-3)}
/* Compound so it outranks .btn-row.center, which it is always used with. */
.btn-row.btn-row-tight{margin-top:var(--s-1)}

/* ---------- cards / grids ---------- */
/* THE container primitive. Tone and elevation say what contains what; a
   four-sided 1px box does not. Before this there were 96 identical hairline
   rectangles in site.css alone — 28 of them in view at once on /team — and
   every "card" on the site (.fgroup, .sc-panel, .game-row, .sponsor-card,
   .tr-card, /schedule's old hero aside) re-declared the same background + border +
   a different padding rather than being this one.
   Rule, applied everywhere below: a card on the page ground is --surface-2 +
   --elev-1 + radius and NO border; anything nested inside a card is
   --surface-3 with no border and no shadow, because the tone step IS the
   container. */
.card,.surface{
  background:var(--surface-2);border:0;border-radius:var(--r-md);
  box-shadow:var(--elev-1);padding:var(--card-pad);
}
.card > .card,.surface-nested{
  background:var(--surface-3);border:0;box-shadow:none;border-radius:var(--r-sm);
}
/* Only when the card is itself a link — a hover that lifts something you
   cannot click is the site telling you a lie. */
@media (hover:hover){a.card:hover,.card > a:hover{box-shadow:var(--elev-2)}}
.card h3{font-family:var(--display);font-size:var(--d-sect);text-transform:uppercase;letter-spacing:var(--tr-tight);line-height:var(--lh-display);margin:0 0 var(--s-3)}
.card p{color:var(--text-2);margin:0 0 var(--s-3)}
/* .meta is the quiet kicker above a card or dialog heading — the same idiom as
   .kicker, kept as its own name because ~20 templates spell it. It is aliased
   onto .kicker's values rather than carrying its own size and tracking, which
   is how it came to be one of the seven near-identical label classes. */
.meta{font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-eyebrow);text-transform:uppercase;line-height:var(--lh-tight);color:var(--text-3);margin:0 0 var(--s-2)}

/* ---------- countdown ---------- */
.countdown{display:flex;gap:var(--s-3);flex-wrap:wrap;margin-top:var(--s-4)}
/* The `border-top:3px solid var(--red)` this used to wear was one of seven
   red top rules used as the site's only emphasis mechanism — here it marked
   nothing at all (every cell had it, so it distinguished no cell from any
   other). Tone and elevation carry the cell now. */
.cd-cell{background:var(--surface-2);border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);padding:var(--s-3) var(--s-2);min-width:86px;text-align:center}
.cd-cell b{font-family:var(--display);font-size:var(--t-3xl);display:block;line-height:1;font-variant-numeric:tabular-nums}
.cd-cell span{font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-eyebrow);text-transform:uppercase;color:var(--text-3)}

/* ---------- schedule / results ---------- */
/* A result row is a card, and the club-red left rule is the one place that
   accent still earns its keep — it says whose game this is. 2px, not 4px:
   the rule is a mark, not a border. */
.game-row{
  display:flex;flex-wrap:wrap;gap:var(--s-2) var(--s-5);align-items:baseline;
  background:var(--surface-2);border:0;border-left:2px solid var(--red);
  border-radius:var(--r-sm);box-shadow:var(--elev-1);
  padding:var(--s-4) var(--s-5);margin-bottom:var(--s-3);
}
.game-row.practice{border-left-color:var(--dim)}
/* Holidays in What's coming up: unboxed text row, not a practice panel. */
.game-row.holiday{
  background:transparent;border:0;border-left:0;padding:8px 4px;margin-bottom:4px;
  color:#9aa3b0;
}
.game-row.holiday .g-opp{font-weight:400;color:#b4bbc6}
.game-row.holiday .g-date,.game-row.holiday .g-loc,.game-row.holiday .g-time{color:#8a93a3}
.game-row.tournament{border-left-color:var(--gold)}
/* A game called off: the row stays on the list, muted and tagged, so a family
   sees that it was called off rather than wondering why it is gone. */
.game-row.off{border-left-color:var(--dim)}
.game-row.off .g-opp{color:var(--text-2);text-decoration:line-through;text-decoration-color:var(--dim)}
.g-off{
  font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-0);text-transform:uppercase;
  color:var(--red-text);border:1px solid currentColor;border-radius:var(--r-xs);padding:2px 6px;
}
.game-row .g-date{font-family:var(--display);font-size:var(--t-lg);letter-spacing:var(--tr-tight);text-transform:uppercase;min-width:130px}
.game-row .g-opp{font-weight:600}
.game-row .g-loc,.game-row .g-time{color:var(--text-2);font-size:var(--t-base)}
.game-row .g-result{margin-left:auto;font-family:var(--display);font-size:var(--t-lg)}
/* 19px Anton is not "large text" (24px / 18.67px bold), so the loss marker
   needs the AA red, not the display red. */
.g-result .win{color:var(--ok)}.g-result .loss{color:var(--red-text)}

/* No dashed border. A dashed rectangle is the "drop your content here" idiom
   from a slide template; an empty state on a finished site is a quieter
   surface with the same radius as everything else, and it says so in words. */
.empty{
  border:0;background:var(--surface-1);border-radius:var(--r-md);
  padding:var(--s-7) var(--s-6);text-align:center;color:var(--text-2);
}
.empty b{display:block;font-family:var(--display);font-size:var(--t-xl);text-transform:uppercase;color:var(--text-1);margin-bottom:var(--s-2);letter-spacing:var(--tr-tight)}
/* The retry button sits directly under the outage copy inside .empty — it has
   no .btn-row to give it the usual 34px. */
.retry-btn{margin-top:12px}

/* A live STATUS message, which is not the same thing as "there is nothing here
   yet". /store shipped "TEAM ORDERS ARE CLOSED" — real, current, important
   information — inside .empty: a 1264 x 156px DASHED, centred placeholder box
   sitting directly above a solid, left-aligned card of the same width. Two
   adjacent boxes, two contradictory alignments, one dashed and one solid
   (W9/desktop F10). .empty means "empty"; .notice is the banner. */
.notice{
  background:rgb(var(--red-rgb) / .08);
  border-left:3px solid var(--red);border-radius:var(--r-sm);
  padding:var(--s-4) var(--s-5);text-align:left;color:var(--text-2);max-width:var(--measure-body);
}
.notice b{
  display:block;font-family:var(--cond);font-weight:600;font-size:var(--t-md);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--text-1);margin-bottom:var(--s-1);
}
.notice a{color:var(--text-1)}

/* ---------- live banner ---------- */
/* A live game is the loudest thing on the page, so this one keeps a real fill
   and gets --elev-2 rather than a 1px outline of its own background colour. */
.live-banner{
  /* One colour of text runs across the whole banner, so the blend toward the main
     colour stops where that text would not read on it: lib/colours.js writes
     --banner-end (the deep colour again) in that case, and the text colour with
     the deep colour. Without a saved set both fall back to what was always here. */
  background:linear-gradient(115deg,var(--red-deep),var(--banner-end,var(--red)));color:var(--on-deep,inherit);
  border:0;border-radius:var(--r-md);box-shadow:var(--elev-2);
  padding:var(--card-pad-lg);display:flex;flex-wrap:wrap;gap:var(--s-5);align-items:center;
}
.live-banner .lb-label{font-family:var(--cond);font-weight:600;letter-spacing:var(--tr-eyebrow);text-transform:uppercase;font-size:var(--t-2xs);display:flex;align-items:center;gap:var(--s-2)}
.live-banner .lb-score{font-family:var(--display);font-size:var(--d-sect);line-height:1;font-variant-numeric:tabular-nums}
.live-banner .btn{background:var(--ink);border-color:transparent}

.record-strip{display:flex;gap:var(--s-3);margin:var(--s-3) 0 var(--s-4)}
.record-strip .cd-cell{min-width:100px}

/* season picker — toolbar control, filled red to match primary CTAs */
.season-pick{display:inline-flex;align-items:center;gap:10px;margin:0}
.season-pick span{
  font-family:var(--cond);font-weight:600;text-transform:uppercase;
  letter-spacing:var(--tr-label);font-size:var(--t-2xs);color:var(--text-2)
}
.season-pick select{
  appearance:none;-webkit-appearance:none;
  background:var(--red) 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='%23fff' d='M1.1 1.2l4.9 4.7 4.9-4.7 1.1 1.2-6 5.7-6-5.7z'/%3E%3C/svg%3E") no-repeat right 12px center / 10px;
  color:var(--on-red);border:1px solid var(--red);border-radius:var(--r-sm);
  padding:11px 34px 11px 16px;font-family:var(--cond);font-weight:600;font-size:var(--t-base);
  letter-spacing:var(--tr-label);text-transform:uppercase;cursor:pointer;
}
@media (hover:hover){
.season-pick select:hover{
  background-color:var(--red-hover);border-color:var(--red-hover);
  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='%23fff' d='M1.1 1.2l4.9 4.7 4.9-4.7 1.1 1.2-6 5.7-6-5.7z'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 12px center;background-size:10px;
}
}
.season-pick select:focus-visible{outline:2px solid var(--white);outline-offset:3px}
.season-pick select option{background:var(--panel);color:var(--white);text-transform:none;letter-spacing:normal}

/* ---------- gallery ---------- */
.carousel{position:relative;background:var(--surface-2);border:0;border-radius:var(--r-md);aspect-ratio:16/9;overflow:hidden}
.carousel img{width:100%;height:100%;object-fit:cover;position:absolute;inset:0;opacity:0;transition:opacity var(--dur-3) var(--ease-out)}
.carousel img.active{opacity:1}
.car-pause{position:absolute;right:10px;bottom:10px;z-index:2;padding:6px 12px;border-radius:var(--r-xs);
  background:rgba(0,0,0,.62);color:#fff;border:1px solid rgba(255,255,255,.45);font:inherit;font-size:var(--t-xs);
  letter-spacing:var(--tr-0);cursor:pointer}
.car-pause:focus-visible{outline:2px solid #fff;outline-offset:2px}
/* R088 / R236: the Facebook timeline mounts on a tap, not on page view. */
.fb-gate{display:grid;gap:12px;justify-items:start;padding:var(--s-3)}
iframe.fb-embed{border:0}
.thumb-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:var(--s-3);margin-top:var(--s-3)}
/* A photo does not need an outline; the radius is what says "this is a tile". */
.thumb-grid img{width:100%;aspect-ratio:4/3;object-fit:cover;border:0;border-radius:var(--r-sm);cursor:pointer}

.lightbox{
  position:fixed;inset:0;z-index:var(--z-overlay);cursor:zoom-out;
  background:rgba(6,6,8,.94);display:flex;align-items:center;justify-content:center;
}
/* svh, not vh: vh is the LARGE viewport on iOS, so with the address bar shown
   a portrait photo ran past the bottom of the screen with no way to pan to it
   (sweep A, suspected list). */
.lightbox img{max-width:94vw;max-height:92svh;width:auto;height:auto;border:0;border-radius:var(--r-md);box-shadow:var(--elev-3)}

/* ---------- TeamReach access modal ---------- */
/* align-items:center with no overflow means a card taller than the viewport
   is clipped at BOTH ends and cannot be scrolled to — the calendar popup runs
   to 480-560px with six rows and a stacked button row, which a phone in
   landscape cannot show. Scroll the veil, cap the card. */
.tr-veil{
  position:fixed;inset:0;z-index:var(--z-modal);padding:20px;
  background:rgba(6,6,8,.9);display:flex;align-items:flex-start;justify-content:center;
  overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
}
/* The dialog that covers the whole page used to be the FLATTEST thing on it —
   box-shadow:none, a 1px grey box and a red top rule — while the sponsor tier
   cards behind it floated. Elevation is what says "this is on top": --elev-3,
   the largest step, and nothing else on the site may use it.
   Entrance on --dur-3, the duration reserved for things that cover content. */
.tr-card{
  position:relative;max-width:440px;width:100%;text-align:center;
  background:var(--surface-3);border:0;border-radius:var(--r-lg);box-shadow:var(--elev-3);
  padding:var(--s-6) var(--s-6) var(--s-5);
  margin:auto;max-height:calc(100svh - 40px);overflow-y:auto;
  animation:trCardIn var(--dur-3) var(--ease-out) both;
}
@keyframes trCardIn{from{opacity:0;transform:translateY(10px) scale(.985)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.tr-card{animation:none}}
/* A bare glyph is ~15x26px. 44px is the touch minimum, and this is the only
   way out of the calendar/TeamReach dialog on a phone. */
.tr-x{
  position:absolute;top:6px;right:8px;background:none;border:none;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;padding:0;
  font-size:var(--t-2xl);line-height:1;color:var(--dim);
}
.tr-x:focus-visible{outline:2px solid var(--white);outline-offset:-2px}
.tr-x:hover{color:var(--white)}
.tr-logo{height:30px;width:auto;margin-bottom:16px;opacity:.95}
.tr-card h3{font-family:var(--display);text-transform:uppercase;font-size:var(--d-sect);letter-spacing:var(--tr-tight);line-height:var(--lh-display);margin:0 0 var(--s-3);color:var(--text-1)}
.tr-card p{color:var(--text-2);font-size:var(--t-base);line-height:var(--lh-body);margin:0 0 var(--s-3)}
/* Nested inside the dialog card, so: tone step, no border, no shadow. */
.tr-card .tr-note{
  background:rgb(var(--red-rgb) / .1);border:0;border-radius:var(--r-sm);
  padding:var(--s-3) var(--s-4);
  color:var(--text-2);font-size:var(--t-base);margin:0 0 var(--s-5);
}
.tr-note b{color:var(--white)}

/* ---------- forms ---------- */
form{margin:0}
/* The red top rule came off here too: every fieldset on /join had one, so it
   marked no fieldset as different from any other. A form group is a card. */
.fgroup{background:var(--surface-2);border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);padding:var(--card-pad);margin-bottom:var(--s-5)}
/* h2 as well: /join, /order and /store each typed this same declaration
   inline on every fieldset heading — eleven copies of one rule. */
.fgroup h2,.fgroup h3{font-family:var(--display);font-size:var(--t-xl);text-transform:uppercase;margin:0 0 var(--s-4);letter-spacing:var(--tr-tight);line-height:var(--lh-display)}
.frow{display:grid;grid-template-columns:1fr 1fr;gap:var(--s-4)}
.field{display:flex;flex-direction:column;gap:var(--s-2);margin-bottom:var(--s-4)}
.field:last-child{margin-bottom:0}
/* Form-label typography. This was a bare `label{}` element rule, which meant
   every <label> that wraps a checkbox or a drop zone (.check-row, .sp-check,
   .fan-ok, .upload-zone, .in-showall, .rx-grid) had to restate font-family,
   weight, tracking and text-transform just to stop its sentence of body copy
   shouting in condensed caps. Scoping it to .field kills all of those unwinds.
   `:where()` contributes no specificity, so the rule still weighs exactly the
   same (0,0,1) as the old `label{}` and every existing override still wins.
   A label outside a .field that genuinely wants this treatment opts in with
   .field-label. */
/* Uppercase, at --tr-label. Wave B sentence-cased these and the owner sent
   them back: a field label on this site is a label, and labels are condensed
   caps. The thing Wave B fixed that stays fixed is the SCOPE — this was a bare
   `label{}` element rule, so every <label> wrapping a checkbox or a drop zone
   had to unwind it by hand; :where(.field) contributes no specificity, so the
   rule still weighs (0,0,1) and a sentence of consent copy inside .check-row
   is still body text rather than forty words of shouting. */
:where(.field) label,.field-label{font-family:var(--cond);font-weight:600;font-size:var(--t-base);letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--text-2)}
/* Not scoped to labels: the required-marker asterisk also appears in the
   consent rows and beside the signature pad, which are not .field labels. */
.req{color:var(--red)}
/* "(optional)", "(bracket weekend page)" — an inline aside inside a <label>
   or an <h3>. Both parents impose their own type (this label rule's condensed
   uppercase tracking; Anton at heading size), so the aside has to restate the
   whole thing or it comes out shouting. sponsors.html was carrying six copies
   of exactly these declarations in inline style attributes; they can come out
   now. --dim is 4.52:1 on --panel, which is what every .fgroup/.adm-details
   surface using this is. */
.dim{
  font-family:var(--body);font-weight:400;font-size:var(--t-sm);
  letter-spacing:var(--tr-0);text-transform:none;color:var(--text-3);
}
/* The control *skin* stays global — every surface on the site (public forms,
   admin filter bars, inline row editors) wants the same near-black field, and
   there is no context that wants the browser default. Full-bleed *width* does
   not: as a global it also stretched every checkbox, radio and inline filter
   select to the container, which is why a dozen rules below and in admin.css
   carried `width:auto` unwinds. Scoped to .field via `:where()` so the rule
   weighs nothing and cannot start winning fights it used to lose. */
/* FILLED fields, not outlined ones. /join alone put ~40 four-sided 1px
   rectangles on one screen, which is most of the "boxed" reading on the page
   a parent actually has to fill in. A field is a well: it sits two tone steps
   below the card it is in (--surface-0 inside a --surface-2 .fgroup), takes
   the same radius as everything else, and keeps its 1px border transparent so
   :focus can still colour it without the box resizing. */
input,select,textarea{
  background:var(--surface-0);border:1px solid transparent;border-radius:var(--r-sm);
  color:var(--text-1);
  font-family:var(--body);font-size:var(--t-md);padding:12px 14px;
  transition:border-color var(--dur-1) var(--ease-out),background-color var(--dur-1) var(--ease-out);
}
@media (hover:hover){input:hover,select:hover,textarea:hover{background:#08080A}}
:where(.field) :where(input:not([type="checkbox"]):not([type="radio"]),select,textarea){width:100%}
/* A select looked EXACTLY like a text box: the rule above set the skin and
   never `appearance`, so /join's eight pickers read as eight typeable fields
   to a parent filling the form, and on desktop they kept the UA arrow, the UA
   rounding and 47px against a sibling input's 45px (W9/mobile F15, desktop
   F9). One chevron, one height, everywhere. `.season-pick select` is the
   filled-red modifier and still wins on specificity. */
select{
  appearance:none;-webkit-appearance:none;
  padding-right:38px;
  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='%23A7ABB3' d='M1.1 1.2l4.9 4.7 4.9-4.7 1.1 1.2-6 5.7-6-5.7z'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 14px center;background-size:11px;
}
select::-ms-expand{display:none}
option{background:var(--surface-2);color:var(--text-1)}
/* The red border alone is a 1px, 3.3:1 indicator — under WCAG 2.4.11 the
   focus indicator needs a 2px perimeter. Keep the border as the "this field
   is live" cue and put the real ring on :focus-visible, the same white ring
   .btn already uses. */
input:focus,select:focus,textarea:focus{border-color:var(--red)}
input:focus:not(:focus-visible),select:focus:not(:focus-visible),textarea:focus:not(:focus-visible){outline:none}
input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--white);outline-offset:2px}
/* The browser's own calendar button is a dark glyph, which is invisible on a
   near-black field — so the pickers looked like they weren't there at all.
   Invert it, give it room, and make it obviously clickable. */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="month"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator{
  filter:invert(1);opacity:.7;cursor:pointer;padding:2px;margin-left:6px;
  width:18px;height:18px;transition:opacity var(--dur-1) var(--ease-out);
}
input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="month"]:hover::-webkit-calendar-picker-indicator,
input[type="time"]:hover::-webkit-calendar-picker-indicator{opacity:1}
input[type="date"],input[type="month"],input[type="time"]{cursor:pointer}
/* Firefox and Safari render their own control; keep the text legible there. */
input[type="date"]::-webkit-datetime-edit{color:var(--white)}
input[type="date"]:invalid::-webkit-datetime-edit{color:var(--dim)}
/* A date/time field's UA-drawn control adds its own vertical rhythm on top of
   the shared input{padding:12px 14px}, so it rendered 2px shorter than a
   plain input or select sitting beside it in the same row. Height only —
   width is unaffected and this holds everywhere the two field types pair up. */
input[type="date"],input[type="time"]{block-size:46px}
textarea{resize:vertical;min-height:88px}
.form-note{color:var(--text-3);font-size:var(--t-sm);margin-top:var(--s-2)}
/* Status, so it reads like the .notice banner: a tinted ground and a coloured
   left rule, not another outlined rectangle. */
.form-msg{padding:var(--s-4) var(--s-5);margin-top:var(--s-4);font-weight:600;display:none;border-radius:var(--r-sm);border:0}
.form-msg.ok{display:block;background:rgb(var(--ok-rgb) / .1);color:var(--ok);border-left:3px solid var(--ok)}
.form-msg.err{display:block;background:rgb(var(--red-rgb) / .12);color:var(--err);border-left:3px solid var(--err)}
/* One per product card, written into by order.js and cleared after 4s. The
   full .form-msg box (16px/20px padding, 18px top margin) is a page-level
   banner and far too heavy for a slot inside a card — and .form-msg.err's
   display:block would paint an empty red bar on every card at rest. */
.ord-err:empty{display:none}
.form-msg.ord-err{margin-top:8px;padding:8px 10px;font-size:var(--t-sm);line-height:1.35}

/* ---------- tables (admin) ---------- */
/* Tabular data is the one place the audit keeps a visible container, because a
   table needs an edge to scroll against — but it is a rounded surface, not an
   outline: the hairlines inside it do the real work as row dividers. */
.table-scroll{
  overflow-x:auto;-webkit-overflow-scrolling:touch;border:0;
  border-radius:var(--r-md);box-shadow:var(--elev-1);
  /* Hint that more columns sit off-screen on narrow viewports */
  background:
    linear-gradient(90deg,var(--surface-2) 30%,transparent) left / 28px 100% local,
    linear-gradient(270deg,var(--surface-2) 30%,transparent) right / 28px 100% local,
    var(--surface-2);
}
/* --cell-bg is the colour a cell paints when it has to be opaque. Rows that
   carry a state (tr.is-new) override it, and the pinned-column rules read it
   so a highlight runs the full width of the row instead of stopping at a
   pinned column. See the pinned-column block further down. */
/* No min-width here. `table{min-width:900px}` is an ADMIN sheet rule written
   as a bare element selector: at (0,1,1) it beat .ps-table{min-width:0} at
   (0,1,0) and forced /player's stat table to 900px inside a 690px scroller on
   every tablet. It lives in admin-desktop.css now. */
.table-scroll > table,
table{border-collapse:collapse;width:100%;background:var(--surface-2);font-size:var(--t-base);--cell-bg:var(--surface-2)}
/* A column header stays uppercase: it is a data label, it is one line, and the
   table is where condensed caps genuinely help scanning. It uses the single
   label tracking rather than a fifth value of its own. */
th{font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--text-3);text-align:left;padding:10px 14px;border-bottom:1px solid var(--hairline);background:var(--surface-1);white-space:nowrap}
td{padding:11px 14px;border-bottom:1px solid var(--hairline);color:var(--text-2);vertical-align:top}
td:first-child{color:var(--text-1);font-weight:600;white-space:nowrap}
/* The last row's divider is the container's bottom edge drawn twice. */
tbody tr:last-child td{border-bottom:0}


/* ---------- roster page ---------- */
.ro-page{overflow-x:clip}
.gc-bar,.ro-gc-bar{
  display:flex;align-items:center;gap:14px 18px;flex-wrap:wrap;
  margin:0 0 22px;padding:14px 18px;
  border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);background:linear-gradient(90deg,#16161b 0%,var(--panel) 100%);
}
.gc-bar img,.ro-gc-bar img{height:18px;width:auto;opacity:.92}
.gc-bar>p,.ro-gc-bar>p{margin:0;flex:1;min-width:180px;font-size:var(--t-sm);line-height:1.4;color:var(--silver)}
.ro-gc-actions{margin:0}
.ro-toolbar{margin:0 0 var(--space-4)}
.ro-board{display:flex;flex-direction:column;gap:22px}
.ro-squad{
  border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);background:var(--surface-2);overflow:hidden;
  animation:ro-in var(--dur-3) var(--ease-out) both;
}
.ro-squad:nth-child(2){animation-delay:.06s}
.ro-squad:nth-child(3){animation-delay:.12s}
@keyframes ro-in{
  from{opacity:0;transform:translateY(10px)}
  to{opacity:1;transform:none}
}
@media (prefers-reduced-motion:reduce){
  .ro-squad{animation:none}
  .player-card{transition:none}
  .player-card:hover{transform:none}
}
.ro-squad-head{
  padding:22px 24px 18px;border-bottom:1px solid var(--hairline);
  background:linear-gradient(180deg,var(--panel-2) 0%,var(--panel) 100%);
}
.ro-squad-head .eyebrow{margin-bottom:8px}
.ro-squad-title{
  font-family:var(--display);font-size:var(--d-sect);
  text-transform:uppercase;letter-spacing:var(--tr-tight);margin:0 0 6px;line-height:1;
}
.ro-squad-count{
  margin:0;font-family:var(--cond);font-weight:600;font-size:var(--t-xs);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--dim);
}
.ro-squad-body{padding:22px 24px 24px}
.ro-inactive{
  margin-top:4px;border:0;border-radius:var(--r-sm);background:var(--surface-3);padding:14px 18px 18px;
}
/* 308 x 20px was one of the smallest targets measured on the site — a
   disclosure a parent has to hit to see last season's players (W9/mobile F22,
   WCAG 2.5.8). The row is 44px tall now; the ink is unchanged. */
.ro-inactive>summary{
  cursor:pointer;list-style:none;
  display:flex;align-items:center;min-height:44px;
  font-family:var(--cond);font-weight:600;font-size:var(--t-sm);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--silver);
}
.ro-inactive>summary::before{content:"\25B8  ";color:var(--red)}
.ro-inactive[open]>summary::before{content:"\25BE  "}
.ro-inactive>summary span{color:var(--dim);letter-spacing:var(--tr-label)}
.ro-inactive-note{margin:10px 0 14px;font-size:var(--t-sm);color:var(--dim)}
.ro-page .empty{margin-top:0}
.ro-page .empty a{color:var(--white)}

/* ---------- photos page (gallery + highlights) ---------- */
.ph-page{overflow-x:clip}
.ph-toolbar{margin:0 0 var(--space-4)}
.ph-panel-lead{
  margin:0 0 22px;color:var(--silver);font-size:var(--t-base);line-height:1.45;max-width:52ch;
}
.ph-page .carousel{margin-top:0}
.ph-page .thumb-grid{margin-top:18px}
.ph-albums .thumb-grid{margin-top:14px}
.fan-veil{
  position:fixed;inset:0;z-index:var(--z-modal);background:rgba(10,12,16,.72);
  display:flex;align-items:flex-start;justify-content:center;padding:20px;
  overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
}
.fan-card{
  position:relative;width:min(480px,100%);background:var(--panel);
  border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);padding:28px 24px 22px;color:var(--white);
  margin:auto;max-height:calc(100svh - 40px);overflow-y:auto;
}
.fan-card h3{margin:0 0 8px;font-family:var(--display);font-size:var(--d-sect);letter-spacing:var(--tr-tight)}
.fan-card p{margin:0 0 12px;color:var(--silver)}
.fan-x{
  position:absolute;top:6px;right:8px;border:0;background:transparent;
  display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;padding:0;
  font-size:var(--t-2xl);line-height:1;cursor:pointer;color:var(--silver);
}
.fan-x:focus-visible{outline:2px solid var(--white);outline-offset:-2px}
.ph-page .hl-grid{margin-top:0}
.ph-page .empty{margin-top:0}

/* ---------- roster cards ---------- */
.roster-h{
  font-family:var(--display);font-size:var(--t-2xl);text-transform:uppercase;letter-spacing:var(--tr-tight);
  margin:34px 0 16px;padding-left:14px;border-left:2px solid var(--red);
}
/* 34px reads as "heading over its own grid". Two of these in a row, or the
   second album on /photos, are separate sections and need the bigger gap. */
.roster-h + .roster-h,
.ph-albums .roster-h:not(:first-of-type){margin-top:52px}
.roster-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));gap:14px}
.player-card{
  --i:0;
  position:relative;isolation:isolate;overflow:hidden;
  background:var(--ink);border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);padding:0 0 14px;
  display:flex;flex-direction:column;gap:0;
  animation:ro-card-in .5s cubic-bezier(.22,1,.36,1) both;
  animation-delay:calc(var(--i) * 45ms);
  transition:border-color var(--dur-3) var(--ease-out), transform var(--dur-3) cubic-bezier(.22,1,.36,1), box-shadow var(--dur-3) var(--ease-out);
}
@keyframes ro-card-in{
  from{opacity:0;transform:translateY(14px) scale(.98)}
  to{opacity:1;transform:none}
}
.player-card::before{
  content:"";position:absolute;left:0;right:0;bottom:0;height:3px;z-index:3;
  background:linear-gradient(90deg,var(--red),#fff);
  transform:scaleX(0);transform-origin:left center;
  transition:transform var(--dur-3) cubic-bezier(.22,1,.36,1);
}
.pc-photo{
  position:relative;margin:0;overflow:hidden;
  aspect-ratio:1;border-bottom:1px solid var(--hairline);
  background:var(--ink);
}
.pc-photo > img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center center;border:0;display:block;
  transform-origin:center center;
  transition:transform .55s cubic-bezier(.22,1,.36,1), filter var(--dur-3) var(--ease-out);
}
.pc-photo .pc-ph{
  position:absolute;inset:0;width:100%;height:100%;display:block;
  transform-origin:center center;
  transition:transform .55s cubic-bezier(.22,1,.36,1), filter var(--dur-3) var(--ease-out);
}
.pc-photo::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    linear-gradient(180deg,transparent 42%,rgba(0,0,0,.55) 100%),
    linear-gradient(135deg,rgb(var(--red-rgb) / .0),transparent 55%);
  opacity:.7;transition:opacity var(--dur-3) var(--ease-out), background var(--dur-3) var(--ease-out);
}
.pc-ph{
  border:0;
  background:radial-gradient(360px 200px at 50% -20%, rgb(var(--red-rgb) / .28), transparent 60%), #0e0e12;
  display:flex;align-items:center;justify-content:center;
}
.pc-ph img{
  width:52%;aspect-ratio:1;opacity:.5;border:none;border-radius:50%;
  object-fit:contain;object-position:center;
  transition:opacity var(--dur-3) var(--ease-out), transform .55s var(--ease-out);
}
.pc-num{
  position:absolute;top:10px;left:10px;z-index:2;background:var(--hl,var(--red));color:var(--on-hl,var(--on-red));
  font-family:var(--display);font-size:var(--t-md);padding:3px 10px;transform:skewX(var(--skew));
  box-shadow:0 6px 16px rgba(0,0,0,.35);
  transition:transform var(--dur-3) cubic-bezier(.22,1,.36,1), box-shadow var(--dur-3) var(--ease-out), background var(--dur-3) var(--ease-out);
}
.pc-meta{
  position:relative;z-index:2;padding:12px 12px 0;
  display:flex;flex-direction:column;gap:4px;
  transition:transform var(--dur-3) cubic-bezier(.22,1,.36,1);
}
.player-card b{
  font-family:var(--display);font-size:var(--t-md);letter-spacing:var(--tr-tight);text-transform:uppercase;
  line-height:1.15;color:var(--white);
  transition:letter-spacing var(--dur-3) var(--ease-out), color var(--dur-3) var(--ease-out);
}
.pc-squad{
  font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-label);
  text-transform:uppercase;color:var(--dim);
  transition:color var(--dur-3) var(--ease-out);
}
/* A child on more than one team: under the squad being read, the others. Quieter
   than the chip, never a second chip, so one child does not read as two. */
.pc-also{
  font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-label);
  text-transform:uppercase;color:var(--dim);
}
.pc-pos{font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--silver)}

.roster-inactive .player-card{opacity:.65;animation:none}
.roster-inactive .pc-photo img{filter:grayscale(1)}
.roster-inactive .pc-num{background:#3a3a40;color:#fff}
@media (prefers-reduced-motion:reduce){
  .player-card{animation:none;transition:none}
  .pc-photo > img,.pc-photo .pc-ph,.pc-photo::after,.pc-num,.pc-meta,.player-card b,.player-card::before{transition:none}
}

/* ---------- highlight posters ---------- */
.hl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:18px}
.hl-poster{
  width:100%;aspect-ratio:2/3;object-fit:cover;display:block;cursor:zoom-in;
  border:0;border-radius:var(--r-sm);transition:transform var(--dur-1),border-color var(--dur-1);
}
@media (hover:hover){.hl-poster:hover{transform:translateY(-3px);border-color:var(--red)}}

/* ---------- gear grid ---------- */
.gear-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px}
.gear-img{width:100%;aspect-ratio:1;object-fit:contain;background:var(--surface-light);border:0;border-radius:var(--r-sm);margin-bottom:4px}
.checklist{list-style:none;margin:0 0 12px;padding:0;counter-reset:ck}
.checklist li{
  counter-increment:ck;display:flex;flex-direction:column;gap:2px;
  padding:12px 0 12px 44px;position:relative;border-bottom:1px solid var(--hairline);
}
.checklist li:last-child{border-bottom:none}
.checklist li::before{
  content:counter(ck);position:absolute;left:0;top:12px;
  width:30px;height:30px;display:flex;align-items:center;justify-content:center;
  background:var(--hl,var(--red));color:var(--on-hl,var(--on-red));font-family:var(--display);font-size:var(--t-md);
  transform:none;
}
.checklist b{font-family:var(--cond);font-weight:600;font-size:var(--t-md);letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--white)}
.checklist span{color:var(--silver);font-size:var(--t-base)}
.checklist i{color:var(--red);font-style:normal;font-family:var(--cond);font-weight:600;letter-spacing:var(--tr-0)}
.gear-card{
  display:flex;flex-direction:column;gap:8px;align-items:flex-start;
  background:var(--surface-2);border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);
  padding:20px 22px;text-decoration:none;transition:border-color var(--dur-1),transform var(--dur-1);
}
@media (hover:hover){.gear-card:hover{border-color:var(--red);transform:translateY(-2px)}}
.gear-card .org-tag{margin-left:0}
.gear-card b{font-family:var(--display);font-size:var(--t-lg);letter-spacing:var(--tr-tight);text-transform:uppercase;color:var(--white);line-height:1.1}
.gear-note{color:var(--silver);font-size:var(--t-sm)}
.gear-cta{
  margin-top:auto;padding-top:8px;
  font-family:var(--cond);font-weight:600;font-size:var(--t-sm);letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--link,var(--red-text));
}

/* ---------- sponsors ---------- */
.sponsor-strip-label{
  margin:0 0 10px;font-family:var(--cond);font-weight:600;font-size:var(--t-xs);
  letter-spacing:var(--tr-eyebrow);text-transform:uppercase;color:var(--dim);
}
.sponsor-premier-wrap{margin:0 0 22px}
.sponsor-premier{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;
}
.sponsor-card-premier{
  min-height:160px;border-color:rgb(var(--red-rgb) / .55);
  background:linear-gradient(180deg,rgb(var(--red-rgb) / .12),var(--panel));
}
#sponsors.section{padding-bottom:70px}
.sponsor-scroll-wrap{margin:0 0 8px}
/* Home marquee: no panel/box — logos float on the page ink. */
.sponsor-scroll{
  overflow:hidden;border:0;background:transparent;
  -webkit-mask-image:linear-gradient(90deg,transparent 0%,#000 5%,#000 95%,transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0%,#000 5%,#000 95%,transparent 100%);
}
.sponsor-scroll-track{
  display:flex;align-items:center;gap:28px;width:max-content;padding:10px 0;
  /* The band used to be pinned at 128px so it could not grow as lazy logos
     arrived; the logos have a fixed box now (see .sponsor-card img), so the
     cards set the height and a two-line sponsor name is no longer clipped. */
}
.sponsor-scroll-track.is-animated{animation:sp-marquee 42s linear infinite}
.sponsor-scroll:focus-within .sponsor-scroll-track{animation-play-state:paused}
@media (hover:hover){.sponsor-scroll-track:hover{animation-play-state:paused}}
/* Same touch-pause treatment as the score ticker: the marquee runs on
   phones and a finger on it holds it still. */
.sponsor-scroll-track.is-paused{animation-play-state:paused}
@keyframes sp-marquee{
  from{transform:translateX(0)}
  to{transform:translateX(-50%)}
}
.sponsor-scroll .sponsor-card{
  flex:0 0 auto;min-height:0;width:auto;max-width:220px;
  padding:8px 10px;gap:8px;
  background:transparent;border:0;box-shadow:none;
}
@media (hover:hover){
  .sponsor-scroll .sponsor-card:hover{
    border-color:transparent;background:transparent;transform:translateY(-2px);
  }
}
.sponsor-scroll .sponsor-card img{
  max-height:88px;max-width:180px;
  filter:drop-shadow(0 4px 14px rgba(0,0,0,.55));
}
.sponsor-scroll .sponsor-card-premium,.sponsor-scroll .sp-home-premium{
  flex-basis:auto;min-height:0;
  border:0;background:transparent;
}
.sponsor-scroll .sponsor-card-premium img,.sponsor-scroll .sp-home-premium img{max-height:110px;max-width:220px}
.sponsor-scroll .sponsor-cta{
  min-width:160px;padding:14px 18px;
  border:0;border-radius:var(--r-md);background:var(--surface-2);box-shadow:var(--elev-1);
}
.sponsor-scroll .sp-card-chip{background:rgb(var(--red-rgb) / .9)}
.sponsor-card-premium,.sp-home-premium{
  flex-basis:320px;min-height:150px;transform:none;
  border-color:color-mix(in srgb,var(--gold) 55%,transparent);
}
.sponsor-card-premium img,.sp-home-premium img{height:140px}
.sp-card-chip{
  display:inline-block;margin-bottom:4px;padding:3px 8px;
  font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--on-red);background:rgb(var(--red-rgb) / .85);
}
/* `.sponsor-card span` (below) is more specific than the rule above and used to
   win, which is why the chip read silver on its fill whatever the fill was. */
.sponsor-card .sp-card-chip{color:var(--on-red)}
.sponsor-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:var(--s-4)}
.sponsor-card{
  position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;
  min-height:130px;padding:22px 18px;text-align:center;text-decoration:none;
  background:var(--surface-2);border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);transition:border-color var(--dur-1),transform var(--dur-1);
}
@media (hover:hover){.sponsor-card:hover{border-color:var(--red);transform:translateY(-2px)}}
/* A fixed box, not a cap: with only max-height these lazy logos landed at
   their natural height and grew the card (and the page) under the reader —
   0.135 CLS on the /sponsors roll, +164px of page (sweep A#7). object-fit
   keeps the logo's own proportions inside the reserved 120px. */
.sponsor-card img{height:120px;width:auto;max-width:85%;object-fit:contain}
.sponsor-card b{font-family:var(--display);font-size:var(--t-lg);letter-spacing:var(--tr-tight);text-transform:uppercase;color:var(--white);line-height:1.15}
.sponsor-card span{font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--silver)}
.sponsor-cta{border-style:dashed}
.sponsor-cta span{color:var(--link,var(--red-text))}
.sp-perms{display:flex;flex-direction:column;gap:var(--s-3);margin-top:var(--s-4)}
/* Consent checkboxes in the fan-photo modal — same shape as .sp-check and
   .check-row. These used to restate the body face to unwind the global
   `label{}` rule; that rule is scoped to .field now, so only the layout and
   the reading size are left. */
.fan-ok{
  display:flex;gap:11px;align-items:flex-start;margin:10px 0;
  font-size:var(--t-base);color:var(--silver);cursor:pointer;line-height:1.5;
}
.fan-ok input{width:18px;height:18px;accent-color:var(--red);flex:none;margin-top:2px}
.fan-ok span{flex:1;min-width:0}
.fan-drop{margin-top:12px}
.fan-actions{margin-top:8px}
.sp-check{
  display:flex;gap:12px;align-items:flex-start;cursor:pointer;
  font-size:var(--t-base);color:var(--silver);line-height:1.45;
}
.sp-check input[type="checkbox"]{
  width:16px;height:16px;margin:3px 0 0;padding:0;flex:none;
  accent-color:var(--red);cursor:pointer;
}
.sp-check span{flex:1;min-width:0}
.sp-badge-dl .btn-row{flex-wrap:wrap}
/* Sits between the badge preview and its download row, so it needs more room
   above than .form-note's 10px and less below. */
.sp-badge-lede{margin:16px 0 8px}
@media (prefers-reduced-motion:reduce){
  .sponsor-scroll-track.is-animated{animation:none}
}
/* ---------- /sponsors and /sponsor ---------- */
/* One page on the site's own system. The header is the .page-hero every tab
   wears, every band is a .section, every box is a .card, the directory is the
   home page's own sponsor strip (.sponsor-scroll, drawn by the same builder)
   and the one primary button is .btn.primary. What is written below is only
   what no other page has: the level ladder, the comparison table and the
   sign-up form's own pieces. The page used to run a second design system (its own bands, cards,
   tier colours, a closing red band, an entrance animation) and read as another
   site; nothing here restates a section, a heading, a card or a button. */

/* Held until sponsor.js has painted the levels and the roll, then shown in one
   frame (see revealBody there). visibility, not display: the sections keep
   their height, and a hidden element is never counted as having moved. */
[data-sp-wait]>section:not(.page-hero){visibility:hidden}
/* Anchor alias for #sign-up. The offset must clear the REAL sticky bar —
   a flat 70px put the target under the ~104-149px phone header stack. */
.sp-hash-alias{display:block;position:relative;top:calc(-1 * (var(--chrome-h,var(--header-h)) + 12px));height:0;width:0;overflow:hidden;visibility:hidden}
/* /sponsor's eyebrow is the way back to the list; it should read as the
   eyebrow, not as a link. */
.sp-hero .eyebrow a{color:inherit;text-decoration:none}
@media (hover:hover){.sp-hero .eyebrow a:hover{text-decoration:underline;text-underline-offset:3px}}

/* The small label over a group of cards ("What your support builds"). The
   kicker idiom, one step up in size. */
.sp-group{
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-eyebrow);
  text-transform:uppercase;color:var(--text-3);margin:var(--s-6) 0 var(--s-3);
}
.sp-group:first-child{margin-top:0}

/* ---- the level ladder ---- */
.sp-tiers{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(212px,1fr));gap:var(--s-4);
  margin:0;align-items:stretch;
}
/* A level is a card you can press, so it is a .card in every respect and takes
   --elev-2 on hover like any other card that is a link. Gold marks the featured
   level and nothing else; the rest are neutral. */
.sp-tier-card{
  position:relative;display:flex;flex-direction:column;align-items:flex-start;text-align:left;
  padding:var(--card-pad);background:var(--surface-2);border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);
  color:var(--text-1);cursor:pointer;font:inherit;min-height:100%;
  transition:box-shadow var(--dur-2) var(--ease-out);
}
@media (hover:hover){.sp-tier-card:hover{box-shadow:var(--elev-2)}}
.sp-tier-card:focus-visible{outline:2px solid var(--text-1);outline-offset:2px}
.sp-tier-card.is-selected{outline:2px solid var(--red);outline-offset:2px}
/* "Best value" is said with one tone step and one elevation step, both from
   the ladder, plus the gold badge and price below. */
.sp-tier-card.is-featured{background:var(--surface-3);box-shadow:var(--elev-2)}
.sp-tier-badge{
  display:inline-block;align-self:flex-start;min-height:20px;margin:0 0 var(--s-4);
  font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--text-2);border-radius:var(--r-xs);background:var(--surface-hover);padding:3px 10px;
}
/* The spacer that keeps the cards' headings on one line when only some levels
   carry a badge. It has a fill, so it has to clear that too. */
.sp-tier-badge-ghost{color:transparent;background:transparent;padding:3px 0}
.sp-tier-card.is-featured .sp-tier-badge{color:var(--surface-0);background:var(--gold)}
.sp-tier-name{
  font-family:var(--display);font-size:var(--d-sect);letter-spacing:var(--tr-tight);
  text-transform:uppercase;line-height:var(--lh-display);margin:0 0 var(--s-2);color:var(--text-1);
}
.sp-tier-amt{font-family:var(--display);font-size:var(--t-2xl);line-height:1;color:var(--text-1)}
.sp-tier-card.is-featured .sp-tier-amt{color:var(--gold)}
.sp-tier-season{
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--text-3);margin:var(--s-2) 0 0;
}
.sp-tier-tagline{margin:var(--s-3) 0 0;color:var(--text-2);font-size:var(--t-base);line-height:1.4}
.sp-tier-perks{
  list-style:none;margin:var(--s-4) 0 var(--s-5);padding:var(--s-4) 0 0;border-top:1px solid var(--hairline);
  display:flex;flex-direction:column;gap:var(--s-3);width:100%;flex:1;font-size:var(--t-base);color:var(--text-2);
}
.sp-tier-perks li{display:flex;gap:var(--s-2);align-items:flex-start;padding:0;line-height:1.35}
.sp-tier-perks li::before{content:none}
.sp-perk-check{color:var(--text-3);flex:0 0 auto;line-height:1.2}
.sp-tier-card.is-featured .sp-perk-check{color:var(--gold)}
/* The card's own button: a plain .btn (the featured level's is .btn.primary).
   It is a <span> inside the card's <button>, so the card carries the hover. */
.sp-tier-cta{margin-top:auto;width:100%;padding-inline:var(--s-3);text-align:center}
@media (hover:hover){
  .sp-tier-card:hover .sp-tier-cta{background:var(--surface-3)}
  .sp-tier-card:hover .sp-tier-cta.primary{background:var(--btn-bg-hover,var(--red-hover))}
}

/* ---- the comparison table ---- */
/* Behind a disclosure (.fold, the schedule's own), so the page reads as a
   pitch and the table is there for whoever wants every row.
   The scroller is `position:relative` on purpose. The "Not included" cells
   carry a .vh — an ABSOLUTELY positioned span — and an absolute box is clipped
   only by a scroller that is its containing block. Without this the span
   escaped the scroller, sat 300px to the right of a phone's edge, and made the
   whole page scroll sideways (scrollWidth 431 at 390) whenever nothing else
   happened to be the containing block: an entrance transform used to be, until
   it finished. It is the reason /sponsors was the one page that scrolled
   sideways on a phone. */
.sp-compare{margin-top:var(--s-6)}
.sp-compare-wrap{position:relative;overflow-x:auto;-webkit-overflow-scrolling:touch}
.sp-compare-wrap:focus-visible{outline:2px solid var(--text-1);outline-offset:2px}
/* sponsor.js builds this from settings.sponsorTiers, so the column count is
   whatever a coach has configured. The rowheader gets the one explicit column;
   every tier cell after it lands in an implicit column of the same width, so
   the template follows the row's own children. --sp-matrix-cols (tiers + 1,
   published by sponsor.js) drives the scroller's floor on a wide screen; on a
   phone the table stops being a table (mobile.css) and there is nothing to
   scroll. */
.sp-matrix{min-width:calc(var(--sp-matrix-cols,5) * 144px)}
.sp-matrix-row{
  display:grid;grid-auto-flow:column;
  grid-template-columns:1.7fr;grid-auto-columns:minmax(0,1fr);
  align-items:center;
  border-top:1px solid var(--hairline);
}
/* Rows sit inside role="rowgroup" wrappers, so the body's first row is child 1
   of its group — odd here stripes the first, third, fifth body row. */
.sp-matrix-row:nth-child(odd){background:var(--surface-3)}
.sp-matrix-head:nth-child(odd){background:transparent}
.sp-matrix-head{border-top:0;background:transparent}
.sp-matrix-head > div{
  display:flex;flex-direction:column;gap:3px;align-items:center;text-align:center;
  padding:var(--s-4) var(--s-3);border-left:1px solid var(--hairline);
  font-family:var(--cond);font-weight:600;font-size:var(--t-sm);letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--text-3);
}
.sp-matrix-head > div:first-child{align-items:flex-start;text-align:left;border-left:0;align-self:end}
.sp-matrix-head b{
  font-family:var(--display);font-size:var(--t-lg);text-transform:uppercase;letter-spacing:var(--tr-tight);color:var(--text-1);font-weight:400;
}
.sp-matrix-head span{font-family:var(--cond);font-weight:600;font-size:var(--t-sm);letter-spacing:var(--tr-label);color:var(--text-2);text-transform:none}
.sp-matrix-row > div{
  text-align:center;padding:var(--s-3);border-left:1px solid var(--hairline);
  font-family:var(--cond);font-weight:600;font-size:var(--t-sm);letter-spacing:var(--tr-tight);line-height:1.3;color:var(--text-1);
}
.sp-matrix-row > div:first-child{
  text-align:left;border-left:0;font-family:var(--body);font-weight:400;font-size:var(--t-base);
  letter-spacing:var(--tr-0);color:var(--text-2);padding:var(--s-3) var(--s-4);
}
.sp-matrix-row > .is-accent{color:var(--gold)}
.sp-matrix-row > .is-muted{color:var(--text-3)}

/* ---- why it matters ---- */
.sp-story-grid{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);gap:var(--s-5) var(--s-7);align-items:start;
}
.sp-story-headline{
  font-family:var(--display);font-size:var(--t-2xl);text-transform:uppercase;
  letter-spacing:var(--tr-tight);line-height:var(--lh-display);margin:0;
}
.sp-story-copy{font-size:var(--t-md);color:var(--text-2);max-width:var(--measure-body)}
.sp-story-copy p{margin:0 0 var(--s-4);line-height:1.6}
/* Scoped: this is a <p> inside .sp-story-copy, so it only needed to outrank
   .sp-story-copy p — a compound selector does that without !important. */
.sp-story-copy .sp-story-pull{
  margin:var(--s-5) 0 0;padding-top:var(--s-4);border-top:1px solid var(--hairline);
  font-family:var(--cond);font-weight:600;font-size:var(--t-md);letter-spacing:var(--tr-label);
  text-transform:uppercase;color:var(--text-1);line-height:1.3;
}
/* "What your support builds" and the terms below are both ordinary .card grids;
   a card title here is a label, not the display face .card h3 gives it. */
.sp-goes,.sp-terms-grid{display:grid;gap:var(--s-4)}
.sp-goes{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
.sp-terms-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
.card .sp-card-title{
  font-family:var(--cond);font-weight:600;font-size:var(--t-md);letter-spacing:var(--tr-label);
  text-transform:uppercase;line-height:var(--lh-tight);margin:0 0 var(--s-2);color:var(--text-1);
}
.sp-goes .card p,.sp-terms-grid .card p{margin:0;font-size:var(--t-base);line-height:1.5}
.sp-contact{margin:var(--s-5) 0 0;color:var(--text-2)}
.sp-contact a{color:var(--text-1)}

/* ---- sign-up ---- */
/* Everything below sits inside a .card, whose `.card p` (text-2, 12px under)
   outranks a bare class — so each of these names its container. */
.sp-signup-panel.card{padding:var(--card-pad-lg)}
/* One row of fields is one grid row on a wide screen. On a phone .frow is one
   column, and its row gap stacked on each field's own bottom margin spaced the
   two fields of a row twice as far apart as two fields from different rows. */
.sp-form .frow{row-gap:0}
.sp-signup-panel .sp-form-selected{
  font-family:var(--cond);font-weight:600;font-size:var(--t-base);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--text-2);margin:0 0 var(--s-4);
}
.sp-signup-panel .sp-form-selected b{color:var(--text-1)}
.sp-signup-panel .form-note{color:var(--text-3);margin:var(--s-2) 0 0}
.sp-thanks{
  margin-top:var(--s-2);background:rgb(var(--ok-rgb) / .1);border:0;border-left:3px solid var(--ok);
  border-radius:var(--r-sm);padding:var(--s-5);
}
.sp-thanks p{color:var(--text-2);margin:0}
.sp-thanks .eyebrow{color:var(--link,var(--red-text));margin:0 0 var(--s-3)}
.sp-thanks .form-note{margin-top:var(--s-3)}
.sp-logo-upload{display:flex;align-items:flex-start;gap:var(--s-3)}
.sp-logo-preview{
  width:84px;height:84px;flex:0 0 84px;background:var(--surface-light);border:0;border-radius:var(--r-sm);
  display:flex;align-items:center;justify-content:center;overflow:hidden;
}
.sp-logo-preview img{max-width:90%;max-height:90%;object-fit:contain}
.sp-logo-upload-main{flex:1;min-width:0}
/* The file input sits outside a .field, so it is not caught by the scoped
   full-width rule up in the forms section — say so here rather than widening
   that rule back out to every control on the page. */
.sp-logo-upload-main input[type="file"]{width:100%}
.sp-note{margin:var(--s-2) 0 0;color:var(--text-2);font-size:var(--t-base);font-style:italic}

/* ---- /sponsor: one sponsor's page ---- */
.sp-profile{
  display:grid;grid-template-columns:minmax(200px,320px) 1fr;gap:var(--s-6);align-items:center;
}
/* A logo sits on a light plate (a sponsor's mark is drawn for white), inside
   the card: the tone step is the container, so no shadow of its own. */
.sp-profile-logo{
  aspect-ratio:1;background:var(--surface-light);border-radius:var(--r-sm);
  display:flex;align-items:center;justify-content:center;padding:var(--s-6);
}
.sp-profile-logo img{max-width:100%;max-height:100%;object-fit:contain}
.sp-profile-nameplate{font-family:var(--display);font-size:var(--t-2xl);letter-spacing:var(--tr-tight);text-transform:uppercase;color:var(--ink-on-light);text-align:center}
.sp-profile .sp-profile-tier{
  font-family:var(--display);font-size:var(--t-2xl);line-height:1.1;text-transform:uppercase;
  letter-spacing:var(--tr-tight);color:var(--text-1);margin:0 0 var(--s-3);
}
.sp-profile .btn-row{margin-top:var(--s-4)}
.sp-profile .sp-badge-dl{margin-top:var(--s-4)}

/* ---------- callout / footer ---------- */
.callout{border:0;border-left:3px solid var(--red);border-radius:var(--r-sm);background:rgb(var(--red-rgb) / .08);padding:18px 22px;color:var(--silver);font-size:var(--t-base)}
.callout b{color:var(--white)}
.site-footer{margin-top:var(--sec-lg);border-top:1px solid var(--hairline);padding:var(--s-8) 0 var(--s-6);color:var(--text-3);font-size:var(--t-sm)}
/* Row gap USED to be 26px against a 29px link pitch, so the eye grouped the
   footer horizontally — rows running across all four columns — instead of
   vertically as four nav blocks. That is what "double spaced" looks like on a
   desktop footer. The column gap is now more than twice the row pitch, which
   is what makes a column read as a column. */
.foot-grid{display:grid;gap:var(--s-5) var(--s-7);align-items:start}
.f-mark{height:24px;width:auto;align-self:flex-start;margin-bottom:8px}
.foot-brand span:not(.f-brand){font-size:var(--t-sm)}
/* The pitch comes from the rows, not from a gap stacked on top of them: 7px of
   gap under a 21.7px line box that already inherited body's line-height:1.55
   made a 29px pitch = 2.07x the font size for a 14px link. 13.5px x 1.35 =
   18.2px of line + 8px of padding = a 26px pitch and a 26px target, which
   clears WCAG 2.5.8 AA (24px) with the row itself. */
.foot-col{display:flex;flex-direction:column;gap:0}
.foot-col a,.foot-col .link-like,.foot-col span{line-height:1.35;padding:4px 0}
/* The one restored rule that takes --tr-eyebrow rather than --tr-label: a
   footer column heading is the eyebrow idiom (four words, wide, above a list),
   and it carried --track-xwide before Wave B, which is this token. */
.foot-col b{
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-eyebrow);
  text-transform:uppercase;color:var(--text-2);margin-bottom:var(--s-3);
}
.site-footer .f-brand{font-family:var(--display);text-transform:uppercase;color:var(--white);font-size:var(--t-lg);letter-spacing:var(--tr-tight)}
.site-footer a{color:var(--dim);text-decoration:none}
.site-footer a:hover{color:var(--white)}
/* The footer's TeamReach control opens a dialog, so it is a <button>, not an
   <a> — but it sits in a list of links and has to read as one. Strip the UA
   button chrome and inherit the colour of whatever context it lands in (the
   footer sets --dim on itself, so it matches its neighbours exactly). */
.link-like{
  appearance:none;background:none;border:0;padding:0;margin:0;
  font:inherit;color:inherit;text-align:left;cursor:pointer;
}
.link-like:hover{color:var(--white)}
.link-like:focus-visible{outline:2px solid var(--white);outline-offset:2px}
.foot-legal{
  display:flex;flex-wrap:wrap;gap:8px 30px;justify-content:space-between;
  margin-top:30px;padding-top:18px;border-top:1px solid var(--hairline);font-size:var(--t-xs);color:var(--dim);
}

/* ---------- countdown: second squad line ---------- */
.cd-also{
  margin-top:18px;padding:12px 16px;border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);border-left:2px solid var(--red);
  background:var(--panel);color:var(--silver);font-family:var(--cond);font-weight:600;
  font-size:var(--t-base);letter-spacing:var(--tr-label);text-transform:uppercase;
}
.cd-also b{color:var(--white)}

/* ---------- squad chip on game rows ---------- */
.g-squad{
  flex:none;background:var(--panel);border:0;border-radius:var(--r-xs);color:var(--silver);
  font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);letter-spacing:var(--tr-0);
  padding:2px 7px;align-self:center;
}
/* The same squad colours the calendar chips carry, from the same --sq/--sq-line
   pair site.js writes onto the element. */
.g-squad-on{background:var(--sq);border-color:var(--sq-line,var(--sq));color:var(--on-sq,#fff)}

/* ---------- fundraiser (/fundraiser) ---------- */
/* The page's own hero photo is a runtime value (the lead fundraiser's photo),
   set as --page-hero-image by assets/fundraiser.js; this is only the built-in
   fallback for a club that hasn't picked one. */
.fr-hero{--hero-img:url("/assets/img/join-hero.webp");--hero-pos:center 42%}
/* The blurb is plain text a coach typed, newlines and all. */
/* Several collections can run at once. They stack in the coaches' own order,
   one column at every width — a fundraiser card carries a progress meter and a
   donor wall, and side by side neither is readable. */
.fr-cards{display:flex;flex-direction:column;gap:26px;max-width:640px}
.fr-card{max-width:640px;margin:0}
.fr-card-photo{
  display:block;width:100%;height:auto;aspect-ratio:16/9;object-fit:cover;
  border:0;border-radius:var(--r-sm);margin-bottom:16px;
}
.fr-card-title{
  margin:0 0 8px;font-family:var(--display);text-transform:uppercase;
  letter-spacing:var(--tr-tight);line-height:1.02;font-size:var(--d-sect);
}
.fr-card-blurb{margin:0 0 18px;color:var(--silver);white-space:pre-line}
.fr-prog{display:flex;flex-direction:column;gap:10px}
/* A bar, not a meter element: <meter>'s own painting can't be restyled
   consistently across browsers, and this one has to read at a glance in
   daylight on a phone. role="progressbar" carries the meaning. */
.fr-bar{
  position:relative;height:16px;border-radius:999px;overflow:hidden;
  background:var(--panel-2);border:1px solid var(--hairline-strong);
}
.fr-bar-fill{
  display:block;height:100%;min-width:3px;border-radius:999px;
  background:linear-gradient(90deg,rgb(var(--hl-rgb,var(--red-rgb)) / .85),var(--hl,var(--red)));
}
/* The unit board (the owner's is 30 ice cubes at $10). auto-fill rather than a
   fixed count: the same thirty cells wrap to whatever the card is wide, so a
   phone gets five rows of six and a desktop gets two of fifteen, with no width
   query in either sheet. */
.fr-cubes{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(26px,1fr));
  gap:6px;margin:0;
  /* Capped so the board keeps its shape: stretched to a 640px card the same
     thirty cells become one long strip nobody can count. */
  max-width:380px;
}
.fr-cube{
  display:block;aspect-ratio:1;border-radius:var(--r-sm);
  background:var(--panel-2);border:1px solid var(--hairline-strong);
}
.fr-cube.is-on{
  background:linear-gradient(160deg,rgb(var(--hl-rgb,var(--red-rgb)) / .9),var(--hl,var(--red)));
  border-color:var(--hl,var(--red));
}
.fr-figs{display:flex;flex-wrap:wrap;align-items:baseline;gap:10px;margin:0}
.fr-figs b{
  font-family:var(--display);font-size:var(--d-sect);line-height:1;
  color:var(--white);letter-spacing:var(--tr-tight);font-variant-numeric:tabular-nums;
}
.fr-figs span{color:var(--silver);font-size:var(--t-base)}
.fr-src{margin:0}
.fr-card-cta{margin-top:18px}

/* ---------- the donor wall ---------- */
.fr-wall{margin-top:22px;border-top:1px solid var(--hairline);padding-top:16px}
.fr-wall-h{
  margin:0 0 12px;font-family:var(--cond);font-weight:600;font-size:var(--t-sm);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--silver);
}
.fr-gifts{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.fr-gift{padding:10px 0;border-bottom:1px solid var(--hairline)}
.fr-gift:last-child{border-bottom:0}
.fr-gift-top{
  display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
  gap:10px;margin:0;
}
.fr-gift-top b{color:var(--white);font-size:var(--t-base)}
.fr-gift-amt{
  font-family:var(--display);font-size:var(--t-lg);color:var(--red);
  font-variant-numeric:tabular-nums;
}
/* The messages people wrote are the reason the wall is worth showing, so they
   read as quoted human sentences rather than as another data column. */
.fr-gift-msg{margin:5px 0 0;color:var(--silver);font-style:italic;overflow-wrap:anywhere}
/* Lowercase on purpose: uppercased, "3d ago" reads as "3D AGO" — a dimension,
   not a date. */
.fr-gift-ago{
  margin:4px 0 0;color:var(--dim);font-family:var(--cond);
  font-size:var(--t-xs);letter-spacing:var(--tr-0);
}
.fr-wall-more{margin-top:14px}

/* ---------- shop page ---------- */
.sh-page{overflow-x:clip}
/* /shop is the one commerce page: a hero, the team store, spirit wear. Both
   sections are the same shape as every other page — a .section heading, one
   line under it, then cards — and each has exactly one primary button.

   The team store is somebody else's website and opens in its own tab; it used
   to be framed in a 1,500px white slab that a phone's browser could refuse.
   What is left is a card with the vendor's mark, small, and the button. */
.sh-page #fan-store{padding-bottom:0}
.sh-store{
  display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;
  gap:var(--s-4) var(--s-6);
}
.sh-store .btn-row{margin:0}
/* A credit line for whose store this is, never a hero: 20px tall. */
.sh-mark{display:block;height:20px;width:auto;max-width:100%}

/* Spirit wear: two product cards side by side, the order card under them.
   Prices are rows, not badges — the numbered badges belong to the checklist
   component and read as steps when reused here. */
.sh-products{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s-4);margin-bottom:var(--s-4)}
.sh-product{display:flex;align-items:center;gap:var(--s-5);padding:var(--s-4)}
/* The product photos are shot on near-white; the plate matches it so the
   picture reads as a swatch of the card, not a slab dropped into it. */
.sh-photo{
  flex:0 0 clamp(104px,26%,150px);align-self:flex-start;
  background:var(--surface-light);border-radius:var(--r-sm);overflow:hidden;
}
.sh-photo img{display:block;width:100%;height:auto}
.sh-product-body{flex:1 1 auto;min-width:0}
.sh-product h3{margin:0 0 var(--s-3);font-size:var(--t-xl);text-wrap:balance}
.sh-prices{list-style:none;margin:0;padding:0}
.sh-prices li{
  display:flex;align-items:baseline;justify-content:space-between;gap:var(--s-4);
  padding:var(--s-2) 0;border-bottom:1px solid var(--hairline);
}
.sh-prices li:last-child{border-bottom:0}
.sh-prices span{color:var(--text-2);font-size:var(--t-base)}
.sh-prices b{font-family:var(--cond);font-weight:600;font-size:var(--t-lg);letter-spacing:var(--tr-label);color:var(--text-1)}
.sh-note{margin:var(--s-3) 0 0;color:var(--text-3);font-size:var(--t-sm)}
.sh-order{display:grid;grid-template-columns:minmax(0,1fr) minmax(220px,280px);gap:var(--s-5) var(--s-7);align-items:start}
.sh-order p{max-width:var(--measure-body);margin:0 0 var(--s-3)}
.sh-order .sh-options{color:var(--text-1);font-size:var(--t-md)}
.sh-order .sh-how{margin-bottom:0}
/* The primary and its two secondaries stack in their own column beside the
   words on a wide screen; on a phone they run full width under them. */
.sh-order .btn-row{flex-direction:column;align-items:stretch;margin:0}
.sh-order .sh-contact{display:flex;flex-wrap:wrap;gap:var(--s-1) var(--s-4);margin:var(--s-4) 0 0;font-size:var(--t-sm);color:var(--text-3)}
.sh-contact a{color:var(--text-2);text-decoration:none;border-bottom:1px solid var(--hairline-strong)}
@media (hover:hover){.sh-contact a:hover{color:var(--text-1);border-bottom-color:var(--red)}}

/* ---------- join page ---------- */
.jn-page{overflow-x:clip}
.jn-form-wrap{max-width:820px}

/* ---------- order gear (/store + /order) ---------- */
.og-page{overflow-x:clip}
.og-form-wrap{max-width:820px}

/* ---------- join: submitted state ---------- */
.submit-done{
  text-align:center;padding:56px 24px 60px;border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);background:var(--surface-2);
}
.submit-done .sd-check{
  width:74px;height:74px;margin:0 auto 18px;border-radius:50%;background:var(--hl,var(--red));
  display:flex;align-items:center;justify-content:center;
  font-size:var(--t-3xl);color:var(--on-hl,var(--on-red));line-height:1;
}
.submit-done h3{font-size:var(--d-sect);text-transform:uppercase;margin:0 0 12px}
.submit-done p{color:var(--silver);max-width:460px;margin:0 auto 24px;line-height:1.6}

/* ---------- join: waiver + signature ---------- */
.waiver-text{
  background:var(--surface-3);border:0;border-radius:var(--r-sm);padding:18px 20px;
  font-size:var(--t-base);color:var(--silver);line-height:1.65;margin-bottom:16px;
}
.waiver-text b{
  display:block;color:var(--white);font-family:var(--cond);font-weight:600;
  letter-spacing:var(--tr-label);text-transform:uppercase;margin-bottom:10px;
}
.waiver-text p{margin:0 0 10px}
.check-row{
  display:flex;gap:11px;align-items:flex-start;margin:10px 0;
  font-size:var(--t-base);color:var(--silver);cursor:pointer;line-height:1.5;
}
.check-row input{width:18px;height:18px;accent-color:var(--red);flex:none;margin-top:2px}
.sig-wrap{position:relative}
/* join.js maps pointer coords with independent per-axis scaling
   (x*pad.width/r.width, y*pad.height/r.height), so any box whose aspect ratio
   differs from the 700x200 bitmap's 3.5:1 stores a stretched signature — a
   390px phone was writing them 2.3x too wide. Pinning the BOX to the bitmap's
   ratio makes both scales equal at every width. The real fix is to size the
   bitmap from the box on load/resize; see join.js. */
#sig-pad{
  width:100%;aspect-ratio:7/2;display:block;background:#fff;
  border:1px solid var(--hairline-strong);touch-action:none;cursor:crosshair;
}
/* 58 x 24 measured; the only way to undo a signature on a phone. */
.sig-clear{
  position:absolute;top:8px;right:8px;background:var(--surface-light);border:1px solid #ccc;color:#333;
  display:inline-flex;align-items:center;min-height:36px;
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);
  text-transform:uppercase;padding:4px 14px;cursor:pointer;
}
@media (hover:hover){.sig-clear:hover{background:#e2e2e2}}

/* ---------- team gear order form ---------- */
.order-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:16px;margin-top:16px}
.order-card{background:var(--surface-3);border:0;border-radius:var(--r-sm);padding:14px;display:flex;flex-direction:column;gap:8px}
.order-card img{width:100%;aspect-ratio:1;object-fit:contain;background:#fff;border:0;border-radius:var(--r-sm)}
.order-card>b{font-family:var(--cond);font-size:var(--t-md);letter-spacing:var(--tr-tight)}
.order-price{font-family:var(--display);font-size:var(--t-xl);color:var(--white)}
.order-opt{display:flex;justify-content:space-between;align-items:center;gap:10px;font-family:var(--cond);font-size:var(--t-sm);letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--silver)}
/* background-COLOR, not the shorthand: the shorthand wiped the shared select
   chevron (see the `select` rule above) and put /order back to a picker that
   looks like a text box. */
.order-opt select{flex:0 0 58%;background-color:var(--panel);color:var(--white);border:0;border-radius:var(--r-sm);padding:8px 30px 8px 10px}
.order-card .btn{justify-content:center;margin-top:auto}
/* Required vs optional has to be readable from across the room — it's the
   one thing a parent must not get wrong on this page. */
.order-flag{
  display:block;text-align:center;padding:7px 10px;margin:-14px -14px 4px;
  font-family:var(--cond);font-weight:600;font-size:var(--t-sm);letter-spacing:var(--tr-label);text-transform:uppercase;
}
.order-flag.req{background:var(--hl,var(--red));color:var(--on-hl,var(--on-red))}
.order-flag.opt{background:rgb(var(--ok-rgb) / .14);color:var(--ok);border-bottom:1px solid rgb(var(--ok-rgb) / .35)}

/* ---------- DiamondOps: search + season plan ---------- */
.sc-bar{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}
.sc-bar h3{margin:0 0 4px}

/* ---------- live: tournament HQ (/live) ---------- */
/* One column of time slots — the bracket the way the day actually runs.
   Placeholder slots ("Winner of G2") render dim until Exposure resolves them. */
.lv-page .sub{margin-bottom:4px}
.lv-h{font-family:var(--cond);font-weight:600;font-size:var(--t-base);letter-spacing:var(--tr-eyebrow);text-transform:uppercase;
  color:var(--white);margin:34px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--hairline)}
.lv-ourgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr));gap:12px}
.lv-game{position:relative;background:var(--surface-2);border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);padding:14px 16px 12px}
.lv-game.lv-ours{border-color:var(--red);box-shadow:inset 3px 0 0 var(--red)}
.lv-gnum{position:absolute;top:10px;right:12px;font-family:var(--cond);font-weight:600;font-size:var(--t-xs);
  letter-spacing:var(--tr-0);color:var(--dim)}
.lv-ours .lv-gnum{color:var(--link,var(--red-text))}
.lv-teams{padding-right:34px}
.lv-teams b{display:block;font-family:var(--cond);font-weight:600;font-size:var(--t-lg);line-height:1.25;
  text-transform:uppercase;letter-spacing:var(--tr-tight);color:var(--white)}
.lv-teams i{display:block;font-style:normal;font-family:var(--cond);font-weight:500;font-size:var(--t-2xs);
  letter-spacing:var(--tr-eyebrow);text-transform:uppercase;color:var(--dim);margin:2px 0}
.lv-teams .lv-tbd{color:var(--dim);font-weight:500}
.lv-teams .lv-won{color:var(--white)}
.lv-teams .lv-lost{color:var(--dim)}
.lv-teams .lv-hot,.lv-teams .lv-hot.lv-won,.lv-teams .lv-hot.lv-lost{color:var(--red-text)}
/* The letter that says who won, next to the team name. Its colour comes from
   the line's own state, not from the letter, so W and L can share one class:
   --ok is 8.09:1 and --red-text 4.79:1 on --panel (--red would be 3.13:1). */
.lv-wl{
  display:inline-block;margin-left:7px;font-family:var(--cond);font-weight:600;
  font-size:var(--t-xs);letter-spacing:var(--tr-0);
}
.lv-teams .lv-won .lv-wl{color:var(--ok)}
.lv-teams .lv-lost .lv-wl{color:var(--red-text)}
.lv-teams .lv-ts{float:right;font-variant-numeric:tabular-nums}
.lv-meta{display:block;margin-top:8px;font-family:var(--cond);font-weight:600;font-size:var(--t-xs);
  letter-spacing:var(--tr-eyebrow);text-transform:uppercase;color:var(--silver)}
.lv-meta .lv-dir{color:inherit;text-decoration:underline;text-underline-offset:3px}
/* Field forecast — one glanceable line under the event title. */
.lv-wx{margin:12px 0 0;font-family:var(--cond);font-weight:600;font-size:var(--t-base);letter-spacing:var(--tr-label);
  text-transform:uppercase;color:var(--silver)}
.lv-next{display:block;margin-top:8px;padding-top:8px;border-top:1px solid var(--hairline);
  font-size:var(--t-sm);color:var(--silver)}
.lv-ladder{display:flex;flex-direction:column;gap:14px}
.lv-day{font-family:var(--cond);font-weight:600;font-size:var(--t-sm);letter-spacing:var(--tr-eyebrow);text-transform:uppercase;
  color:var(--silver);margin:10px 0 0}
.lv-slot{display:grid;grid-template-columns:92px 1fr;gap:12px;align-items:start}
.lv-time{font-family:var(--display);font-size:var(--t-lg);color:var(--white);line-height:1.1;padding-top:14px;
  font-variant-numeric:tabular-nums}
.lv-slotgames{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr));gap:10px}
/* The watch buttons stack, GameChanger on top — watching the stream is the
   page's first job; the Exposure link-out is the footnote under it. */
#lv-watch{flex-direction:column;align-items:flex-start}

/* The bracket tree: absolutely-laid-out nodes + an SVG of elbow connectors,
   inside a horizontal scroller (brackets are wide; that's their nature).
   Solid edges follow winners; dashed edges are the loser drops. */
.bx-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;padding:12px 2px 10px}
/* What the two finals are worth — a tag riding the node's top edge. */
.bx-crown{position:absolute;top:-9px;left:8px;padding:1px 8px;font-family:var(--cond);font-weight:600;
  font-size:var(--t-xs);letter-spacing:var(--tr-label);text-transform:uppercase;border-radius:var(--r-xs);line-height:16px}
.bx-gold{background:var(--gold);color:#1a1408}
.bx-silver{background:#c7ccd4;color:#14161a}
/* Phones see one round at a time — say so. Wide screens see the whole tree
   and the hint would be noise. */
.bx-swipe{display:none;font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);
  text-transform:uppercase;color:var(--dim);margin:0 0 8px}
.bx-canvas{position:relative}
.bx-lines{position:absolute;inset:0;pointer-events:none}
.bx-edge{fill:none;stroke:var(--line);stroke-width:2}
.bx-drop{stroke-dasharray:4 5;opacity:.75}
.bx-edge-hot{stroke:var(--red)}
.bx-node{position:absolute;background:var(--surface-2);border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);padding:8px 10px;box-sizing:border-box}
.bx-node.bx-ours{border-color:var(--red);box-shadow:inset 3px 0 0 var(--red)}
.bx-g{position:absolute;top:7px;right:9px;font-family:var(--cond);font-weight:600;font-size:var(--t-2xs);
  letter-spacing:var(--tr-0);color:var(--dim)}
.bx-ours .bx-g{color:var(--link,var(--red-text))}
.bx-slot{font-family:var(--cond);font-weight:600;font-size:var(--t-sm);line-height:22px;letter-spacing:var(--tr-label);
  text-transform:uppercase;color:var(--white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  padding-right:26px}
.bx-slot:first-of-type{border-bottom:1px solid var(--hairline)}
.bx-slot.bx-tbd{color:var(--dim);font-weight:500}
/* Once a result posts: winner bright, loser dimmed, scores on the right
   (slot A's score sidesteps the G-number chip). DBC stay red always. */
.bx-slot{position:relative}
.bx-slot.bx-won{color:var(--white)}
.bx-slot.bx-lost{color:var(--dim)}
/* AA red: these name OUR games at 11-13px. The red border + inset shadow on
   .bx-ours/.lv-ours still carries the brand cue at full strength. */
.bx-slot.bx-hot,.bx-slot.bx-hot.bx-won,.bx-slot.bx-hot.bx-lost{color:var(--red-text)}
.bx-s{position:absolute;right:0;top:0;font-variant-numeric:tabular-nums}
.bx-slot:first-of-type .bx-s{right:28px}
.bx-scored .bx-slot{padding-right:30px}
.bx-scored .bx-slot:first-of-type{padding-right:56px}
.bx-meta{position:absolute;left:10px;right:10px;bottom:7px;font-family:var(--cond);font-weight:600;
  font-size:var(--t-2xs);letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--silver);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* Training Legends tournaments on /live: a pinned two-squad scoreboard
   whose chips are the tabs, over swipeable division panels. */
.lv-tld{margin-top:20px}
.lv-tld .lv-ourgrid{margin-bottom:18px}
/* Pinned UNDER the site header, not inside it. The hard-coded 58/50 matched
   #app-header's reserved min-height, not the header's real rendered height —
   on a phone that stacks to ~104px (149px with Next Up), so the bar parked at
   y=50 with a z-index under the header's sat wholly inside its band, underneath it:
   the squad switcher and the Watch button vanished the moment /live scrolled.
   --chrome-h is the measured height site.js/chrome-boot publish; the fallbacks
   are the same stacked-bar estimates .gd-save-alert uses. z-index clears the
   header's 50 so a short measurement can never bury it again. */
.lv-tlbar{position:sticky;top:var(--chrome-h,var(--header-h));z-index:var(--z-chrome);background:rgba(11,11,13,.96);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  margin-top:16px;padding:10px 0 12px;border-bottom:3px solid var(--red)}
/* The squad switcher is a segmented control cut from the same cloth as the
   site's skewed buttons: an angled recessed track, and a red thumb that
   physically slides under the selected squad — same motion as the panels
   below it, so "this switches" is legible at a glance. Track and thumb skew
   together; the chip content counter-skews so the type stays upright. */
.lv-schips{display:flex;gap:6px;position:relative;background:#131318;border:0;border-radius:var(--r-sm);
  padding:5px;isolation:isolate;margin:0 8px;transform:none;
  box-shadow:inset 0 2px 10px rgba(0,0,0,.5)}
.lv-thumb{position:absolute;top:5px;bottom:5px;left:5px;z-index:0;
  background:linear-gradient(180deg,var(--red-hover),var(--red));transition:transform var(--dur-3) var(--ease-out);
  box-shadow:0 2px 12px rgb(var(--red-rgb) / .5),inset 0 1px 0 rgba(255,255,255,.16)}
.lv-thumb.noanim{transition:none}
.lv-schip{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px;align-items:flex-start;
  background:none;border:0;padding:9px 11px 10px;cursor:pointer;position:relative;z-index:1;
  color:#ccd0d7;text-align:left;font-family:var(--cond);-webkit-tap-highlight-color:transparent;
  transform:none;transition:color var(--dur-2) var(--ease-out)}
.lv-schip:active{opacity:.85}
.lv-schip.on{color:#fff}
.lv-schip b{font-family:var(--display);font-size:var(--t-md);letter-spacing:var(--tr-0);line-height:1;color:inherit}
.lv-schip b i{font-style:normal;font-size:var(--t-xs);margin-left:7px;font-family:var(--cond);opacity:.75}
.lv-schip span{font-size:var(--t-xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;opacity:.8}
.lv-schip em{font-style:normal;font-size:var(--t-sm);font-weight:700;display:flex;align-items:center;gap:6px;
  font-variant-numeric:tabular-nums}
.lv-schip.on .live-dot{background:#fff}
/* One watch button, not a second 10U row: every squad's slot stays in the
   DOM but only the selected squad's shows — tlSelect swaps .on with the chip. */
.lv-tlwatch{margin:10px 8px 0}
.lv-tlwatch .lv-gcbtn{display:none}
.lv-tlwatch .lv-gcbtn.on{display:flex;align-items:center;justify-content:center}
.lv-gcbtn{text-align:center;font-family:var(--cond);font-weight:600;font-size:var(--t-xs);
  letter-spacing:var(--tr-label);text-transform:uppercase;padding:9px 10px;border:0;border-radius:var(--r-sm);
  background:var(--surface-3);
  color:var(--white);text-decoration:none;line-height:1.3;transform:none}
.lv-gcbtn>span{display:inline-flex;transform:none}
@media (hover:hover){.lv-gcbtn:hover{background:rgb(var(--red-rgb) / .14)}}
.lv-gcsoon{border-color:var(--hairline);color:var(--silver);cursor:default}
@media (hover:hover){.lv-gcsoon:hover{background:none}}
/* Swipe discoverability: a quiet hint line above the panels, and a one-time
   peek where the track eases toward the neighboring squad and back on the
   session's first paint — the motion IS the instruction. */
.lv-swipehint{margin:14px 8px 0;font-family:var(--cond);font-weight:600;font-size:var(--t-xs);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--dim);text-align:center}
.lv-sh-arrows{color:var(--red);font-size:var(--t-base);vertical-align:-1px}
@keyframes lv-peek{0%{transform:translateX(0)}45%{transform:translateX(-34px)}100%{transform:translateX(0)}}
.lv-swipe-track.lv-peek{animation:lv-peek 1s var(--ease-out) .7s 1}
/* TL bracket panel: the Exposure tree's matchup nodes in a static grid —
   TL publishes no advance-to wiring, so there are no connectors to draw. */
.lv-bxh{margin-top:26px}
.lv-bxgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px;margin-top:14px}
.lv-bxgrid .bx-node{position:relative;padding:10px 12px 32px}
.lv-bxgrid .bx-meta{bottom:8px}
.lv-swipe{overflow:hidden;margin-top:6px}
.lv-swipe-track{display:flex;align-items:flex-start;transition:transform var(--dur-3) var(--ease-out)}
.lv-swipe-track.noanim{transition:none}
.lv-pane{flex:0 0 100%;min-width:100%}
.lv-tb{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:22px;height:22px;margin-right:8px;vertical-align:-5px;
  font:600 12px/1 var(--cond);letter-spacing:var(--tr-tight);color:#fff;
  border:0;border-radius:var(--r-xs);background:rgba(255,255,255,.08)}
.lv-tb-us{background:#101014;border-color:var(--red)}
.lv-tb img{width:15px;height:15px;object-fit:contain;display:block}


/* ---------- Coaches Corner: the Today tab + deep-link chips ---------- */
.hs-chip{display:inline-block;background:none;border:0;border-radius:var(--r-xs);color:var(--silver);
  font-family:var(--cond);font-weight:600;font-size:var(--t-xs);letter-spacing:var(--tr-label);text-transform:uppercase;
  padding:8px 14px;cursor:pointer;text-decoration:none;transition:color var(--dur-1) var(--ease-out),border-color var(--dur-1) var(--ease-out)}
@media (hover:hover){.hs-chip:hover{color:var(--white);border-color:var(--silver)}}
.hs-chip.hs-hot{border-color:var(--red);color:var(--white)}
@media (hover:hover){.hs-chip.hs-hot:hover{background:rgb(var(--red-rgb) / .14)}}

/* ---------- Player stat pages + roster stat strips ---------- */
/* Active roster cards are now links — same card, no link dressing. */
a.player-card{color:inherit;text-decoration:none;display:flex}
.ro-stats{margin:2px 0 20px;padding:14px 16px 10px;background:var(--surface-3);border:0;border-radius:var(--r-sm)}
.ro-stats .record-strip{margin-top:8px}
.ro-stat-strip .cd-cell b{font-size:var(--t-2xl)}
.ro-stats-note{margin:8px 0 2px}
.ps-hero{display:flex;gap:26px;align-items:flex-end;flex-wrap:wrap;margin-top:6px}
.ps-photo{width:min(300px,60vw);flex:0 0 auto}
.ps-photo .pc-ph img{width:44%}
.ps-num{font-size:var(--t-base);padding:5px 12px}
.ps-name{margin:6px 0 0;font-size:clamp(30px,6vw,44px)}
.ps-chips{margin:10px 0 4px;display:flex;gap:8px}
.ps-squadchip{display:inline-block}
.ps-block{margin-top:30px}
.ps-tiles{flex-wrap:wrap}
.ps-tiles .cd-cell b{font-size:var(--t-2xl)}
.ps-seasons{display:flex;gap:10px;margin-top:24px;flex-wrap:wrap}
.ps-scroll{margin-top:10px}
.ps-table{min-width:0;width:100%}
.ps-table th,.ps-table td{padding:9px 12px;white-space:nowrap;text-align:center}
.ps-table td{font-variant-numeric:tabular-nums;font-weight:600;color:var(--white)}
.ps-table td:first-child,.ps-table th:first-child{text-align:left}

/* Stat strips wrap into a grid — 6-8 cells never overflow a phone. */
.ro-stats .record-strip,.ps-tiles.record-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:10px}
.ro-stats .record-strip .cd-cell,.ps-tiles .cd-cell{min-width:0}
/* The card says it's tappable. */
/* align-self, because the card is a flex COLUMN: an inline-block child of a
   column stretches to the card's full width unless it says not to, which is how
   a 90px chip became a full-bleed red bar under every player's name. */
.pc-view{display:inline-block;align-self:flex-start;margin-top:8px;font-family:var(--cond);font-weight:600;font-size:var(--t-xs);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--link,var(--red-text));border:0;border-radius:var(--r-xs);background:rgb(var(--red-rgb) / .12);padding:4px 10px;
  white-space:nowrap;
  transition:background var(--dur-1) var(--ease-out),color var(--dur-1) var(--ease-out)}
a.player-card:focus-visible .pc-view{background:var(--red);color:var(--on-red)}
@media (hover:hover){a.player-card:hover .pc-view{background:var(--red);color:var(--on-red)}}
/* Way back off a kid's page, top and bottom. */
.ps-backrow{margin:0 0 18px}
.ps-back{padding:9px 18px;font-size:var(--t-sm)}

/* The stat legend — collapsed by default, definitions in tidy columns. */
.ps-legend{margin-top:26px;background:var(--surface-2);border:0;border-radius:var(--r-md);box-shadow:var(--elev-1);padding:0 18px}
.ps-legend summary{cursor:pointer;padding:14px 0;font-family:var(--cond);font-weight:600;font-size:var(--t-sm);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--silver)}
.ps-legend summary:hover{color:var(--white)}
.ps-legend .eyebrow{margin:14px 0 8px}
.ps-legend-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:5px 22px;margin:0 0 16px}
.ps-legend-list div{display:flex;gap:10px;align-items:baseline}
.ps-legend-list dt{flex:0 0 44px;font-family:var(--cond);font-weight:600;font-size:var(--t-xs);
  letter-spacing:var(--tr-label);color:var(--link,var(--red-text))}
.ps-legend-list dd{margin:0;font-size:var(--t-sm);color:var(--silver)}

/* Stat tooltips: hover on desktop, tap (focus) on touch. Table headers get
   the bubble BELOW — the horizontal scroll box would clip one above. */
[data-tip]{position:relative;cursor:help}
/* These cells carry tabindex="0" (site.js), so they are a keyboard stop: the
   tooltip appearing is not a focus indicator. Ring for keyboard, nothing for
   a mouse click. */
[data-tip]:focus:not(:focus-visible){outline:none}
[data-tip]:focus-visible{outline:2px solid var(--white);outline-offset:2px}
[data-tip]:hover::after,[data-tip]:focus::after{
  content:attr(data-tip);position:absolute;left:50%;transform:translateX(-50%);
  bottom:calc(100% + 8px);background:var(--panel-2);color:var(--white);border:0;box-shadow:var(--elev-2);
  /* 600, not 500: Barlow is requested at 400/600/700 (see the Google Fonts
     link in every <head>), so a 500 here was synthesised by the browser and
     rendered visibly lighter and blurrier than the rest of the UI. */
  padding:7px 11px;font-family:var(--body);font-weight:600;font-size:var(--t-xs);line-height:1.35;
  letter-spacing:normal;text-transform:none;white-space:normal;width:max-content;max-width:170px;
  text-align:center;z-index:var(--z-toast);box-shadow:0 10px 24px rgba(0,0,0,.55);pointer-events:none}
[data-tip][data-tip-below]:hover::after,[data-tip][data-tip-below]:focus::after{
  bottom:auto;top:calc(100% + 6px)}
/* Edge columns pin to their own edge so the scroll box can't clip them. */
th[data-tip]:first-child:hover::after,th[data-tip]:first-child:focus::after{left:0;transform:none}
th[data-tip]:last-child:hover::after,th[data-tip]:last-child:focus::after{left:auto;right:0;transform:none}
[data-tip]:hover b,[data-tip]:focus b{color:var(--red)}

/* ---------- pressed states ----------------------------------------------
 * Measured before this block: 79 :hover rules across site.css + desktop.css
 * and exactly ONE :active rule on the whole public site (.lv-schip). The only
 * acknowledgement a finger got was the UA default tap highlight, measured on
 * <html> as rgba(51,181,229,.4) — a LIGHT BLUE flash on a red-and-black brand
 * (W9/mobile F5). Nothing on the site answered a tap, which is most of what
 * "cemented" means.
 *
 * Last in the sheet on purpose: :active and :hover carry the same specificity,
 * so the press has to come after the hover rules to win while the finger is
 * down. The surface families each get the same two ideas — a small scale-down
 * and a lighter ground — so one press reads the same everywhere.
 */
html{-webkit-tap-highlight-color:transparent}
:where(
  .btn,.sc-schip,.tab,.soc-tab,.tab-chip,.hs-chip,.gear-card,.sponsor-card,
  .hl-poster,.player-card,.sb,.cal-today,.cal-nav,.cal-more,.row-del,.sig-clear,
  .lv-gcbtn,.sp-tier-card,.game-row a,.thumb-grid img,.retry-btn
):active{
  transform:scale(.985);
}
/* .btn carries the brand skew on the element itself, so a bare scale() would
   throw the skew away mid-press. */
.btn:active{transform:scale(.985)}
:where(
  .sc-schip,.sc-panel-head,.sb,.gear-card,.sponsor-card,.cal-today,.cal-nav,
  .hs-chip,.tab,.soc-tab,.tab-chip,.fold>summary,.sched-fold>summary,.ro-inactive>summary,
  .ps-legend summary,.ts-remove-d>summary
):active{
  background-color:var(--surface-active);
}
:where(button.cal-ev,.affil-strip a,.sponsor-scroll .sponsor-card):active{filter:brightness(1.18)}
/* Everything above moves; nothing above should move for a reader who asked it
   not to. The tap highlight stays off either way — it is a colour, not motion. */
@media (prefers-reduced-motion:reduce){
  :where(.btn,.sc-schip,.tab,.soc-tab,.tab-chip,.hs-chip,.gear-card,.sponsor-card,
    .hl-poster,.player-card,.sb,.cal-today,.cal-nav,.cal-more,.row-del,.sig-clear,
    .lv-gcbtn,.sp-tier-card,.game-row a,.thumb-grid img,.retry-btn):active{transform:none}
  .btn:active{transform:none}
}

/* ---------- images inside tappable things --------------------------------
 * A 14-step swipe starting over a gallery photo travelled 573px; the same
 * swipe over the page background travelled 867px — a third of the gesture
 * lost (W9/mobile F19). No touch listener on the site is the cause (all four
 * are {passive:true}); it is iOS raising the image callout / starting an image
 * drag on a press-and-move that begins on an <img> inside an <a>.
 * grep found no touch-callout, user-select or user-drag rule anywhere.
 */
.thumb-grid img,.carousel img,.hero-rotator img,.gear-img,.affil-strip img,
.sponsor-card img,.hl-poster img,.pc-photo img,.order-card img{
  -webkit-touch-callout:none;
  -webkit-user-select:none;user-select:none;
  -webkit-user-drag:none;
}
/* Only the walls of photos get touch-action: the carousel and the Tournament
   HQ panels carry real horizontal swipe handlers and must keep both axes. */
.thumb-grid img,.gear-img,.affil-strip img,.sponsor-card img{touch-action:pan-y}

/* ---------- the coach bar (assets/coach-edit.js) -------------------------
 * Drawn only for a reader /api/settings has already named as a coach, and
 * only on a browser that has held a coach session. A visitor's page never
 * builds any of this.
 *
 * position:fixed on a child of <body>, never a strip above <main> or inside
 * the header: this codebase measures layout shift on every page at four
 * widths, and chrome that grows an ancestor is the defect it keeps finding.
 * Where the strip sits is the two layout sheets' job — mobile.css pins it to
 * the bottom of the phone, desktop.css to the top-right corner.
 *
 * The entrance is an animation rather than a class the script has to add, so
 * the bar cannot be left invisible by a frame callback that never runs.
 */
.coach-bar{
  position:fixed;z-index:var(--z-chrome);
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-2);
  padding:var(--s-2);
  background:var(--panel-2);
  border:1px solid var(--hairline-strong);border-radius:var(--r-md);
  box-shadow:var(--elev-2);
  animation:cb-in var(--dur-2) var(--ease-out) both;
}
@keyframes cb-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.cb-who{
  flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  padding-left:var(--s-2);
  color:var(--text-2);font-family:var(--cond);font-size:var(--t-xs);
  letter-spacing:var(--tr-label);text-transform:uppercase;
}
/* The one button in every coach surface on a public page: the bar, a gear's
   sheet, the Undo toast. 40px is the floor every control on this site is held
   to (ui-check (f)), so it belongs to the component and not to the strip. */
.cb-btn{
  flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  min-height:40px;padding:0 var(--s-3);
  background:var(--surface-hover);color:var(--text-1);text-decoration:none;
  border:1px solid var(--hairline);border-radius:var(--r-sm);
  font-family:var(--cond);font-size:var(--t-sm);font-weight:600;
  letter-spacing:var(--tr-label);text-transform:uppercase;white-space:nowrap;
  cursor:pointer;
  transition:background-color var(--dur-1) var(--ease-out);
}
.cb-btn:hover{background:var(--surface-active)}
.coach-bar .cb-edit{background:var(--red);border-color:var(--red);color:var(--on-red)}
.coach-bar .cb-edit:hover{background:var(--red-hover)}
/* A page with no words of the club's on it says so by being plainly dead,
   rather than by opening an editor with nothing in it. */
.cb-btn[disabled]{
  background:var(--surface-hover);border-color:var(--hairline);color:var(--text-3);
  cursor:default;
}
@media (prefers-reduced-motion:reduce){
  .coach-bar{animation:none}
}
/* One line of state, under the buttons: what the editor is doing, what it
   refused, what it saved. role="status" so it is spoken, not just shown. */
.cb-note{
  flex:1 0 100%;
  color:var(--text-2);font-family:var(--body);font-size:var(--t-xs);line-height:var(--lh-tight);
}

/* ---------- Edit page: one field per sentence (assets/coach-edit.js) -----
 * The field sits in the flow directly under the slot it edits, and the slot
 * keeps painting through applyVoice on every keystroke — so the sentence
 * above the box IS the preview, and there is no second rendering of the page
 * to keep in step with the real one. Left-aligned whatever the slot does: a
 * centered hero must not centre a text field.
 */
.cv-field{
  display:block;margin:var(--s-2) 0;padding:var(--s-2) var(--s-3);
  max-width:var(--measure-body);
  background:var(--panel-2);border:1px solid var(--hairline-strong);
  border-left:3px solid var(--red);border-radius:var(--r-sm);
  text-align:left;
}
.cv-label{
  display:inline-block;
  color:var(--text-2);font-family:var(--cond);font-size:var(--t-2xs);
  letter-spacing:var(--tr-label);text-transform:uppercase;
}
.cv-count{
  float:right;
  color:var(--text-3);font-family:var(--mono);font-size:var(--t-2xs);
}
/* The cap is the server's (lib/voice.js); say so before the clipping does. */
.cv-count.is-full{color:var(--warn)}
.cv-input{
  display:block;width:100%;margin-top:var(--s-1);padding:var(--s-2);
  background:var(--surface-0);color:var(--text-1);
  border:1px solid var(--hairline-strong);border-radius:var(--r-xs);
  font-family:var(--body);font-size:var(--t-base);line-height:var(--lh-body);
  resize:vertical;
}
.cv-input:focus{outline:2px solid var(--red-hover);outline-offset:1px}

/* ---------- the gears: settings, beside the card that shows them ---------
 * The button is absolutely positioned inside a card the page already ships,
 * so a coach's chrome costs the page no height at all — the same contract the
 * bar keeps, and the reason /dues can carry two gears and still be measured
 * against the layout-shift budget every page is held to. The sheet under it
 * is ordinary flow: it only ever appears on a deliberate tap.
 */
.cg-anchor{position:relative}
.cg-gear{
  position:absolute;top:var(--s-3);right:var(--s-3);z-index:3;
  display:inline-flex;align-items:center;justify-content:center;
  width:40px;height:40px;padding:0;
  background:var(--panel-2);color:var(--text-1);
  border:1px solid var(--hairline-strong);border-radius:var(--r-sm);
  box-shadow:var(--elev-1);
  font-size:var(--t-md);line-height:var(--lh-tight);
  cursor:pointer;
  transition:background-color var(--dur-1) var(--ease-out);
}
.cg-gear:hover{background:var(--surface-active)}
.cg-gear:focus-visible{outline:2px solid var(--red-hover);outline-offset:2px}
.cg-gear[aria-expanded="true"]{background:var(--red);border-color:var(--red);color:var(--on-red)}
/* Off the edges at every width, and never wider than a form wants to be. */
.cg-sheet{
  margin:var(--s-3) auto;padding:var(--s-4);
  width:calc(100% - var(--s-4) * 2);max-width:var(--measure-body);
  background:var(--panel-2);border:1px solid var(--hairline-strong);
  border-left:3px solid var(--red);border-radius:var(--r-sm);
  text-align:left;
}
.cg-title{
  margin:0 0 var(--s-3);
  color:var(--text-1);font-family:var(--cond);font-size:var(--t-sm);font-weight:600;
  letter-spacing:var(--tr-label);text-transform:uppercase;
}
.cg-field{display:block;margin:0 0 var(--s-3)}
.cg-label{
  display:block;
  color:var(--text-2);font-family:var(--cond);font-size:var(--t-2xs);
  letter-spacing:var(--tr-label);text-transform:uppercase;
}
.cg-hint{
  display:block;
  color:var(--text-3);font-family:var(--body);font-size:var(--t-2xs);line-height:var(--lh-tight);
}
.cg-input{
  display:block;width:100%;margin-top:var(--s-1);
  /* 40px is the floor every control on this site is held to (ui-check (f)). */
  min-height:40px;padding:var(--s-2);
  background:var(--surface-0);color:var(--text-1);
  border:1px solid var(--hairline-strong);border-radius:var(--r-xs);
  font-family:var(--body);font-size:var(--t-base);line-height:var(--lh-body);
}
.cg-input:focus{outline:2px solid var(--red-hover);outline-offset:1px}
/* The five handles, put away while dues use the team account. */
.cg-pay{margin:0 0 var(--s-3);padding-left:var(--s-3);border-left:1px solid var(--hairline)}
.cg-row{display:flex;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--s-3)}
/* Save is the one thing in a sheet a coach came to press — the same red the
   bar's Edit page carries, so the two editors read as one tool. */
.cg-row .cb-save{background:var(--red);border-color:var(--red);color:var(--on-red)}
.cg-row .cb-save:hover{background:var(--red-hover)}
.cg-note{
  margin:var(--s-2) 0 0;
  color:var(--text-2);font-family:var(--body);font-size:var(--t-xs);line-height:var(--lh-tight);
}

/* ---------- the Undo toast (assets/coach-edit.js) ------------------------
 * Five seconds, one Undo, and the Undo is a second save through the same
 * route — not a local rollback (docs/COACH-EDITING.md §6 rule 4). Fixed and
 * parented to <body> for the same reason the bar is: it must not move the
 * page it is confirming a change to. Where it sits is the two layout sheets'
 * job, clear of the bar in both.
 */
.cb-toast{
  position:fixed;z-index:var(--z-chrome);
  display:flex;align-items:center;gap:var(--s-3);
  padding:var(--s-2) var(--s-3);
  background:var(--panel-2);color:var(--text-1);
  border:1px solid var(--hairline-strong);border-radius:var(--r-md);
  box-shadow:var(--elev-2);
  animation:cb-in var(--dur-2) var(--ease-out) both;
}
.cb-toast-msg{
  flex:1 1 auto;min-width:0;
  font-family:var(--cond);font-size:var(--t-sm);
  letter-spacing:var(--tr-label);text-transform:uppercase;
}
.cb-toast .cb-btn{background:var(--red);border-color:var(--red);color:var(--on-red)}
.cb-toast .cb-btn:hover{background:var(--red-hover)}
@media (prefers-reduced-motion:reduce){
  .cb-toast{animation:none}
}

/* ============================================================
   THE TEAM SPACE — the wall, and the way off the public roster
   ============================================================
   One card, three one-field steps, on a page whose own markup has just been
   replaced. Nothing here is a new visual idea: it is .section/.wrap/.field/
   .btn wearing the panel surface the rest of the site uses for a form, so a
   parent who has seen /join recognises it.
*/
.ts-wall{padding-block:var(--s-8)}
.ts-wall-title{margin:var(--s-2) 0 var(--s-3)}
.ts-wall-lead{color:var(--text-2);max-width:var(--measure-lead);margin:0 0 var(--s-6)}
.ts-card{
  background:var(--panel-2);
  border:1px solid var(--hairline-strong);
  border-radius:var(--r-lg);
  padding:var(--s-6);
  max-width:var(--measure-lead);
}
.ts-card .btn-row{margin-top:var(--s-4)}
.ts-wall-foot{margin-top:var(--s-6);max-width:var(--measure-body);color:var(--text-3)}
/* The refusals read in the club red; everything else is the quiet note the
   rest of the site uses for a status line. */
.ts-card .msg-line.is-bad{color:var(--red)}

.ts-remove{margin-top:var(--s-8)}
/* The same treatment .ro-inactive>summary got on this very page, and for the
   same reason: site.css strips the native disclosure marker site-wide, so a
   summary with no ::before, no hover and no min-height is dim grey uppercase
   text floating in empty space that does not look clickable — and it measured
   40px at 390, against the 44px WCAG 2.5.8 asks for (W9/mobile F22). */
.ts-remove-d>summary{
  cursor:pointer;list-style:none;color:var(--text-2);
  display:flex;align-items:center;min-height:44px;
  font-family:var(--cond);font-size:var(--t-sm);
  letter-spacing:var(--tr-label);text-transform:uppercase;
}
.ts-remove-d>summary::before{content:"\25B8  ";color:var(--red)}
.ts-remove-d[open]>summary::before{content:"\25BE  "}
.ts-remove-d>summary:hover{color:var(--text-1)}
.ts-remove-d>summary:focus-visible{outline:2px solid var(--red);outline-offset:3px;border-radius:var(--r-sm)}
.ts-remove-d[open]{
  margin-top:var(--s-3);padding:var(--s-5);
  background:var(--panel-2);
  border:1px solid var(--hairline);border-radius:var(--r-lg);
  max-width:var(--measure-lead);
}
/* The hero's ground and the age chip — spec brand.ground "diamond", brand.chip "square". */
.hero-full[data-ground="diamond"]{background-color:#132A4A;background-image:var(--home-hero-image, none),url(/assets/img/home/ground.svg);background-position:var(--home-hero-pos, center 40%),center bottom;background-size:cover,cover;background-repeat:no-repeat,no-repeat}
