/* ---------------------------------------------------------------------------
   Rockball home v2. Self-contained; touches neither site.css nor index.html.

   Target: the craft level of a premium product page, with the character of a
   broadcast/NBA property. Which means:
     - typographic confidence, not decoration — display type is big and tight,
       body is quiet, and the gap between them does the work
     - numbers are heroes. Prices, ages and counts are set like a box score
     - photography is treated, not dropped in: greyscale by default, colour on
       interaction, so it reads as one system rather than a stock library
     - hairlines and alignment over boxes and shadows
     - section numbers (01/02/03) like a broadcast package

   Palette is the brand guide, unchanged. The two greys swap roles by ground —
   measured: on white, Grey Dark 5.77 body-safe / Grey 2.96 hairlines only;
   on ink, Grey 6.57 body-safe / Grey Dark 3.37 small labels only.
   Surface Red and Banner Red are interaction states only, never a whole band.
   --------------------------------------------------------------------------- */

@font-face { font-family:'Poppins'; font-style:normal; font-weight:300; font-display:swap;
  src:url('assets/fonts/poppins-300-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:400; font-display:swap;
  src:url('assets/fonts/poppins-400-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family:'Poppins'; font-style:normal; font-weight:500; font-display:swap;
  src:url('assets/fonts/poppins-500-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }

:root{
  --red:#D52F2E;
  --white:#FEFEFE;
  --grey:#959597;
  --grey-dark:#656567;
  --ink:#0C0B0E;
  --surface:#111113;
  --surface-red:#160C0D;
  --banner-red:#241514;

  --rule:rgba(149,149,151,.26);
  --rule-soft:rgba(149,149,151,.15);
  --rule-dark:rgba(149,149,151,.16);
  --red-tint:rgba(213,47,46,.05);

  --content:min(93vw,1280px);

  /* display type runs big and tight; body stays quiet. that gap is the design */
  --fs-display:clamp(52px,8.4vw,132px);
  --fs-h2:clamp(32px,4.1vw,62px);
  --fs-h3:clamp(19px,1.5vw,25px);
  --fs-stat:clamp(38px,4.4vw,64px);
  --fs-lead:clamp(16.5px,1.18vw,19.5px);
  --fs-body:clamp(15px,1vw,16.5px);
  --fs-label:11px;

  --sec:clamp(86px,8.2vw,150px);

  /* a back-out curve: overshoots the target then settles. this is what makes a
     hover feel bubbly rather than mechanical */
  --spring:cubic-bezier(.34,1.62,.58,1);
}

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth}
/* the header is sticky, so anchored sections must clear it or the nav jumps land
   with the eyebrow hidden underneath */
:target,#offer,#groups,#memberships,#group-memberships,#packages{scroll-margin-top:76px}
body{
  margin:0;background:var(--white);color:var(--ink);
  font:400 var(--fs-body)/1.72 'Poppins',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
}
img{max-width:100%;display:block}
/* any class that sets display (.price, .checks li) would otherwise beat the attribute */
[hidden]{display:none !important}
a{color:inherit;text-decoration:none}
h1,h2,h3,h4,p{margin:0}

.wrap{width:var(--content);margin-inline:auto}
.sec{padding-block:var(--sec)}
.sec.tight{padding-block:clamp(60px,5.6vw,96px)}
/* Every section ground is white as of 2026-08-14. Only three things stay dark: the header,
   the footer and the hero photograph. Nothing alternates — every section sits on the same
   ground, and `ends` marks where one finishes.

   `ends` is a hairline on the section's bottom edge, held to the width of the information and
   feathered away at both ends so it stops without a cut. It is the same geometry the tonal
   band used, reduced to a single line: --content wide, centred, --fade-x of dissolve at each
   end, capped at the margin available so a narrow window shortens the feather instead of
   running it off the screen. Only the red bar under the footer runs edge to edge — see
   design\BRAND.md.

   This replaced an alternating white / near-white band. The band worked, but a checkerboard of
   grounds is a heavy way to say "a section ended", and at any tone faint enough not to look
   like an uneven backlight it was too faint to read. A line says it once and says it exactly.

   Not every section takes one. Two sections joined by `padding-top:0` are a single run, so the
   rule goes under the pair rather than between them, and the last section before the footer
   needs nothing — the dark ground already ends the page.

   It is `.ends` rather than `.sec.ends` because the masthead on the five interior pages is a
   `.mast`, not a `.sec`, and needs the same rule under it. `.mast` used to draw its own
   full-bleed `border-bottom` and `.closer` its own full-bleed `border-top`; both are gone, so
   there is now exactly one section-boundary device on the site instead of three. */
.ends{
  --fade-x:min(56px, calc((100vw - var(--content)) / 2));
  --band:calc(var(--content) + 2 * var(--fade-x));
  background-image:linear-gradient(90deg,
    rgba(149,149,151,0) 0, var(--rule) var(--fade-x),
    var(--rule) calc(100% - var(--fade-x)), rgba(149,149,151,0) 100%);
  background-repeat:no-repeat;
  background-position:center bottom;
  background-size:var(--band) 1px;
}

/* ---- the broadcast eyebrow: 01 — LABEL, with a red rule -------------------- */
.eyebrow{display:flex;align-items:center;gap:14px;margin-bottom:22px}
.eyebrow .no{
  font-size:var(--fs-label);font-weight:500;letter-spacing:.14em;color:var(--red);
  font-variant-numeric:tabular-nums;
}
/* an unnumbered eyebrow still paid one flex gap for the empty slot: 14px of indent, 7px off centre */
.eyebrow .no:empty{display:none}
.eyebrow .bar{width:38px;height:2px;background:var(--red);flex:none}
.js .rv .eyebrow .bar{transform:scaleX(0);transform-origin:left;
  transition:transform .7s cubic-bezier(.2,.7,.3,1) calc(var(--d,0ms) + 150ms)}
.js .rv.in .eyebrow .bar{transform:none}
.js .rv .eyebrow .pick{opacity:0;transform:scale(.4);
  transition:opacity .2s calc(var(--d,0ms) + 450ms),transform .6s var(--spring) calc(var(--d,0ms) + 450ms)}
.js .rv.in .eyebrow .pick{opacity:1;transform:none}
.eyebrow .tx{font-size:var(--fs-label);font-weight:500;letter-spacing:.22em;text-transform:uppercase;color:var(--grey-dark)}

/* display type — uppercase, tight, confident. this is the jersey voice */
.display{
  font-size:var(--fs-display);font-weight:500;line-height:.93;letter-spacing:-.038em;
  text-transform:uppercase;
}
h2{font-size:var(--fs-h2);font-weight:500;line-height:1.02;letter-spacing:-.032em;text-transform:uppercase}
h3{font-size:var(--fs-h3);font-weight:500;line-height:1.22;letter-spacing:-.012em}
.lead{font-size:var(--fs-lead);line-height:1.74;color:var(--grey-dark);font-weight:300}

.head{max-width:760px;margin-bottom:clamp(44px,4.4vw,72px)}
.head h2{margin-bottom:18px}
.head.center{margin-inline:auto;text-align:center}
.head.center .eyebrow{justify-content:center}

/* ---- buttons -------------------------------------------------------------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  font-size:13px;font-weight:500;line-height:1;letter-spacing:.11em;text-transform:uppercase;
  padding:19px 32px;border:1.5px solid transparent;border-radius:2px;
  transition:background .2s,border-color .2s,color .2s,box-shadow .35s ease,transform .42s var(--spring);
  white-space:nowrap;cursor:pointer;position:relative;overflow:hidden;
}
/* pressing gives — a button that only moves on hover feels like a picture of one */
.btn:active{transform:translateY(-1px) scale(.97);transition-duration:.08s}
.btn:focus-visible{outline:2px solid var(--red);outline-offset:3px}
.btn-primary{background:var(--red);color:var(--white);border-color:var(--red)}
.btn-primary:hover{background:var(--banner-red);border-color:var(--red);transform:translateY(-3px) scale(1.02);
                   box-shadow:0 14px 26px -14px rgba(213,47,46,.6)}
/* one pass of light across the face on the way in. It only animates on entry — leaving
   resets it instantly, so it never plays backwards. White over red, so still one red. */
.btn-primary::after{content:'';position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(105deg,transparent 32%,rgba(254,254,254,.24) 50%,transparent 68%);
  transform:translateX(-110%)}
.btn-primary:hover::after{transform:translateX(110%);transition:transform .75s cubic-bezier(.2,.7,.3,1)}
.btn-line{border-color:var(--rule);color:var(--ink)}
.btn-line:hover{border-color:var(--red);color:var(--red);background:var(--red-tint);transform:translateY(-3px) scale(1.02)}
/* the two grounds that are still dark need the inverted outline button */
#hdr .btn-line,.hero .btn-line{border-color:rgba(254,254,254,.36);color:var(--white)}
#hdr .btn-line:hover,.hero .btn-line:hover{border-color:var(--red);background:var(--surface-red);color:var(--white)}
.btn-sm{padding:13px 20px;font-size:11.5px}
.arw{line-height:0;font-size:1.15em;transition:transform .42s var(--spring)}
.btn:hover .arw{transform:translateX(5px)}
.tile:hover .go .arw,.band:hover .go .arw,.coach:hover .go .arw{transform:translateX(5px)}
.go .arw{transition:transform .42s var(--spring)}
/* the faint underline is always there; hover draws a solid one over it from the left */
.tlink{color:var(--red);font-weight:500;box-shadow:inset 0 -1.5px 0 rgba(213,47,46,.35);
       background:linear-gradient(var(--red),var(--red)) 0 100%/0 1.5px no-repeat;
       transition:background-size .45s cubic-bezier(.2,.7,.3,1)}
.tlink:hover{background-size:100% 1.5px}

/* ---- header --------------------------------------------------------------- */
/* dark by design, not by constraint — logo.svg is transparent and works on any surface */
#hdr{position:sticky;top:0;z-index:60;background:var(--ink);border-bottom:1px solid var(--rule-dark)}
.hdr-in{display:flex;align-items:center;gap:26px;height:76px}
.hdr-in .logo img{width:168px}
/* the inline logo a .season page uses — same box as the <img> */
.hdr-in .logo svg{width:168px;height:auto;display:block}
.nav{display:flex;gap:28px;margin-left:auto}
.nav a{
  font-size:11.5px;font-weight:500;letter-spacing:.13em;text-transform:uppercase;
  color:var(--white);opacity:.72;transition:opacity .16s,color .16s;
}
.nav a:hover{opacity:1;color:var(--red)}
.hdr-cta{display:flex;align-items:center;gap:14px}
.socials{display:flex;gap:12px}
.socials img{width:17px;height:17px;opacity:.5;transition:opacity .16s}
.socials a:hover img{opacity:1}

/* The phone menu. The desktop nav is dropped at 1050px, and before this there was nothing in
   its place — a phone could only reach another page through the footer. The panel hangs off
   the sticky header on the same ink ground, so it adds no new dark section; it fills the
   screen below the bar, which is why tapping "outside" it never comes up. One red: the page
   you're on, and hover. With no script it isn't hidden at all, just listed under the bar. */
.menu-btn,.menu{display:none}
@media (max-width:1050px){
  .hdr-cta{margin-left:auto}
  .menu-btn{display:flex;flex-direction:column;justify-content:center;gap:5px;flex:none;
            width:44px;height:44px;margin-right:-10px;padding:0 10px;border:0;background:none;cursor:pointer}
  .menu-btn span{display:block;height:1.5px;background:var(--white);border-radius:1px;
                 transition:transform .3s cubic-bezier(.2,.7,.3,1),opacity .2s}
  .menu-btn:focus-visible{outline:2px solid var(--red);outline-offset:-2px;border-radius:2px}
  .menu-open .menu-btn span:nth-child(1){transform:translateY(6.5px) rotate(45deg)}
  .menu-open .menu-btn span:nth-child(2){opacity:0}
  .menu-open .menu-btn span:nth-child(3){transform:translateY(-6.5px) rotate(-45deg)}
  .no-js .menu-btn{display:none}

  .menu{display:block;border-top:1px solid var(--rule-dark)}
  .js .menu{position:absolute;left:0;right:0;top:100%;background:var(--ink);
            height:calc(100dvh - 100%);overflow-y:auto;overscroll-behavior:contain}
  .js .menu:not(.open){display:none}
  .js .menu.open{animation:menu-in .26s cubic-bezier(.2,.7,.3,1)}
  .menu .wrap{padding-block:8px 36px}
  .menu-a{display:flex;align-items:center;justify-content:space-between;padding:17px 0;
          border-bottom:1px solid var(--rule-dark);color:var(--white);
          font-size:24px;font-weight:500;line-height:1.1;letter-spacing:-.02em;text-transform:uppercase;
          transition:color .16s}
  .menu-a .arw{color:var(--grey-dark);transition:color .16s,transform .42s var(--spring)}
  .menu-a:hover,.menu-a.on,.menu-a:hover .arw,.menu-a.on .arw{color:var(--red)}
  .menu-a:hover .arw{transform:translateX(5px)}
  .no-js .menu-a{font-size:15px;padding:11px 0}
  .menu-foot{display:flex;align-items:center;flex-wrap:wrap;gap:6px 26px;padding-top:24px}
  .menu-foot > a{font-size:14px;color:var(--grey);padding:12px 0;transition:color .16s}
  .menu-foot > a:hover{color:var(--red)}
  .menu-foot .socials{gap:4px;margin-left:auto}
  .menu-foot .socials a{display:grid;place-items:center;width:44px;height:44px}
  .menu-foot .socials img{width:20px;height:20px;opacity:.7}
  html.menu-open{overflow:hidden}
}
/* a phone keeps Log In on the bar — it's what a member opened the site for — and hands the
   socials to the menu so the logo, Log In and the button fit at 360px */
@media (max-width:480px){
  .hdr-in{gap:16px;height:64px}
  .hdr-in .logo img,.hdr-in .logo svg{width:132px}
  .hdr-cta .socials{display:none}
}
@keyframes menu-in{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){
  .js .menu.open{animation:none}
  .menu-btn span{transition:none}
}

/* ---- treated photography -------------------------------------------------- */
/* Fully black and white at rest — the source photos carry colour only in small
   spots (the balls), and leaving that in made the set look inconsistent rather
   than treated. On hover the whole photograph returns to full colour.
   One image per tile, one filter. No overlay layers. */
.duo{position:relative;overflow:hidden;background:var(--ink)}
.duo img{
  width:100%;height:100%;object-fit:cover;
  filter:grayscale(1) contrast(1.14) brightness(.96);
  /* --spring overshoots a little and settles — that's the bubbly part */
  transition:filter .5s ease,transform .7s var(--spring);
  will-change:filter,transform;
}
a:hover .duo img,.tile:hover .duo img{
  filter:grayscale(0) contrast(1.02) brightness(1) saturate(1.06);
  transform:scale(1.09);
}

/* ---- hero ----------------------------------------------------------------- */
.hero{position:relative;min-height:min(92vh,900px);display:flex;align-items:flex-end;overflow:hidden;background:var(--ink)}
.hero .duo{position:absolute;inset:0}
.hero .duo::after{opacity:.18}
.hero-scrim{
  position:absolute;inset:0;pointer-events:none;
  background:
    linear-gradient(180deg,rgba(12,11,14,.66) 0%,rgba(12,11,14,.10) 32%,rgba(12,11,14,.92) 100%),
    linear-gradient(96deg,rgba(12,11,14,.90) 0%,rgba(12,11,14,.62) 52%,rgba(12,11,14,.28) 100%);
}
.hero-in{position:relative;width:var(--content);margin-inline:auto;padding-block:clamp(56px,6vw,86px);color:var(--white)}
.hero .display{margin-bottom:26px;max-width:14ch}
.hero .display em{font-style:normal;color:var(--red)}
/* 600px sets the lead in two full lines — at 520 it broke to three and orphaned "crowd." */
.hero .lead{color:var(--grey);max-width:600px;margin-bottom:36px}
.cta-row{display:flex;flex-wrap:wrap;gap:12px}
.hero .eyebrow .tx{color:var(--grey)}

/* ---- the box score -------------------------------------------------------- */
.score{border-bottom:1px solid var(--rule-soft)}
.score-in{display:grid;grid-template-columns:repeat(4,1fr)}
.score .s{padding:clamp(30px,3vw,44px) 0 clamp(28px,2.8vw,40px);position:relative}
.score .s + .s{padding-left:clamp(22px,2.4vw,38px);border-left:1px solid var(--rule-soft)}
.score .n{
  font-size:var(--fs-stat);font-weight:500;line-height:1;letter-spacing:-.04em;
  font-variant-numeric:tabular-nums;margin-bottom:9px;
}
.score .k{font-size:var(--fs-label);font-weight:500;letter-spacing:.17em;text-transform:uppercase;color:var(--grey-dark)}

/* ---- offer ---------------------------------------------------------------- */
.offer{border:1px solid var(--rule);border-top:3px solid var(--red);
       padding:clamp(32px,3.6vw,54px);display:flex;gap:44px;flex-wrap:wrap;align-items:center}
.offer .t{flex:1 1 430px}
.offer h2{font-size:clamp(26px,2.7vw,40px);margin-bottom:12px}
.offer h2 .p{color:var(--red)}
/* --grey is 3.0:1 on white and fails AA; --grey-dark is 5.7:1. On the old dark ground it was
   the other way round, which is why these two read --grey until the 2026-08-14 inversion. */
.strike{color:var(--grey-dark);text-decoration:line-through;font-weight:300}
.offer-cta{display:flex;gap:11px;flex-wrap:wrap}

/* ---- age bands ------------------------------------------------------------ */
.grid{display:grid;gap:0}
.g4{grid-template-columns:repeat(4,1fr)}
.bands{border-top:1px solid var(--rule)}
.band{
  display:flex;flex-direction:column;padding:32px 26px 26px;
  border-bottom:1px solid var(--rule);position:relative;
  transition:background .25s,transform .5s var(--spring),box-shadow .45s ease;z-index:1;
}
.band:hover{transform:translateY(-11px);z-index:2;box-shadow:0 24px 44px -28px rgba(12,11,14,.45)}
.band + .band{border-left:1px solid var(--rule)}
.band::before{content:'';position:absolute;left:0;right:0;top:-1px;height:3px;background:var(--red);
              transform:scaleX(0);transform-origin:left;transition:transform .45s cubic-bezier(.2,.7,.3,1)}
.band:hover{background:var(--red-tint)}
.band:hover::before{transform:scaleX(1)}
.band .age{
  font-size:var(--fs-label);font-weight:500;letter-spacing:.16em;text-transform:uppercase;
  color:var(--red);font-variant-numeric:tabular-nums;
}
.band h3{margin:10px 0 18px;text-transform:uppercase;letter-spacing:-.02em}
.band ul{list-style:none;margin:0;padding:0;flex:1}
.band li{font-size:13.5px;color:var(--grey-dark);font-weight:300;line-height:1.5;padding:8px 0;
         border-top:1px solid var(--rule-soft)}
.band li:first-child{border-top:0}
.band .go{margin-top:20px;font-size:11px;font-weight:500;letter-spacing:.13em;text-transform:uppercase;
          color:var(--red);display:flex;gap:8px;align-items:center}

/* ---- programme tiles ------------------------------------------------------ */
.tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--rule)}
.tile{position:relative;display:block;background:var(--white);
       transition:background .25s,transform .58s var(--spring),box-shadow .45s ease;z-index:1}
.tile:hover{transform:translateY(-11px) scale(1.018);z-index:2;
            box-shadow:0 26px 50px -28px rgba(12,11,14,.55)}
.tile .duo{aspect-ratio:3/4}
.tile-b{padding:24px 22px 26px}
.tile h3{margin-bottom:7px;text-transform:uppercase;letter-spacing:-.018em}
.tile p{font-size:13.5px;color:var(--grey-dark);font-weight:300;line-height:1.58}
.tile .go{margin-top:15px;font-size:11px;font-weight:500;letter-spacing:.13em;text-transform:uppercase;
          color:var(--red);display:flex;gap:8px;align-items:center}
.tile-flag{
  position:absolute;top:0;left:0;z-index:3;background:var(--red);color:var(--white);
  font-size:10px;font-weight:500;letter-spacing:.15em;text-transform:uppercase;padding:8px 14px;
}

/* ---- memberships ----------------------------------------------------------- */
.plans{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--rule)}
.plan{
  background:var(--white);padding:36px 30px 34px;display:flex;flex-direction:column;
  transition:background .3s,transform .55s var(--spring),box-shadow .45s ease;position:relative;z-index:1;
  /* Kept on its own layer for good. Otherwise the lift promotes the card only while it
     moves, and Chrome on Windows re-rasters its text from LCD to greyscale and back, so
     any type that isn't already layered (the tier, the meta line) flashes on hover. */
  will-change:transform;
}
/* All three plans are identical until you touch one. The Most popular label is the only
   thing that points, so hover is the only red — a tinted featured card sat at the same
   specificity as :hover and won, which meant the popular plan was the one card that
   couldn't respond to the pointer. */
.plan:hover{background:var(--red-tint);transform:translateY(-11px);z-index:2;box-shadow:0 26px 46px -28px rgba(12,11,14,.45)}
/* Plans and packs share one hover: the card lifts, a red rule wipes across its top (the
   band idiom from the home page), a soft light follows the pointer, the price swells,
   the ticks pop in sequence and the button commits to red. --mx/--my come from the
   pricing page's script; anywhere without it the light simply sits top-centre. The light
   is z-index -1 inside the card's own stacking context, so it lands behind the type. */
/* Hover motion is for things that can be clicked. Rows, steps, facts and detail boxes
   that lead nowhere stay still — moving under the pointer would promise a click. */
.plan::before,.pack::before,.col::before,.tile::before{
  content:'';position:absolute;left:0;right:0;top:0;height:3px;background:var(--red);z-index:4;
  transform:scaleX(0);transform-origin:left;transition:transform .45s cubic-bezier(.2,.7,.3,1)}
.plan:hover::before,.pack:hover::before,.col:hover::before,.tile:hover::before{transform:scaleX(1)}
/* every card the pointer can rest on carries the same light — life.js moves it */
.plan::after,.pack::after,.band::after,.col::after,.coach::after{
  content:'';position:absolute;inset:0;z-index:-1;pointer-events:none;opacity:0;
  background:radial-gradient(380px circle at var(--mx,50%) var(--my,0%),rgba(213,47,46,.09),transparent 65%);
  transition:opacity .4s}
.plan:hover::after,.pack:hover::after,.band:hover::after,.col:hover::after,.coach:hover::after{opacity:1}
.plan .price .n{display:inline-block;transform-origin:0 85%;transition:transform .55s var(--spring)}
.plan:hover .price .n{transform:scale(1.05)}
.plan:hover li .tick{animation:tick-pop .55s var(--spring) both}
.plan:hover li:nth-child(2) .tick{animation-delay:.06s}
.plan:hover li:nth-child(3) .tick{animation-delay:.12s}
.plan:hover li:nth-child(4) .tick{animation-delay:.18s}
@keyframes tick-pop{0%{transform:scale(1)}45%{transform:scale(1.55)}100%{transform:scale(1)}}
.plan:hover .btn-line,.pack:hover .btn-line,.col:hover .btn-line{background:var(--red);border-color:var(--red);color:var(--white)}
/* on arrival the list fills in line by line, after the card itself has landed */
.js .plan.rv li,.js .band.rv li{opacity:0}
.js .plan.rv.in li,.js .band.rv.in li{animation:row-in .55s cubic-bezier(.2,.7,.3,1) calc(var(--d,0ms) + 260ms) both}
.js .plan.rv.in li:nth-child(2),.js .band.rv.in li:nth-child(2){animation-delay:calc(var(--d,0ms) + 330ms)}
.js .plan.rv.in li:nth-child(3),.js .band.rv.in li:nth-child(3){animation-delay:calc(var(--d,0ms) + 400ms)}
.js .plan.rv.in li:nth-child(4),.js .band.rv.in li:nth-child(4){animation-delay:calc(var(--d,0ms) + 470ms)}
.js .band.rv.in li:nth-child(5){animation-delay:calc(var(--d,0ms) + 540ms)}
@keyframes row-in{from{opacity:0;translate:-10px 0}to{opacity:1;translate:none}}
/* cards life.js has made clickable all over (see section 7 there) */
.hit{cursor:pointer}
.plan-top{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:24px}
.plan .tier{font-size:var(--fs-label);letter-spacing:.19em;text-transform:uppercase;color:var(--grey-dark);font-weight:500}
.pick{background:var(--red);color:var(--white);font-size:9.5px;font-weight:500;letter-spacing:.13em;
      text-transform:uppercase;padding:5px 10px;white-space:nowrap}
.price{display:flex;align-items:baseline;gap:8px;margin:20px 0 4px}
.price .n{font-size:clamp(46px,4.4vw,64px);font-weight:500;letter-spacing:-.045em;line-height:.9;
          font-variant-numeric:tabular-nums}
.price .per{font-size:12px;color:var(--grey-dark);font-weight:400;letter-spacing:.1em;text-transform:uppercase}
.was{font-size:12.5px;color:var(--grey-dark);letter-spacing:.04em;margin-bottom:26px}
.plan ul{list-style:none;margin:0 0 30px;padding:0;flex:1}
.plan li{font-size:14px;color:var(--grey-dark);font-weight:300;padding:10px 0;
         border-top:1px solid var(--rule-soft);display:flex;gap:11px;align-items:flex-start}
.plan li:first-child{border-top:0}
.plan li .tick{color:var(--red);flex:none;font-size:12px;line-height:1.55}
.plan .btn{width:100%}
.fineprint{margin-top:30px;font-size:13px;line-height:1.75;color:var(--grey-dark);letter-spacing:.01em}

/* ---- split ---------------------------------------------------------------- */
.split{display:grid;grid-template-columns:1.02fr .98fr;gap:clamp(36px,5vw,84px);align-items:center}
/* The tagline graphic was trimmed to its ink and its ground matched to the section on
   2026-08-14, so it has no edge at all — the words read as part of the page instead of a
   picture in a frame. Its ground is baked into the PNG, so it only works on the ground it
   was made for. Original: design\archive\2026-08-14\asset-backup\ */
/* direct child only — the about and camps panels put a photo inside a .duo with a fixed
   aspect ratio, and those need to keep .duo's cover behaviour rather than height:auto */
.split-img > img{display:block;width:100%;height:auto}
.checks{list-style:none;margin:28px 0 34px;padding:0}
.checks li{display:flex;gap:14px;padding:14px 0;font-size:15.5px;color:var(--grey-dark);font-weight:300;
           border-top:1px solid var(--rule-soft)}
.checks li:first-child{border-top:0}
.checks .tick{color:var(--red);flex:none}

/* ---- why: one set of lines -------------------------------------------------- */
/* Home only, and the most rigidly aligned block on the site — deliberately. Every rule
   here runs the full content width and every column holds top to bottom, so there is
   never a question of whether two lines agree: there is only one line.

   It replaced a two-column split. That version put both columns on a shared grid row so
   their two hairlines could not drift apart, which worked, but it was machinery in
   service of an alignment that a single full-width rule gives away for free.

   The header's columns are 1.25fr/.75fr rather than .split's even ratio because at that
   ratio the graphic's height lands within a few px of the heading block's, so both cells
   reach the rule together instead of one leaving a hole above it. */
.why-head{display:grid;grid-template-columns:1.25fr .75fr;gap:clamp(36px,5vw,84px);
          align-items:start;margin-bottom:clamp(46px,4.6vw,72px)}

/* The box score. Three columns, five rows, one rule per row. The numbers were the four
   facts the parked band under the hero used to carry; they read better as the evidence
   under a claim than as the first thing on the page, and the Google rating is the last
   row rather than a separate device because it is the same kind of fact. */
.facts{border-top:1px solid var(--rule)}
.fact{display:grid;grid-template-columns:clamp(70px,6vw,96px) clamp(128px,12vw,176px) 1fr;
      gap:clamp(16px,2vw,30px);align-items:baseline;
      padding:clamp(20px,2vw,28px) 0;border-bottom:1px solid var(--rule)}
.fact-n{font-size:clamp(28px,2.6vw,40px);font-weight:500;line-height:1;letter-spacing:-.045em;
        font-variant-numeric:tabular-nums}
.fact-k{font-size:var(--fs-label);font-weight:500;letter-spacing:.17em;text-transform:uppercase;
        color:var(--red)}
.fact-t{font-size:15.5px;line-height:1.62;color:var(--grey-dark);font-weight:300}
.fact-t strong{color:var(--ink);font-weight:500;font-variant-numeric:tabular-nums}
/* the stars ride at the head of the last row's sentence instead of taking a cell of their
   own, so that row keeps the same height and the same three columns as the four above it */
.fact-t .stars{display:inline-block;vertical-align:-2px;margin-right:11px}

/* the sentence and the button share the last line: the button's right edge lands on the
   content edge, which is where the rules and the graphic end too */
.why-foot{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;
          gap:22px 40px;padding-top:clamp(26px,2.6vw,38px)}
/* no measure cap: the sentence holds one line beside the button wherever there's room */
.why-foot .fineprint{margin-top:0}

@media (max-width:1050px){
  .why-head{grid-template-columns:1fr}
}
/* below this the three columns leave the sentence too narrow to read, so the number and
   its label keep their row and the sentence drops underneath them */
@media (max-width:760px){
  .fact{grid-template-columns:auto 1fr;gap:12px 18px}
  .fact-t{grid-column:1 / -1}
}

/* ---- stars ------------------------------------------------------------------- */
/* Drawn, not typed. U+2605 is not in the Poppins subset, so a character falls back to
   whatever the system has and changes shape per platform. */
.stars{display:block;color:var(--red);height:15px;width:auto}

/* ---- name, address, phone -------------------------------------------------- */
/* The address is set at h3 size rather than body because on the contact page it
   is the thing the visitor came for. font-style:normal is for the <address> —
   the UA italicises it, and this is a destination, not an aside. */
.nap{font-style:normal;margin:0 0 30px}
.nap .street{display:block;font-size:var(--fs-h3);font-weight:400;line-height:1.45;letter-spacing:-.012em}
.nap .sub{display:block;margin-top:12px;font-size:15px;color:var(--grey-dark);font-weight:300}
.nap a{border-bottom:1px solid var(--rule);transition:color .2s,border-color .2s}
.nap a:hover{color:var(--red);border-color:var(--red)}

/* ---- faq ------------------------------------------------------------------- */
/* Ruled rows rather than an accordion. A <details> would hide the answers from
   a glance and hide them from anyone who skims — and the answers are the whole
   point of the block, so nothing here collapses. */
.faq{border-top:1px solid var(--rule)}
.faq-q{padding:26px 2px;border-bottom:1px solid var(--rule)}
.faq-q h3{font-size:clamp(17px,1.3vw,21px);font-weight:400;letter-spacing:-.012em;margin-bottom:11px}
.faq-q p{font-size:15px;line-height:1.68;color:var(--grey-dark);font-weight:300;max-width:82ch}

/* ---- prose ----------------------------------------------------------------- */
/* Legal pages only. One measured column — nothing else on the site is a wall of
   text, so this is deliberately narrower than --content. */
.prose{max-width:76ch}
.prose h2{font-size:clamp(21px,1.7vw,27px);letter-spacing:-.015em;margin:46px 0 14px}
.prose h3{font-size:16px;font-weight:500;margin:26px 0 10px}
.prose p{font-size:15.5px;line-height:1.72;color:var(--grey-dark);font-weight:300;margin-bottom:16px}
.prose strong{color:var(--ink);font-weight:500}
.prose a{color:var(--red);border-bottom:1px solid var(--red-tint)}
.prose a:hover{border-color:var(--red)}
.prose .nap{margin-top:24px}

/* ---- closing -------------------------------------------------------------- */
.close-in{text-align:center;max-width:820px;margin-inline:auto}
.close-in .eyebrow{justify-content:center}
.close-in h2{margin-bottom:18px}
.close-in .cta-row{justify-content:center;margin-top:34px}

/* ---- footer --------------------------------------------------------------- */
.ft{background:var(--ink);color:var(--white);padding-block:64px 30px}
.ft-grid{display:grid;grid-template-columns:1.6fr 1fr 1fr 1fr;gap:36px}
.ft h4{font-size:var(--fs-label);letter-spacing:.19em;text-transform:uppercase;color:var(--grey-dark);
       font-weight:500;margin-bottom:17px}
/* font-style:normal is for the footer <address> — the UA italicises it, and the
   address is there for local search, not for emphasis */
.ft a,.ft .l{display:block;font-size:14px;color:var(--grey);font-weight:300;padding:5px 0;font-style:normal}
.ft a{transition:color .2s,transform .42s var(--spring)}
.ft a:hover{color:var(--red);transform:translateX(4px)}
.ft-logo img{width:158px;margin-bottom:18px}
.ft-logo svg{width:158px;height:auto;display:block;margin-bottom:18px}
.ft-bot{display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap;
        margin-top:48px;padding-top:22px;border-top:1px solid var(--rule-dark);
        font-size:12px;color:var(--grey-dark);letter-spacing:.03em}
.ft-bot a{display:inline;color:var(--grey-dark);padding:0}
/* at phone width two equal columns are ~150px, narrower than "Jennifer@Rockball.com" and
   "Pricing & memberships", which ran off the screen. The logo block and Contact take full
   rows; Train and Visit stay side by side */
@media (max-width:480px){
  .ft-grid{gap:36px 24px}
  .ft-logo,.ft-grid > div:last-child{grid-column:1 / -1}
}
.redbar{height:4px;background:var(--red)}


/* ---- life: motion, rhythm, time -------------------------------------------
   A page where nothing ever moves reads as abandoned. Everything below is
   opt-in via the `js` class, so with scripting off the page is simply static
   and complete rather than blank. All of it is disabled under
   prefers-reduced-motion. ------------------------------------------------- */

/* scroll reveal. staggered by --d so rows arrive in sequence, not as a block.
   It runs as an animation on `translate`, not a transition on `transform`. The transition
   version outranked every card's own transition and pinned transform to none once a card
   had arrived, so no revealed plan, pack, band or tile could lift on hover. On its own
   property and its own clock, the reveal leaves both alone. */
.js .rv{opacity:0;translate:0 22px}
.js .rv.in{animation:rv-in .7s cubic-bezier(.2,.7,.3,1) var(--d,0ms) both}
@keyframes rv-in{from{opacity:0;translate:0 22px}to{opacity:1;translate:none}}

/* the hero photograph drifts, very slowly. it should be felt, not noticed */
@keyframes drift{
  0%{transform:scale(1.06) translate3d(0,0,0)}
  50%{transform:scale(1.13) translate3d(-1.4%,-1.1%,0)}
  100%{transform:scale(1.06) translate3d(0,0,0)}
}
.js .hero .duo img{animation:drift 38s ease-in-out infinite}

/* live status — reads from the clock, so the page differs by time of day. It sits at the end
   of the eyebrow, set in the same small caps and divided by a hairline instead of boxed in a
   pill: the meta line then reads as one line in one voice rather than a label with a badge
   stuck on the end. It appears in two places on opposite grounds — the white masthead and
   the dark hero — so it carries both treatments. */
.status{display:inline-flex;align-items:center;gap:9px;position:relative;padding-left:14px;
        font-size:var(--fs-label);font-weight:500;letter-spacing:.22em;text-transform:uppercase;
        color:var(--grey-dark)}
.status::before{content:'';position:absolute;left:0;top:50%;margin-top:-6px;
                width:1px;height:12px;background:var(--rule)}
.hero .status{color:var(--grey)}
.hero .status::before{background:rgba(254,254,254,.2)}
.status .dot{width:7px;height:7px;border-radius:50%;background:var(--grey-dark);flex:none}
.status.open .dot{background:#3ddc84;box-shadow:0 0 0 0 rgba(61,220,132,.55);animation:pulse 2.4s ease-out infinite}
.status.shut .dot{background:var(--grey-dark)}
/* on a phone the label and the status won't share a line — "Rancho Cordova, California" broke
   into three short rows beside it. So the status takes its own row, its dot under the label's
   first letter (the 38px bar plus the 14px gap), and the hairline, which only divides two
   things on one line, goes */
@media (max-width:480px){
  .eyebrow:has(.status){flex-wrap:wrap;row-gap:10px}
  .eyebrow .status{flex-basis:100%;padding-left:52px}
  .eyebrow .status::before{display:none}
}
@keyframes pulse{
  0%{box-shadow:0 0 0 0 rgba(61,220,132,.5)}
  70%{box-shadow:0 0 0 9px rgba(61,220,132,0)}
  100%{box-shadow:0 0 0 0 rgba(61,220,132,0)}
}

/* the ticker — the original site ran marquees; this is that idiom, kept */
.ticker{background:var(--ink);color:var(--white);overflow:hidden;
        border-block:1px solid var(--rule-dark);padding-block:15px}
.ticker-track{display:flex;width:max-content;animation:roll 46s linear infinite}
.js .ticker:hover .ticker-track{animation-play-state:paused}
.ticker-item{display:flex;align-items:center;gap:26px;padding-right:26px;
             font-size:12px;font-weight:500;letter-spacing:.2em;text-transform:uppercase;
             color:var(--grey);white-space:nowrap}
.ticker-item b{color:var(--white);font-weight:500}
.ticker-item .sep{color:var(--red);font-size:15px;line-height:0}
@keyframes roll{to{transform:translateX(-50%)}}

/* stat count-up needs a stable width so the row doesn't twitch while counting */
.score .n{font-variant-numeric:tabular-nums}

/* keyboard focus — a maintained site has these; most don't */
a:focus-visible,.btn:focus-visible{outline:2px solid var(--red);outline-offset:3px;border-radius:2px}

/* nav underline that draws in rather than just recolouring */
.nav a{position:relative}
.nav a::after{content:'';position:absolute;left:0;right:0;bottom:-6px;height:1.5px;background:var(--red);
              transform:scaleX(0);transform-origin:right;transition:transform .3s cubic-bezier(.2,.7,.3,1)}
.nav a:hover::after{transform:scaleX(1);transform-origin:left}

@media (prefers-reduced-motion:reduce){
  .js .rv,.js .rv.in{opacity:1;translate:none;animation:none}
  .js .hero .duo img{animation:none}
  .ticker-track{animation:none}
  .status.open .dot{animation:none}
  *{scroll-behavior:auto !important}
}

@media (max-width:1050px){
  .g4,.tiles{grid-template-columns:repeat(2,1fr)}
  .plans{grid-template-columns:1fr}
  .split{grid-template-columns:1fr}
  .score-in{grid-template-columns:repeat(2,1fr)}
  .nav{display:none}
  .ft-grid{grid-template-columns:1fr 1fr}
}

/* ===========================================================================
   v2 — interior pages. Everything above is shared with the home page; these
   are the components only the sub-pages use.
   =========================================================================== */

/* current page in the nav */
.nav a.on{opacity:1;color:var(--white)}
.nav a.on::after{transform:scaleX(1);transform-origin:left}

/* masthead — the compact opener every interior page starts with */
/* background-COLOR, not the shorthand: .ends draws its rule as a background-image and this
   rule wins on source order, so `background:` here would silently reset it to none. */
.mast{background-color:var(--white);color:var(--ink);padding-block:clamp(58px,6vw,96px) clamp(52px,5.4vw,86px)}
.mast-h{font-size:clamp(40px,6.2vw,92px);font-weight:500;line-height:.96;letter-spacing:-.036em;
        text-transform:uppercase;margin-bottom:22px}
.mast .lead{color:var(--grey-dark);max-width:660px;margin-bottom:32px}
.mast .cta-row{margin-top:4px}
/* the masthead plays in once on load: the rule draws, then the label, title, lead and
   buttons rise in quick succession. It is over in under a second — an entrance, not a show. */
.js .mast .eyebrow .bar,.js .hero-in .eyebrow .bar{transform-origin:left;animation:bar-in .7s cubic-bezier(.2,.7,.3,1) .05s both}
.js .mast .eyebrow .tx,.js .hero-in .eyebrow .tx{animation:mast-in .7s cubic-bezier(.2,.7,.3,1) .12s both}
.js .mast .status,.js .hero-in .status{animation:mast-in .7s cubic-bezier(.2,.7,.3,1) .22s both}
.js .mast-h,.js .hero-in .display{animation:mast-in .85s cubic-bezier(.2,.7,.3,1) .14s both}
.js .mast .lead,.js .hero-in .lead{animation:mast-in .8s cubic-bezier(.2,.7,.3,1) .26s both}
.js .mast .cta-row,.js .hero-in .cta-row{animation:mast-in .8s cubic-bezier(.2,.7,.3,1) .36s both}
@keyframes mast-in{from{opacity:0;translate:0 18px}to{opacity:1;translate:none}}
@keyframes bar-in{from{transform:scaleX(0)}to{transform:none}}

/* age-band detail blocks */
.lvls{margin-top:clamp(56px,5.4vw,90px)}
.lvl{padding-top:clamp(38px,3.6vw,58px);margin-top:clamp(38px,3.6vw,58px);border-top:1px solid var(--rule)}
.lvl:first-child{border-top:0;margin-top:0;padding-top:0}
.lvl-head{margin-bottom:26px}
.lvl-head .age{font-size:var(--fs-label);font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--red)}
.lvl-head h3{margin-top:8px;text-transform:uppercase;letter-spacing:-.02em;font-size:clamp(22px,2vw,30px)}
.lvl-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:1px;background:var(--rule)}
.lvl-item{background:var(--white);padding:22px 20px 24px}
.lvl-item h4{font-size:13px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;margin-bottom:9px}
.lvl-item p{font-size:13.5px;line-height:1.6;color:var(--grey-dark);font-weight:300}

/* The four blocks above were a long scroll — only one or two in view at a time. With script
   they become a slideshow: one band per slide, all sharing a single cell so the box is always
   as tall as the longest band and never jumps as it turns. The first version only faded and
   marked time with a hairline under the active card; nobody could tell it was a slideshow.
   So it is framed as one box, slides in from the side it came from, and carries the controls
   people already know — a dot per slide (the active one is the timer), arrows, play/pause and
   a count. The band cards above still work as buttons. Script is inline in pages.js. */

.show .lvls{display:grid;position:relative;overflow:hidden;margin-top:clamp(28px,2.8vw,44px);
  border:1px solid var(--rule);padding:0 clamp(20px,2.8vw,44px) clamp(24px,2.8vw,44px)}
.show .lvls:focus{outline:none}
.show .lvl{grid-area:2/1;margin-top:0;padding-top:0;border-top:0;
           opacity:0;visibility:hidden;transition:opacity .35s ease,visibility 0s .35s}
.show .lvl.on{opacity:1;visibility:visible;transition:none;
              animation:lvl-in .6s cubic-bezier(.2,.7,.3,1) both}
.show .lvl.on .lvl-item{animation:lvl-in .6s cubic-bezier(.2,.7,.3,1) both}
.show .lvl.on .lvl-item:nth-child(2){animation-delay:.05s}
.show .lvl.on .lvl-item:nth-child(3){animation-delay:.1s}
.show .lvl.on .lvl-item:nth-child(4){animation-delay:.15s}
.show .lvl.on .lvl-item:nth-child(5){animation-delay:.2s}
/* --dir is 1 going forward, -1 going back, so a slide arrives from the side it came from */
@keyframes lvl-in{from{opacity:0;translate:calc(var(--dir,1) * 56px) 0}to{opacity:1;translate:none}}
.show .band.on{background:var(--red-tint)}
.show .band.on::before{transform:scaleX(1)}

/* the control row: dots on the left, count and arrows on the right. It sits at the top of the
   box, straight under the cards, because at the bottom it was below the fold whenever the
   cards were in view — and controls nobody can see do not say "slideshow". */
.lvl-ctrl{display:none}
.show .lvl-ctrl{display:flex;align-items:center;gap:18px;grid-row:1;
  margin:0 calc(-1 * clamp(20px,2.8vw,44px)) clamp(24px,2.8vw,40px);
  padding:14px clamp(20px,2.8vw,44px);border-bottom:1px solid var(--rule)}
.lvl-dots{display:flex;align-items:center;gap:6px;margin-right:auto}
.lvl-dot{border:0;background:none;padding:10px 4px;cursor:pointer;line-height:0}
.lvl-dot span{display:block;position:relative;overflow:hidden;width:10px;height:10px;border-radius:10px;
  background:var(--grey-dark);opacity:.45;transition:width .45s var(--spring),opacity .25s}
.lvl-dot:hover span{opacity:.9}
/* the active dot stretches into a track and fills over the slide's 15 seconds */
.lvl-dot.on span{width:52px;opacity:1;background:rgba(149,149,151,.38)}
.lvl-dot span::after{content:'';position:absolute;inset:0;background:var(--red);
  transform:scaleX(0);transform-origin:left}
.lvl-dot.on span::after{transform:none}
.auto .lvl-dot.on span::after{animation:band-fill 15s linear both}
.held .lvl-dot.on span::after{animation-play-state:paused}
@keyframes band-fill{from{transform:scaleX(0)}to{transform:none}}
.lvl-count{font-size:var(--fs-label);font-weight:500;letter-spacing:.16em;color:var(--grey-dark);
  font-variant-numeric:tabular-nums}
.lvl-count b{color:var(--ink);font-weight:500}
.lvl-nav{display:flex;gap:8px}
.lvl-btn{width:44px;height:44px;display:grid;place-items:center;cursor:pointer;color:var(--ink);
  background:var(--white);border:1.5px solid var(--rule);border-radius:2px;padding:0;
  transition:border-color .2s,color .2s,background .2s,transform .42s var(--spring)}
.lvl-btn svg{width:16px;height:16px}
.lvl-btn:hover{border-color:var(--red);color:var(--red);background:var(--red-tint);transform:translateY(-2px)}
.lvl-btn:active{transform:scale(.94);transition-duration:.08s}
.lvl-btn:focus-visible,.lvl-dot:focus-visible{outline:2px solid var(--red);outline-offset:2px}
.lvl-btn .i-play{display:none}
.stopped .lvl-btn .i-play{display:block}
.stopped .lvl-btn .i-pause{display:none}

/* on a phone the slide already lists every skill in full, so the cards drop theirs and stay
   a compact 2x2 of age and name. Only with script: without it the list is all a card has. */
@media (max-width:760px){
  .show .band{padding:22px 18px 20px}
  .show .band ul{display:none}
  .show .band h3{margin-bottom:14px}
  .show .band .go{margin-top:auto}
  .show .lvl-ctrl{gap:10px}
  .lvl-count{display:none}
  .lvl-dot.on span{width:32px}
}
@media (prefers-reduced-motion:reduce){
  .show .lvl.on,.show .lvl.on .lvl-item{animation:none}
}

/* three-column text blocks */
.cols3{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--rule)}
.col{background:var(--white);padding:32px 28px 34px;display:flex;flex-direction:column;position:relative;z-index:1;
     transition:background .3s,transform .55s var(--spring),box-shadow .45s ease}
.col:hover{background:var(--red-tint);transform:translateY(-9px);z-index:2;box-shadow:0 26px 46px -28px rgba(12,11,14,.45)}
.col h3{text-transform:uppercase;letter-spacing:-.018em;margin-bottom:14px}
.col .lead{flex:1;font-size:15px}
.meta-line{font-size:12px;font-weight:500;letter-spacing:.13em;text-transform:uppercase;color:var(--red);margin:18px 0}

/* session packages */
.packs{display:grid;grid-template-columns:repeat(2,1fr);gap:1px;background:var(--rule)}
.pack{background:var(--white);padding:34px 30px 36px;display:flex;flex-direction:column;
      transition:background .3s,transform .55s var(--spring),box-shadow .45s ease;position:relative;z-index:1;
      will-change:transform /* same reason as .plan */}
.pack:hover{background:var(--red-tint);transform:translateY(-9px);z-index:2;box-shadow:0 26px 46px -28px rgba(12,11,14,.45)}
.pack.feat::before{content:'';position:absolute;left:0;right:0;top:0;height:3px;background:var(--red)}
.pack .tier{font-size:var(--fs-label);letter-spacing:.19em;text-transform:uppercase;color:var(--grey-dark);font-weight:500}
.pack.feat .tier{color:var(--red)}
table.rate{width:100%;border-collapse:collapse;margin:8px 0 26px}
table.rate td{padding:13px 0;border-top:1px solid var(--rule-soft);font-size:15px;font-weight:300;color:var(--grey-dark)}
table.rate tr:first-child td{border-top:0}
table.rate .pr{text-align:right;font-weight:500;color:var(--ink);font-variant-numeric:tabular-nums;font-size:19px}
table.rate .hr{text-align:right;width:5.6em;font-size:12.5px;color:var(--grey-dark);letter-spacing:.04em}
.js .pack.rv tr{opacity:0}
.js .pack.rv.in tr{animation:row-in .55s cubic-bezier(.2,.7,.3,1) calc(var(--d,0ms) + 240ms) both}
.js .pack.rv.in tr:nth-child(2){animation-delay:calc(var(--d,0ms) + 320ms)}
.js .pack.rv.in tr:nth-child(3){animation-delay:calc(var(--d,0ms) + 400ms)}

/* the live Mindbody schedule
   The widget is a cross-origin iframe, so nothing inside it can be styled. Its content sits
   in a 1168px column with at least 16px of gutter either side, and its first row is
   "My Account" 16px from the top, with its own "Book a session" heading below. So the frame
   is pulled out by its gutter and up onto the eyebrow's row: My Account shares the eyebrow's
   line, and the widget's heading reads as the h2 under it, flush with the page's left edge.
   Measured 2026-10-16 — if Mindbody changes the widget, re-measure. The widget script
   injects its own .mindbody-widget{width:100%} after this sheet, hence the #schedule scope. */
#schedule .eyebrow{position:relative;z-index:1;width:fit-content}
#schedule .mindbody-widget{--g:min(16px, (100vw - var(--content)) / 2);
  width:min(calc(100% + 2 * var(--g)), 1200px);margin:-56px 0 0 calc(-1 * var(--g))}
#schedule .mindbody-widget iframe{display:block}

/* opening hours */
.hours{border-top:1px solid var(--rule)}
.hrow{display:flex;justify-content:space-between;gap:20px;padding:17px 2px;
      border-bottom:1px solid var(--rule);font-size:16px}
.hrow .d{font-weight:500}
.hrow .t{color:var(--grey-dark);font-weight:300;font-variant-numeric:tabular-nums}
/* today's row is marked by the life script */
.hrow.today{background:var(--red-tint)}
.hrow.today .d{color:var(--red)}
.hrow.today .d::after{content:' — today';font-size:11px;font-weight:500;letter-spacing:.14em;text-transform:uppercase}

/* numbered steps */
.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--rule)}
.step{background:var(--white);padding:32px 28px 36px}
.step .sn{font-size:clamp(30px,3vw,44px);font-weight:500;letter-spacing:-.04em;color:var(--red);
          line-height:1;margin-bottom:16px;font-variant-numeric:tabular-nums}
.step h3{text-transform:uppercase;letter-spacing:-.018em;margin-bottom:11px}
.step .lead{font-size:15px}

/* trainers */
.coaches{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--rule)}
/* The 1px gaps are the grid ground showing through, so a short last row would show a grey
   hole where a card is missing. Five coaches as of 2026-10-16 left one. The filler spans to
   the row end, and only exists when the row is actually short. */
@media (min-width:1051px){
  .coaches:has(> .coach:last-child:nth-child(3n+1))::after,
  .coaches:has(> .coach:last-child:nth-child(3n+2))::after{content:'';background:var(--white);grid-column:auto / -1}
}
.coach{background:var(--white);display:flex;flex-direction:column;position:relative;z-index:1;
       transition:background .3s}
/* The card itself does not move — the coach does. See the note on the colour
   layer below: it is the masked subject that scales, so the player steps forward
   out of a wall that stays put. */
.coach:hover{background:var(--red-tint);z-index:2}
.coach-img{aspect-ratio:1/1}
.coach-b{padding:26px 24px 30px;flex:1;display:flex;flex-direction:column}
.coach h3{text-transform:uppercase;letter-spacing:-.018em;margin-bottom:8px}
.coach .spec{font-size:11px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--red);
             margin-bottom:15px;line-height:1.5}
/* two lines reserved whether it needs them or not: one specialty list that wraps and one
   that doesn't would otherwise start the bios at different heights across a row */
@media (min-width:1051px){ .coach .spec{min-height:3em} }
.coach .yrs,.bio .yrs{font-size:11px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;
                      color:var(--grey-dark);margin:-7px 0 15px;line-height:1.5}
.bio .yrs{margin-bottom:18px}
.coach p{font-size:14px;line-height:1.68;color:var(--grey-dark);font-weight:300}

/* The age-band hover, borrowed: a red rule that draws in from the left, and an
   arrow that slides. z-index because the bar sits over the portrait — .duo is
   positioned too, and later in the DOM, so without it the photo paints on top.
   The card is not an <a>: a 60-word bio makes terrible link text, so the arrow stays
   the link, and life.js forwards a click anywhere on the card to it. */
.coach::before{content:'';position:absolute;left:0;right:0;top:0;height:3px;background:var(--red);z-index:3;
               transform:scaleX(0);transform-origin:left;transition:transform .45s cubic-bezier(.2,.7,.3,1)}
.coach:hover::before{transform:scaleX(1)}
.coach .go{margin-top:auto;padding-top:20px;font-size:11px;font-weight:500;letter-spacing:.13em;
           text-transform:uppercase;color:var(--red);display:flex;gap:8px;align-items:center;align-self:flex-start}

/* Colour returns to the coach and not to the wall.
   CSS cannot tell a person from a breeze-block, so this is not a cutout: it is the
   same photograph laid over itself in colour and masked to an ellipse where the
   subject stands. Every portrait is the same shot — one person, centred, torso up,
   plain wall — so one ellipse fits all six.
   `--shot` is set per card in the markup, since the URL differs. A true
   subject-only treatment needs cutout PNGs with alpha, one per coach.

   The same layer is what grows on hover. Scaling the card scaled the wall with
   it; scaling this one scales only what the mask lets through, so the coach steps
   forward and the wall behind stays exactly where it was. Origin sits low because
   the players are cut off at the bottom of the frame — growing from the middle
   pushed their heads up and out.

   ONE PLAYER ON SCREEN, ALWAYS. A single scaling layer could not do that. Anywhere
   its mask went soft, the un-scaled photograph underneath showed through, and since
   that photograph is the same person a size smaller, a soft edge across a cap or a
   shoulder read as two players standing one behind the other. Widening the mask
   until it cleared him fixed the doubling and put the whole wall in colour.

   So there are two twins over the photograph, and they scale in lockstep:

     ::before  grey, treated exactly like the photograph under it, masked wide, and
               opaque at all times — at rest it is invisible because it is pixel-for-
               pixel what it covers. It is the layer that actually hides the still
               copy, which is why nothing can ghost through at any point in the move.
     ::after   colour, masked tight to the player, fading in on hover. It only ever
               sits over ::before — same image, same scale — so its soft edge blends
               colour into grey rather than one player into another.

   Net: ::before decides what MOVES (generous, out into flat wall where a 6% shift
   cannot be seen), ::after decides what is COLOURED (tight, the player only). The
   grey filter below must stay identical to `.duo img`'s or the wide mask's edge
   shows up as a step in tone. */
.coach-img:not(.nophoto)::before,
.coach-img:not(.nophoto)::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  background-image:var(--shot);background-size:cover;background-position:center 16%;
  transform:scale(1);transform-origin:50% 88%;
  /* long and gentle, and no spring — the overshoot bounced the player against a
     wall that cannot bounce with him */
  transition:opacity .34s cubic-bezier(.4,0,.2,1),
             transform .95s cubic-bezier(.19,.63,.24,1);
  will-change:transform;
}
.coach-img:not(.nophoto)::before{
  filter:grayscale(1) contrast(1.14) brightness(.96);
  -webkit-mask-image:radial-gradient(66% 88% at 50% 55%, #000 84%, transparent 100%);
          mask-image:radial-gradient(66% 88% at 50% 55%, #000 84%, transparent 100%);
}
.coach-img:not(.nophoto)::after{
  filter:contrast(1.02) saturate(1.06);
  -webkit-mask-image:radial-gradient(50% 70% at 49% 50%, #000 58%, transparent 100%);
          mask-image:radial-gradient(50% 70% at 49% 50%, #000 58%, transparent 100%);
  opacity:0;
}
.coach:hover .coach-img::before,
.coach:hover .coach-img::after{transform:scale(1.06)}
.coach:hover .coach-img::after{opacity:1}

@media (max-width:1050px){
  .cols3,.steps,.coaches{grid-template-columns:1fr}
  .packs{grid-template-columns:1fr}
}

/* The trainer profile — a card's full bio, centred. The card clamps its bio only once the
   script has said the dialog can open (.bios), so without it every bio reads in full on
   the card. In the dialog the coach is in colour: it is the hover state, held. */
.coach-bio p + p,.bio-tx p + p{margin-top:14px}
.bios .coach-bio p:not(:first-child){display:none}
.bios .coach-bio p{display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}
.bio{padding:0;border:0;border-top:3px solid var(--red);background:var(--white);color:var(--ink);
     width:min(940px, calc(100vw - 32px));max-height:calc(100dvh - 32px);overflow:hidden}
.bio::backdrop{background:rgba(12,11,14,.74)}
.bio[open]{animation:bio-in .42s cubic-bezier(.2,.7,.3,1)}
@keyframes bio-in{from{opacity:0;transform:translateY(14px) scale(.985)}}
html:has(.bio[open]){overflow:hidden}
/* the dialog takes focus itself on open, so no button greets a mouse user with a ring */
.bio:focus{outline:none}
/* ONE SIZE FOR EVERY TRAINER, and the photo decides it. The headshots are 500x549, so the
   photo column keeps that shape — never stretched into a strip, never upscaled past its
   pixels — and the box is exactly as tall as the photo. The text column is taken out of
   the row's sizing (height:0, min-height:100%), so a long bio cannot grow the box: it
   scrolls, fading at the bottom while there is more, and the button is pinned below it,
   in the same place on every profile. */
.bio-in{display:grid;grid-template-columns:min(460px, 49%) minmax(0,1fr)}
.bio-img{position:relative;background:var(--ink);aspect-ratio:500/549}
.bio-img img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center 16%}
.bio-b{height:0;min-height:100%;display:flex;flex-direction:column}
.bio-scroll{flex:1;min-height:0;overflow-y:auto;padding:clamp(28px, 3.4vw, 46px) clamp(26px, 3.4vw, 46px) 18px}
.bio-scroll.more{-webkit-mask-image:linear-gradient(#000 calc(100% - 64px), transparent);
                         mask-image:linear-gradient(#000 calc(100% - 64px), transparent)}
.bio-cta{flex:none;padding:18px clamp(26px, 3.4vw, 46px) clamp(26px, 3vw, 38px)}
.bio h3{text-transform:uppercase;letter-spacing:-.018em;margin-bottom:8px;padding-right:36px}
.bio .spec{font-size:11px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:var(--red);
           margin-bottom:18px;line-height:1.5}
.bio .spec:empty,.bio .yrs:empty{display:none}
/* the no-photo coach gets the same monogram tile in the profile as on his card */
.bio-img.nophoto{background:var(--surface)}
.bio-img.nophoto img{inset:auto;top:50%;left:50%;width:38%;height:auto;transform:translate(-50%,-50%);
                     object-fit:contain;opacity:.13;filter:invert(1)}
.bio-tx{font-size:15px;line-height:1.75;color:var(--grey-dark);font-weight:300}
.bio-x{position:absolute;top:12px;right:12px;z-index:1;width:40px;height:40px;display:grid;place-items:center;
       border:0;background:var(--white);color:var(--ink);font-family:inherit;font-size:30px;font-weight:300;line-height:1;cursor:pointer;
       transition:color .16s}
.bio-x:hover{color:var(--red)}
@media (max-width:720px){
  /* stacked: the box fills the screen, the photo is a fixed band on top, the text scrolls
     between it and the pinned button */
  .bio{height:calc(100dvh - 32px)}
  .bio-in{height:100%;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}
  .bio-img{aspect-ratio:5/4}
  .bio-b{height:auto;min-height:0}
}
@media (prefers-reduced-motion:reduce){ .bio[open]{animation:none} }

/* coach portraits are 500x549, so a 1:1 crop takes the top of the head off
   unless the focal point is pulled up */
.coach-img img{object-position:center 16%}

/* A coach with no photograph on file — assets/coach-nophoto.png is the
   Rockball monogram, not a portrait. Rather than present a placeholder as a
   headshot, the card says so. Give that coach a portrait in pages.js and drop `nophoto`. */
.coach-img.nophoto{position:relative;aspect-ratio:1/1;background:var(--surface);
                   display:grid;place-items:center;overflow:hidden}
.coach-img.nophoto img{width:44%;height:auto;object-fit:contain;opacity:.13;filter:invert(1);
                       transition:transform .6s var(--spring)}
/* no photograph means no subject to grow, so the monogram takes the move — her
   card answers the pointer like the other five */
.coach:hover .coach-img.nophoto img{transform:scale(1.06)}
.coach-img.nophoto .np{position:absolute;bottom:16px;left:0;right:0;text-align:center;
                       font-size:10px;font-weight:500;letter-spacing:.18em;text-transform:uppercase;
                       color:var(--grey-dark)}

/* ---- plan finder ------------------------------------------------------------ */
/* Pricing only. A slider is useless without script, so with script off the whole
   block steps aside and the page reads exactly as it did before it existed. The
   result side borrows the plan card's tier/price/was set, so the recommendation
   looks like the card it points at further down. */
html:not(.js) #finder{display:none}
#finder{scroll-margin-top:76px}
/* The box under this head runs the full content width, so the standard 760px head sat
   over it as a narrow stack. Wider, with the title on one line, the two weigh the same.
   1140, not 1080: at the lead's top size (19.5px) the sentence needs 1121px, and at 1080
   "least." dropped to a line of its own. */
#finder .head{max-width:1140px}
#finder .head h2{text-wrap:balance}
.finder{border:1px solid var(--rule);border-top:3px solid var(--red);
        display:grid;grid-template-columns:1.1fr .9fr}
.fd-ctl{padding:clamp(32px,3.6vw,54px);border-right:1px solid var(--rule)}
.fd-out{padding:clamp(32px,3.6vw,54px);display:flex;flex-direction:column}
.fd-out .tier{font-size:var(--fs-label);letter-spacing:.19em;text-transform:uppercase;color:var(--grey-dark);font-weight:500}
.fd-out .was{margin-bottom:14px}

/* The toggle is one red pill that slides between the two options on the spring,
   rather than two buttons that swap fills — the motion says "same question, other
   answer". Its width and offset are measured from the pressed button in script. */
.fd-seg{position:relative;display:inline-flex;border:1px solid var(--rule);border-radius:2px;margin-bottom:38px;padding:3px}
.fd-pill{position:absolute;top:3px;bottom:3px;left:0;width:0;background:var(--red);border-radius:1px;
         transition:transform .55s var(--spring),width .55s var(--spring)}
.fd-seg button{position:relative;z-index:1;font:500 11.5px/1 'Poppins',system-ui,sans-serif;letter-spacing:.13em;text-transform:uppercase;
               padding:12px 20px;background:none;border:0;color:var(--grey-dark);cursor:pointer;transition:color .25s}
.fd-seg button:hover{color:var(--red)}
.fd-seg button[aria-pressed="true"]{color:var(--white)}

.fd-q{display:block;font-size:var(--fs-label);font-weight:500;letter-spacing:.22em;text-transform:uppercase;color:var(--grey-dark)}
.fd-val{margin:10px 0 30px;font-size:var(--fs-h3);font-weight:500;letter-spacing:-.012em;color:var(--ink);
        overflow:hidden;padding-block:4px}
#fd-n{display:inline-block;font-size:var(--fs-stat);letter-spacing:-.045em;line-height:1;font-variant-numeric:tabular-nums;margin-right:4px}

/* The slider. The native range sits on top, invisible, so dragging, keyboard and
   screen readers all behave natively; everything you see is drawn underneath and
   positioned from --pos (0–1), which script sets on every frame. Its thumb is
   sized to match the drawn one so the pointer maps to the same pixel. --sq is
   the drag velocity, which squashes the knob in the direction of travel. */
.fd-track{--pos:.2;--sq:0;--T:30px;position:relative;height:52px;margin-top:34px}
.fd-rail{position:absolute;left:calc(var(--T)/2);right:calc(var(--T)/2);top:50%;height:6px;margin-top:-3px;
         border-radius:3px;background:var(--rule-soft);box-shadow:inset 0 0 0 1px var(--rule-soft)}
.fd-fill{position:absolute;inset:0 auto 0 0;width:calc(var(--pos)*100%);border-radius:3px;background:var(--red)}
.fd-dot{position:absolute;top:50%;width:10px;height:10px;margin:-5px 0 0 -5px;border-radius:50%;
        background:var(--white);border:2px solid var(--rule);
        left:calc(var(--T)/2 + var(--i)/5*(100% - var(--T)));
        transition:border-color .25s,transform .45s var(--spring)}
.fd-dot.on{border-color:var(--red)}
.fd-thumb{position:absolute;top:50%;width:var(--T);height:var(--T);margin:calc(var(--T)/-2) 0 0 calc(var(--T)/-2);
          left:calc(var(--T)/2 + var(--pos)*(100% - var(--T)));pointer-events:none;
          transition:transform .45s var(--spring)}
.fd-knob{position:absolute;inset:0;border-radius:50%;background:var(--white);border:3px solid var(--red);
         box-shadow:0 2px 10px rgba(12,11,14,.16),0 0 0 0 rgba(213,47,46,.14);
         transform:scale(calc(1 + var(--sq)),calc(1 - var(--sq)*.55));
         transition:box-shadow .35s var(--spring)}
/* the knob's centre is a red pip that swells while held — a small thing to look at mid-drag */
.fd-knob::after{content:'';position:absolute;left:50%;top:50%;width:8px;height:8px;margin:-4px 0 0 -4px;border-radius:50%;
                background:var(--red);transform:scale(0);transition:transform .4s var(--spring)}
.fd-ripple{position:absolute;inset:0;border-radius:50%;border:2px solid var(--red);opacity:0}
.fd-track:hover .fd-knob,#fd-range:focus-visible~.fd-thumb .fd-knob{box-shadow:0 2px 10px rgba(12,11,14,.16),0 0 0 9px rgba(213,47,46,.12)}
.fd-track.drag .fd-thumb{transform:scale(1.16)}
.fd-track.drag .fd-knob{box-shadow:0 6px 18px rgba(12,11,14,.2),0 0 0 12px rgba(213,47,46,.12)}
.fd-track.drag .fd-knob::after,#fd-range:focus-visible~.fd-thumb .fd-knob::after{transform:scale(1)}

/* the bubble above the knob — only while it's held or focused, so at rest the
   control stays as quiet as the rest of the page */
.fd-bub{position:absolute;left:50%;bottom:calc(100% + 13px);transform:translateX(-50%) translateY(8px) scale(.5);
        transform-origin:50% 100%;opacity:0;background:var(--ink);color:var(--white);
        font-size:12px;font-weight:500;letter-spacing:.06em;line-height:1;padding:7px 10px;border-radius:2px;white-space:nowrap;
        font-variant-numeric:tabular-nums;transition:transform .45s var(--spring),opacity .2s}
.fd-bub::after{content:'';position:absolute;left:50%;top:100%;margin-left:-5px;border:5px solid transparent;border-top-color:var(--ink)}
.fd-track.drag .fd-bub,#fd-range:focus-visible~.fd-thumb .fd-bub{opacity:1;transform:translateX(-50%) translateY(0) scale(.86)}

#fd-range{position:absolute;inset:0;z-index:2;width:100%;height:100%;margin:0;opacity:0;cursor:grab;
          -webkit-appearance:none;appearance:none;background:none;touch-action:pan-y}
#fd-range:active{cursor:grabbing}
#fd-range::-webkit-slider-thumb{-webkit-appearance:none;width:30px;height:30px}
#fd-range::-moz-range-thumb{width:30px;height:30px;border:0}

/* the stop labels sit on the same positions as the dots, and are clickable */
.fd-ticks{position:relative;height:22px;margin-top:6px;font-size:12.5px;font-weight:500;color:var(--grey);
          font-variant-numeric:tabular-nums;--T:30px}
.fd-ticks span{position:absolute;top:0;left:calc(var(--T)/2 + var(--i)/5*(100% - var(--T)));
               transform:translateX(-50%);cursor:pointer;padding:0 6px;
               transition:color .25s,transform .45s var(--spring)}
.fd-ticks span:hover{color:var(--ink)}
.fd-ticks span.on{color:var(--grey-dark)}
.fd-ticks span.cur{color:var(--red);transform:translateX(-50%) scale(1.25)}
/* same geometry as the tick labels, one line down; set like the .pick chip's type, in red */
.fd-pops{position:relative;height:14px;margin-top:4px;--T:30px}
.fd-pop{position:absolute;top:0;left:calc(var(--T)/2 + var(--i)/5*(100% - var(--T)));transform:translateX(-50%);
        white-space:nowrap;font-size:9.5px;font-weight:500;letter-spacing:.13em;text-transform:uppercase;color:var(--red)}
@media (prefers-reduced-motion:reduce){
  .fd-pill,.fd-thumb,.fd-knob,.fd-knob::after,.fd-dot,.fd-bub,.fd-ticks span{transition:none}
}

.fd-save{font-size:14px;line-height:1.7;color:var(--grey-dark);font-weight:300;flex:1;margin-bottom:26px}
.fd-cta{display:flex;align-items:center;gap:22px;flex-wrap:wrap}

@media (max-width:1050px){
  .finder{grid-template-columns:1fr}
  .fd-ctl{border-right:0;border-bottom:1px solid var(--rule)}
}

@media (prefers-reduced-motion:reduce){
  .btn-primary::after{display:none}
  .js .rv .eyebrow .bar,.js .rv .eyebrow .pick{opacity:1;transform:none;transition:none}
  .js .plan.rv li,.js .band.rv li,.js .pack.rv tr{opacity:1;animation:none}
  .plan:hover li .tick{animation:none}
  .js .mast .eyebrow .bar,.js .mast .eyebrow .tx,.js .mast .status,.js .mast-h,.js .mast .lead,.js .mast .cta-row,
  .js .hero-in .eyebrow .bar,.js .hero-in .eyebrow .tx,.js .hero-in .status,.js .hero-in .display,.js .hero-in .lead,
  .js .hero-in .cta-row{animation:none}
}

/* ---- season: a page wearing its event's colours ----------------------------------------
   A dated exception to BRAND.md's one red, asked for on 2026-10-15. Two layers:
   - the whole camps page swaps Rockball Red for the event's accent (--s-accent);
   - only the event's own section (.tg) takes the flyer's dark ground (--s-ground), so the flyer
     reads as if it were lifted off the page. A black page all the way down was tried first and
     felt too heavy; the rest of the page stays on white.
   build.js puts .season on <body> with both colours, and the page drops the class after the
   event, returning to Rockball Red. Colours only — everything works by remapping tokens. */
.season{
  --red:var(--s-accent);
  --surface-red:color-mix(in srgb,var(--s-accent) 14%,var(--s-ground));
  --banner-red:color-mix(in srgb,var(--s-accent) 22%,var(--s-ground));
  --red-tint:color-mix(in srgb,var(--s-accent) 9%,transparent);
}
.season .rb-ball{fill:var(--red)}

/* The event's section runs the full width of the screen as one dark band — the black stays
   confined to it, the rest of the page stays white. Two earlier versions were dropped: a panel
   held to --content read as a box, and feathering its sides into the white read as a grey
   smudge. The band's top and bottom are the two section lines, so its edges are where the page
   already says a section starts and ends; it replaces this section's own hairline. Edge to edge
   is otherwise reserved for the red bar under the footer (BRAND.md) — part of the same
   seasonal exception. */
.season .tg{
  --white:var(--s-ground);
  --ink:#F4EFEA;                                   /* the flyer's warm white */
  --grey:#8E8984;
  --grey-dark:#ABA59F;                             /* secondary type, lifted to read on black */
  --rule:rgba(244,239,234,.16);
  --rule-soft:rgba(244,239,234,.09);
  color:var(--ink);
  background:
    radial-gradient(620px 560px at 76% 50%,color-mix(in srgb,var(--s-accent) 18%,transparent),transparent 70%),
    var(--s-ground);
}
/* the flyer's button is dark type on orange; on hover the ground darkens, so the type lightens */
.season .tg .btn-primary:hover{color:var(--ink)}
/* The flyer keeps a clean edge. Feathering its edges into the band was tried and read as the
   image breaking up rather than fading on purpose; a poster with light spilling out from
   behind it reads as deliberate. The band's glow sits behind it, the shadow is the spill. */
.season .flyer img{
  box-shadow:0 0 0 1px rgba(244,239,234,.06),0 0 90px -10px color-mix(in srgb,var(--s-accent) 50%,transparent)}
