/*!***************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[2].use[1]!../../node_modules/next/dist/build/webpack/loaders/next-font-loader/index.js??ruleSet[1].rules[14].oneOf[2].use[2]!../../node_modules/next/font/google/target.css?{"path":"src/app/layout.tsx","import":"Anton","arguments":[{"weight":"400","subsets":["latin"],"variable":"--font-display"}],"variableName":"anton"} ***!
  \***************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
/* vietnamese */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/_next/static/media/6ced06489fd81a3f-s.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/_next/static/media/aa5f74293546f6d0-s.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/_next/static/media/62c97acc3aa63787-s.p.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: 'Anton Fallback';src: local("Arial");ascent-override: 129.70%;descent-override: 36.29%;line-gap-override: 0.00%;size-adjust: 90.69%
}.__className_ec6dc7 {font-family: 'Anton', 'Anton Fallback';font-weight: 400;font-style: normal
}.__variable_ec6dc7 {--font-display: 'Anton', 'Anton Fallback'
}

/*!*************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[2].use[1]!../../node_modules/next/dist/build/webpack/loaders/next-font-loader/index.js??ruleSet[1].rules[14].oneOf[2].use[2]!../../node_modules/next/font/google/target.css?{"path":"src/app/layout.tsx","import":"Archivo","arguments":[{"subsets":["latin"],"variable":"--font-body"}],"variableName":"archivo"} ***!
  \*************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
/* vietnamese */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 100%;
  font-display: swap;
  src: url(/_next/static/media/b06b356f834173cc-s.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 100%;
  font-display: swap;
  src: url(/_next/static/media/23d669af23d19c95-s.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 100%;
  font-display: swap;
  src: url(/_next/static/media/1a4aa50920b5315c-s.p.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: 'Archivo Fallback';src: local("Arial");ascent-override: 88.96%;descent-override: 21.28%;line-gap-override: 0.00%;size-adjust: 98.70%
}.__className_5afde0 {font-family: 'Archivo', 'Archivo Fallback';font-style: normal
}.__variable_5afde0 {--font-body: 'Archivo', 'Archivo Fallback'
}

/*!*************************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[10].use[2]!../../node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[14].oneOf[10].use[3]!./src/app/globals.css ***!
  \*************************************************************************************************************************************************************************************************************************************************************************/
/*
 * Isabella Fitness — global styles.
 *
 * MOBILE-FIRST: the primary reader is a member on a phone between classes.
 * The design language follows the committed bundles: Anton (display, set via
 * --font-display) for the poster headlines, Archivo (--font-body) for
 * everything else; near-black ink on white-dominant surfaces with ONE brand
 * accent that the GYM CONTEXT drives.
 *
 * LOCATION-AWARE BRANDING (client direction, 2026-08): `data-gym` on <html>
 * is the single source of truth for which gym the person is "at".
 *   data-gym="if" (default) — Isabella Fitness green + white
 *   data-gym="vf"           — Varno Fitness orange + white
 * The greens/oranges are the REAL site colors: #82C441 is the Elementor
 * global "primary" on isabellafitness.com; #EA580C is the dominant accent
 * on varnofitness.com. Everything brand-colored consumes the --brand-*
 * tokens; nothing outside the :root / [data-gym] blocks names a brand hex.
 *
 * The shell is the Manor pattern: sticky header, scrolling main, bottom tab
 * bar on a phone that becomes a top strip on a wide window.
 */

:root {
  /* -- fixed per-gym constants ------------------------------------------
     Location chips (the IF / VF badges on class cards) wear the gym they
     BELONG to, independent of the active context — that is what makes a
     VF row recognizable while browsing in IF green. */
  --gym-if: #82c441;
  --gym-if-text: #4c7a24; /* darkened for 4.5:1 on white */
  --gym-if-soft: #eef6e2;
  --gym-vf: #ea580c;
  --gym-vf-text: #c2410c; /* darkened for text on white */
  --gym-vf-soft: #fdeee3;

  /* -- context-driven brand (defaults = Isabella green) ----------------- */
  --brand: var(--gym-if);
  --brand-strong: #6aa62f; /* hover / pressed */
  --brand-text: var(--gym-if-text); /* accessible brand accent ON white */
  --brand-soft: var(--gym-if-soft); /* tint surface */
  --brand-ink: #14230a; /* text ON --brand: dark on green (white fails contrast) */

  /* -- neutrals: white-dominant surfaces -------------------------------- */
  --ink: #131417;
  --ink-soft: #3c4046;
  --ink-faint: #71767d;
  --paper: #fafaf8;
  --card: #ffffff;
  --line: #e7e6e2;

  /* -- status (semantic, NOT brand — deliberately fixed in both gyms) --- */
  --ok: #1f7a3f;
  --ok-soft: #e2f2e7;
  --warn: #a15c00;
  --warn-soft: #fdf1dc;
  --bad: #a51528;
  --bad-soft: #fbe5e8;

  /* light-on-dark ink for the few remaining dark chips (toast, owner badge) */
  --dark-ink: #f3f2ee;

  /* -- TYPE RAMP -------------------------------------------------------
     Measured off the approved bundles (member app + owner portal), which
     are drawn on a 402px device. Anton carries display; Archivo carries
     everything else and is set HEAVY (600–800) — that weight, plus the
     size jumps, is what makes the bundles read confident rather than
     generic. Sizes are rem so a reader's browser text-size still scales
     the whole app.

       bundle evidence (inline styles, verbatim sizes)
       40px  next-class "IF60", WOD headline
       38px  owner KPI numerals ("63", "312", "$48.2k")
       30px  screen titles ("Hi, Tapesh", "Book a Class", "Schedule")
       20px  book day-strip numerals
       16px  class name / class time / primary CTA
       13px  ledes + section eyebrows (w800, ls .08em, uppercase)
       11px  card eyebrows ("YOUR NEXT CLASS", w800, ls .12em)
       10px  tab-bar labels                                            */
  --t-hero: 2.5rem; /* 40px */
  --t-display: 2.375rem; /* 38px */
  --t-title: 1.875rem; /* 30px */
  --t-h2: 1.625rem; /* 26px */
  --t-h3: 1.5rem; /* 24px */
  --t-h4: 1.375rem; /* 22px */
  --t-h5: 1.25rem; /* 20px */
  --t-h6: 1.125rem; /* 18px */
  --t-lg: 1.0625rem; /* 17px */
  --t-body-lg: 1rem; /* 16px */
  --t-body: 0.9375rem; /* 15px */
  --t-sm: 0.875rem; /* 14px */
  --t-meta: 0.8125rem; /* 13px */
  --t-caption: 0.75rem; /* 12px */
  --t-micro: 0.6875rem; /* 11px */
  --t-nano: 0.625rem; /* 10px */

  /* -- SPACING RHYTHM ---------------------------------------------------
     The bundles' ladder. Screen margin is 20px (--s-8), a list card is
     padded 16px (--s-6), a block card 18px (--s-7), a hero card 22px
     (--s-9); sections are separated by 20–22px, cards within a list by
     8–10px. Nothing in the app should pick a spacing off this ladder. */
  --s-1: 0.25rem; /*  4px */
  --s-2: 0.5rem; /*  8px */
  --s-3: 0.625rem; /* 10px */
  --s-4: 0.75rem; /* 12px */
  --s-5: 0.875rem; /* 14px */
  --s-6: 1rem; /* 16px */
  --s-7: 1.125rem; /* 18px */
  --s-8: 1.25rem; /* 20px */
  --s-9: 1.375rem; /* 22px */
  --s-10: 1.5rem; /* 24px */
  --s-11: 1.75rem; /* 28px */
  --s-12: 2rem; /* 32px */

  /* -- THE TWO INSETS, DECIDED ONCE ------------------------------------
     Breathing room is one decision, not a per-component habit, so both
     halves of it are named here and every surface consumes the token.

     --gutter    the SCREEN margin: page background to content. 24px, up
                 from 20px, because at 375px the section eyebrows and the
                 title were reading as flush against the bezel.
     --card-pad  the CARD margin: card border to its own content. A card
                 inset from the viewport is only half the job — a row
                 whose value sits ON the card's edge still runs to the
                 edge of the screen, which is exactly how the owner list
                 rows ("312 check-ins", "ends Aug 14") were drawn.

     Two facts make the pair load-bearing: `.row-list` resets a <ul>'s
     browser padding, and a list card is `class="row-list card"` — so the
     reset was silently stripping the card's inset (same specificity, and
     it is declared later in this sheet). That is fixed at `.row-list.card`
     below, by specificity rather than by source order. */
  --gutter: 1.5rem; /* 24px */
  --card-pad: var(--s-7); /* 18px */

  /* Minimum comfortable touch target — every button, tab, chip and row
     control in the app clears this. */
  --tap: 2.75rem; /* 44px */

  --radius: 16px;
  --radius-small: 11px;
  --radius-hero: 20px;
  --shadow: 0 1px 3px rgb(19 20 23 / 0.08), 0 4px 16px rgb(19 20 23 / 0.06);
  --font-display-stack: var(--font-display), "Arial Narrow", Impact, sans-serif;
  --font-body-stack: var(--font-body), -apple-system, "Segoe UI", Roboto, sans-serif;

  /* Theme cross-fade when the gym toggle flips. Zeroed under
     prefers-reduced-motion below. */
  --theme-fade: 300ms;
}

[data-gym="vf"],
.shell[data-gym="vf"] {
  --brand: var(--gym-vf);
  --brand-strong: #c2410c;
  --brand-text: var(--gym-vf-text);
  --brand-soft: var(--gym-vf-soft);
  /* White on the Varno orange — the varnofitness.com button treatment. */
  --brand-ink: #ffffff;
}

/* INSIDE THE SIGNED-IN APP, THE SHELL DECIDES THE BRAND — restated here
   because :root carries the Isabella values and there was no `if` rule to
   win with, so an `if` shell nested in a `vf` <html> inherited orange.
   <html data-gym> is set by the root layout, which paints before it knows
   who is signed in and can only read the brand cookie; the shell knows the
   role and derives an owner's brand from the gym they actually selected. The
   nearer ancestor must therefore be able to override the outer one — for an
   owner it is the only correct answer. */
.shell[data-gym="if"] {
  --brand: var(--gym-if);
  --brand-strong: #6aa62f;
  --brand-text: var(--gym-if-text);
  --brand-soft: var(--gym-if-soft);
  --brand-ink: #14230a;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --theme-fade: 0ms;
  }
}

/* The brand-colored consumers cross-fade when data-gym flips. Scoped to a
   named list (not *) so hover states stay snappy elsewhere. */
body,
.btn,
.badge,
.avatar,
.tab,
.tab[aria-current="page"],
.week-strip a,
.pass-card,
.card--brand,
.class-card--hero,
.wod-block,
.wod-block__format,
.kpi__number,
.gym-toggle__opt,
.gym-chip__dot,
.shell__signout,
.persona {
  transition:
    background-color var(--theme-fade) ease,
    border-color var(--theme-fade) ease,
    color var(--theme-fade) ease;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-body-stack);
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  line-height: 1.5;
  /* The bundles never set Archivo at 400 — the lightest body weight in the
     whole member app is 600, names are 700, labels 800. 500 is the
     white-surface equivalent of that voice (600 on white reads as bold). */
  font-weight: 500;
}

/* NO UNDERLINE BY DEFAULT. The browser's default is right for a link inside a
   sentence and wrong for everything else this app links: cards, rows, tiles,
   footers, tabs. Leaving the default on meant every one of those had to turn
   it off by hand, and the few that forgot ("Renew or change terms →") read as
   body copy that had wandered out of a paragraph. Prose puts it back — see
   `.prose a`, the one place a link really is a word in a sentence. */
a {
  color: inherit;
  text-decoration: none;
}

/* ------------------------------------------------------------- go link */

/* "This takes you somewhere." Label plus a stroke arrow from the tab bar's
   icon family, replacing the literal `→` character the app used to set in
   running text. A glyph from the icon set holds its size and weight
   independently of the label; a `→` inherits the text font and drifts. */
.go {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--brand-text);
  font-weight: 800;
}

.go:hover,
.go:focus-visible {
  color: var(--brand-strong);
}

.go__glyph {
  width: 1rem;
  height: 1rem;
  flex: none;
  transition: transform 0.16s ease;
}

.go:hover .go__glyph {
  transform: translateX(2px);
}

/* The same arrow, inside a button. `.btn` is already an inline-flex row with a
   gap, so the glyph only needs a size. */
.btn__glyph {
  width: 0.9375rem;
  height: 0.9375rem;
  flex: none;
}

.go--sm {
  font-size: var(--t-caption);
}

.go--sm .go__glyph {
  width: 0.875rem;
  height: 0.875rem;
}

@media (prefers-reduced-motion: reduce) {
  .go__glyph {
    transition: none;
  }
  .go:hover .go__glyph {
    transform: none;
  }
}

/* Every interactive control clears the 44px floor. Declared once, on the
   families, rather than remembered per component. */
.btn,
.tab,
.week-strip a,
.rail-tab,
.term-card {
  min-height: var(--tap);
}

/* ------------------------------------------------------------------ front

   The public front door. Off-white paper, the running mark, the wordmark
   under it, one control, and the ticker across the lower third.

   WHY THIS REPLACED A WHOLE MARKETING HERO. What lived here was the client's
   WEBSITE home pressed into service as an app's front door — black nav,
   full-height dark hero, oversized two-line poster headline, subline, two
   CTAs, a reassurance line and two address cards. A website has to sell. This
   screen has one job, which is to let a member in, and every element that was
   not that job was competing with the logo for the first second of attention.

   MOTION CONTRACT: one sequence, in the order it was asked for — runner
   (0.1s) → wordmark (0.45s) → ticker (0.9s) → control (1.2s), each leg
   ~0.7s, transform and opacity only. Once the entrance settles, two things
   keep running: the runner's articulated 30fps cycle and the ticker.
   Everything is zeroed under prefers-reduced-motion at the end of
   this section; because every entrance uses `both`, removal leaves each
   element at its final state and the page simply appears complete. */

.front {
  /* The front door is ISABELLA'S — pinned to IF green even when a returning
     member's gym cookie has the app context on Varno. */
  --brand: var(--gym-if);
  --brand-strong: #6aa62f;
  --brand-ink: #14230a;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  /* Off-white, warmed a shade off --paper so the black mark sits on paper
     rather than on a screen. */
  background: #f6f5f1;
  color: var(--ink);
  /* The ticker runs past both edges. */
  overflow-x: hidden;
  position: relative;
  isolation: isolate;
}

/* -- the room the mark stands in ---------------------------------------

   Plain off-white read as unfinished rather than as calm, so the paper gets
   two layers of weather: slow house lights that breathe, and a drifting field
   of specks. Both are decoration and both are cheap — gradients on a pseudo
   element, animated on transform and opacity only. No DOM, no JavaScript, no
   pointer events, and they sit BELOW the content (z-index 0 against the
   stage's 1). */

@keyframes front-lights {
  from {
    opacity: 0.7;
    transform: translate3d(0, 0, 0) scale(1);
  }
  to {
    opacity: 1;
    transform: translate3d(0, -1.5%, 0) scale(1.06);
  }
}

.front::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(
      42rem 30rem at 18% 8%,
      color-mix(in srgb, var(--brand) 12%, transparent),
      transparent 72%
    ),
    radial-gradient(
      34rem 26rem at 86% 30%,
      color-mix(in srgb, var(--brand) 9%, transparent),
      transparent 72%
    ),
    radial-gradient(24rem 18rem at 50% 98%, rgb(19 20 23 / 0.05), transparent 70%);
  animation: front-lights 22s ease-in-out infinite alternate;
}

/* One 320px tile, repeated, travelling exactly one tile — so the loop has no
   seam to notice. */
@keyframes front-drift {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(0, -320px, 0);
  }
}

.front::after {
  content: "";
  position: absolute;
  inset: 0 0 -320px;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(2px 2px at 20% 30%, color-mix(in srgb, var(--brand) 55%, transparent), transparent 55%),
    radial-gradient(1.5px 1.5px at 70% 14%, rgb(19 20 23 / 0.16), transparent 55%),
    radial-gradient(2.5px 2.5px at 45% 68%, color-mix(in srgb, var(--brand) 38%, transparent), transparent 55%),
    radial-gradient(1.5px 1.5px at 88% 58%, rgb(19 20 23 / 0.12), transparent 55%),
    radial-gradient(2px 2px at 8% 86%, color-mix(in srgb, var(--brand) 32%, transparent), transparent 55%),
    radial-gradient(1.5px 1.5px at 58% 92%, rgb(19 20 23 / 0.13), transparent 55%);
  background-size: 320px 320px;
  animation: front-drift 44s linear infinite;
}

@keyframes front-rise {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes front-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* -- the stage: mark, wordmark, one control ---------------------------- */

.front__stage {
  position: relative;
  z-index: 1;
  flex: 1 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-10);
  /* The band above still owns the notch — it is the first thing in the flow,
     and the inset is folded into the offset that drops it down the page. */
  padding: var(--s-10) var(--gutter) var(--s-12);
  text-align: center;
}

/* `backwards`, NOT `both`, on the two pieces of the lockup. A fill of `both`
   keeps the keyframes' final transform applied for the life of the element at a
   priority that beats an inline style — so the FLIP in LandingGate.tsx, which
   slides the mark out of the panel's way, would compute the right distance and
   then be silently ignored. `backwards` holds the from-state before the
   animation starts and lets go at the end, which is all the entrance needed.
   The keyframes' to-state is already the base style (transform none, opacity
   1), so nothing changes on screen when the fill is dropped. */
.front__mark {
  display: flex;
  justify-content: center;
  animation: front-rise 0.7s cubic-bezier(0.22, 0.7, 0.3, 1) 0.1s backwards;
}

/* TWO LAYERS. The shield is the steady frame and the runner is placed inside
   it at the exact proportions it occupied in the original lockup, so the pair
   scales as one image at any width. A badge that bounces reads as a sticker;
   a runner moving inside a frame that holds still reads as a runner. */
.front__shield {
  position: relative;
  display: block;
  width: min(9.5rem, 44vw);
}

.front__shield-frame {
  position: relative;
  z-index: 2;
  display: block;
  width: 100%;
  height: auto;
}

/* MEASURED FROM THE ORIGINAL LOCKUP, NOT EYEBALLED. Placing the extracted
   runner back inside the extracted frame is a registration problem, and the
   answer is in the source art: in logo-isabella.png the shield's green box is
   106×115 at (9,30) and the runner sits at (40,37), which is 29.25% across,
   6.09% down and 52.83% wide. Template-matching the exported asset back into
   the original agrees at 100% overlap, so these three numbers are the lockup
   rather than a taste call, and the pair scales as one image at any width.

   `top` WAS 0, AND THAT IS THE BUG THAT PUT THE MAN OUTSIDE THE SHIELD. The
   note here used to claim the crop began at the shield's top edge with the
   green rail painting over it, so a lower value would expose a flat-topped
   head. That is not what the asset contains: row 0 of mark-runner.png is a
   13px rounded tip, the natural top of the raised fist, and the spans widen
   smoothly from there — there is no seam to hide. Zeroing it lifted the
   runner 6% of the shield's height and pushed his head through the frame. */
.front__runner {
  position: absolute;
  z-index: 1;
  left: 29.25%;
  top: 6.09%;
  width: 52.83%;
  height: auto;
  object-fit: contain;
}

.front__wordmark {
  display: block;
  width: min(17rem, 72vw);
  height: auto;
  animation: front-rise 0.7s cubic-bezier(0.22, 0.7, 0.3, 1) 0.45s backwards;
}

/* The one door. Solid brand, dark letters — the bundle's CTA, unchanged. */
/* THE TAP TARGET IS THE WHOLE VIEWPORT. Invisible and on top of everything
   except the panel and the departure, so a tap anywhere opens the way in. It
   carries the pointer cursor for the desktop case, where there is no such
   thing as "tap" and the only clue that the page is live is the cursor. */
.front__tap {
  position: fixed;
  inset: 0;
  z-index: 20;
  cursor: pointer;
}

/* The click-away, once the panel is open. Below the panel, above the rest. */
.front__dismiss {
  position: fixed;
  inset: 0;
  z-index: 20;
}

/* THE HINT IS A CAPTION, NOT A CONTROL. It began as a solid green pill and
   then as an outlined one, and both were wrong for the same reason: this door
   has one job, showing the mark, and a button is the loudest object you can
   put under it. Since the entire viewport is now the target, the words do not
   have to look like anything — they only have to say what a tap will do. So:
   no fill, no border, no pill. Small tracked-out type in the brand green,
   breathing very slightly so it reads as an invitation rather than a label.

   It keeps `--tap` of height even without a box, because it is still a real
   link for anyone arriving by keyboard or without JavaScript. */
.front__hint {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding: 0 var(--s-4);
  background: none;
  border: 0;
  color: var(--brand-text);
  font-size: var(--t-sm);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  text-decoration: none;
  animation:
    front-rise 0.7s cubic-bezier(0.22, 0.7, 0.3, 1) 1.2s both,
    front-invite 3.2s ease-in-out 2.2s infinite;
}

/* A slow breath, not a blink — and only opacity, so nothing about the layout
   moves. Starts after the entrance so the two never fight. */
@keyframes front-invite {
  0%,
  100% {
    opacity: 0.62;
  }
  50% {
    opacity: 1;
  }
}

.front__hint:hover,
.front__hint:focus-visible {
  color: var(--brand-strong);
  text-decoration: underline;
  text-underline-offset: 0.32em;
}

.front__locked {
  max-width: 24rem;
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: var(--radius-small);
  padding: var(--s-7);
  animation: front-rise 0.7s cubic-bezier(0.22, 0.7, 0.3, 1) 1.2s both;
}

.front__locked p {
  margin: 0.35rem 0 0;
  font-size: var(--t-body);
  color: var(--ink-soft);
}

/* -- the ticker -------------------------------------------------------- */

@keyframes front-marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

/* IT HANGS BELOW THE TOP EDGE, but only just.

   This offset has been wrong in both directions. Flush to the top (0) it read
   as browser chrome — an awning belonging to the phone rather than to the
   poster. So it went to a third of the screen, which fixed that and broke
   something worse: the band is the FIRST thing in the column, so every pixel
   here also pushes the stage below it, and the mark ended up sitting in the
   bottom half of the page (operator, 2026-08-12: "the logo is now too low and
   the banner is too low bring both togehter up more so the logo is centered").

   A band cannot be a third of the way down AND leave the mark centred; the two
   asks are the same pixels. What actually separated the band from the phone's
   own chrome was never the distance — it was having ANY paper above it at all.
   A few percent does that, and hands the rest of the screen back to the mark.

   Still clamped at both ends: on a short landscape phone the gap must not eat
   the stage, and on a tall desktop window it must not open a metre of empty
   paper before anything happens. */
.front__ticker {
  position: relative;
  z-index: 1;
  overflow: hidden;
  background: var(--brand);
  color: var(--brand-ink);
  margin-top: calc(env(safe-area-inset-top) + clamp(0.75rem, 4dvh, 2.5rem));
  padding: var(--s-6) 0;
  animation: front-fade 0.8s ease 0.9s both;
}

.front__ticker-track {
  display: flex;
  width: max-content;
  animation: front-marquee 26s linear infinite;
}

.front__ticker-run {
  display: flex;
  align-items: center;
  gap: var(--s-10);
  padding-right: var(--s-10);
  font-family: var(--font-display-stack);
  font-size: var(--t-h3);
  text-transform: uppercase;
  white-space: nowrap;
}

.front__ticker-star {
  opacity: 0.4;
}

.front__footer {
  position: relative;
  z-index: 1;
  text-align: center;
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
  padding: var(--s-6) var(--gutter) calc(var(--s-8) + env(safe-area-inset-bottom));
  animation: front-fade 0.8s ease 1.35s both;
}

/* -- the account picker ------------------------------------------------

   NOT A MODAL. It stands exactly where the CTA stood, in the same centred
   column, so opening it lifts the mark by the difference between a button and
   a panel and centres the pair again — no cover, no blur, no scrim over a
   front door whose whole job was to be looked at. */

/* IT RISES INTO PLACE RATHER THAN POPPING INTO IT. The scale went: a panel
   that grows as it fades is a notification, and this one is taking the hint's
   seat in the same column, so the only honest movement is upward travel. The
   distance is bigger than it was (14px read as a twitch at 0.4s) and the fade
   finishes early — at 55% — so the last third of the travel happens on a panel
   that is already solid, which is what makes it look like it is SLIDING rather
   than materialising. */
@keyframes front-panel-in {
  from {
    opacity: 0;
    transform: translateY(26px);
  }
  55% {
    opacity: 1;
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.front__panel {
  /* Above the click-away layer, so the panel's own buttons stay reachable. */
  position: relative;
  z-index: 21;
  width: min(23rem, 100%);
  margin-top: var(--s-2);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-9);
  text-align: left;
  box-shadow: 0 18px 44px rgb(12 13 10 / 0.09);
  /* 620ms, THE SAME AS THE MARK'S SLIDE (RISE_MS in LandingGate.tsx). The
     panel arriving and the logo getting out of its way are one gesture; two
     durations show up as the lockup settling before or after the panel, which
     reads as two things happening. */
  animation: front-panel-in 0.62s cubic-bezier(0.22, 0.7, 0.3, 1) backwards;
}

.front__panel-title {
  font-family: var(--font-display-stack);
  font-size: var(--t-h4);
  text-transform: uppercase;
  margin: 0;
}

.front__panel-lede {
  margin: 0.15rem 0 var(--s-8);
  font-size: var(--t-meta);
  color: var(--ink-faint);
}

.front__accounts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: var(--s-3);
  gap: var(--s-3);
}

.front__account {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-small);
  padding: var(--s-5) var(--s-6);
  min-height: var(--tap);
  justify-content: center;
  text-decoration: none;
  background: var(--card);
  text-align: left;
}

.front__account:hover {
  border-color: var(--brand);
  background: var(--brand-soft);
}

.front__account strong {
  font-size: var(--t-body);
}

.front__account span {
  font-size: var(--t-caption);
  color: var(--ink-faint);
}

.front__panel-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-top: var(--s-7);
}

.front__panel-alt {
  font-size: var(--t-meta);
  font-weight: 700;
  color: var(--brand-text);
  text-decoration: none;
}

.front__panel-close {
  border: 0;
  background: none;
  padding: 0;
  font-size: var(--t-meta);
  font-weight: 700;
  color: var(--ink-faint);
  cursor: pointer;
}

/* -- the departure: the shield's stars, falling ------------------------

   Picking an account rains the mark's OWN stars down the screen and lets a
   green wash close behind them. The redirect is issued while that cover is
   up, so the app is simply already there when this document goes away.
   Covering BEFORE navigating rather than after is deliberate — see the note
   in LandingGate.tsx. */

@keyframes front-star-fall {
  from {
    transform: translate3d(0, -14vh, 0) rotate(0deg);
    opacity: 0;
  }
  12% {
    opacity: 1;
  }
  to {
    transform: translate3d(0, 108vh, 0) rotate(var(--spin, 20deg));
    opacity: 1;
  }
}

@keyframes front-wash-in {
  from {
    opacity: 0;
    transform: translate3d(0, -100%, 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

/* ── THE DEPARTURE ────────────────────────────────────────────────────────
 *
 * WHAT WAS WRONG WITH IT (operator, 2026-08-12: "the post login animation now
 * is tooo long it holds at the solid green … instead of just abruptly landing
 * on to the home"). The old sequence covered the screen in green and then
 * STOPPED — a flat green field with nothing happening on it, held until the
 * redirect fired, at which point the dashboard appeared in one frame. Both
 * halves of that were wrong: dead air in the middle, and a hard cut at the
 * end. The green was also doing the one thing a loading screen must not do,
 * which is look identical whether the server answered in 40ms or 4 seconds.
 *
 * THE SHAPE NOW — one continuous move, no pauses, ending on the colour the
 * app itself is painted:
 *
 *   0.00  the shield's stars start raining
 *   0.04  the green wash sweeps down over the door
 *   0.34  covered — and `paper` snaps opaque UNDERNEATH the green, unseen,
 *         so that whatever happens next cannot expose the landing page
 *   0.34  LIVE flies in from the left
 *   0.38  BETTER flies in from the right; they meet centred
 *   0.68  they hold together on the green
 *   0.82  the green clears — and because the words are dark ink, they do not
 *         change at all: the lockup simply becomes the black-on-white board
 *   1.06  the words fade
 *   1.28  plain --paper, and the redirect goes out on that frame
 *
 * WHY IT ENDS ON PAPER RATHER THAN ON GREEN. The destination is a white app.
 * Handing over from a full-bleed green to a white dashboard is a cut no
 * amount of easing hides, whereas handing over from --paper to a page painted
 * --paper is invisible — the document swaps under a sheet the same colour as
 * the thing arriving. That is the whole reason the fade-out exists, and the
 * reason `paper` is a layer rather than just letting the wash clear.
 *
 * AND IT STILL DOES NOT GATE THE APP. The redirect is scheduled from the tick
 * the sequence starts, so a slow server extends the quiet paper beat at the
 * end rather than freezing a green one in the middle. */
.front__leave {
  position: fixed;
  inset: 0;
  z-index: 90;
  overflow: hidden;
  pointer-events: none;
}

/* The sheet the whole thing lands on — the APP's paper, not the front door's
   slightly warmer off-white, because this is the colour that has to match
   what arrives. It is invisible for the first third of a second, sitting
   under an opaque green, and only matters once that green clears. */
.front__leave-paper {
  position: absolute;
  inset: 0;
  background: var(--paper);
  animation: front-paper-on 1ms linear 0.34s both;
}

@keyframes front-paper-on {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.front__leave-wash {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    var(--brand) 0%,
    var(--brand) 62%,
    color-mix(in srgb, var(--brand) 86%, #0c0d0a) 100%
  );
  /* In fast (0.30s, down from 0.80s) because the cover is no longer the point
     of the animation — it is the setup for one. Out again on its own beat;
     the second animation only touches opacity, so the transform the first one
     landed is what stays. */
  animation:
    front-wash-in 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0.04s both,
    front-wash-out 0.24s ease 0.82s both;
}

@keyframes front-wash-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

/* HIS SIGN. Archivo at its heaviest, not Anton — the board he carries is set
   in a wide grotesque, and the condensed display face would be a different
   object wearing the same words. Sized off the viewport so it fills a phone
   the way the board fills your view when he holds it up. */
.front__leave-words {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-body-stack);
  font-weight: 800;
  /* Smaller than the two-word version was: "BEST HOUR" is half again as wide
     as "BETTER", and a line that has to shrink to fit stops looking like a
     painted sign and starts looking like text. */
  font-size: clamp(2.1rem, 12.5vw, 5.25rem);
  line-height: 0.95;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  /* 1.06s IS WHERE THE WASH FINISHES CLEARING (0.82 + 0.24), so the board
     starts fading the instant it is standing on white — no gap in which the
     screen is holding still with nothing happening.

     It had drifted to 1.14s, which put the end of this beat at 1.36s while
     COVER_MS in LandingGate.tsx still fired the redirect at 1.28s: the
     navigation landed 80ms before the words had finished clearing. The two
     numbers have to be read together, and the comment on COVER_MS spells out
     this same sum. */
  animation: front-words-out 0.22s ease 1.06s both;
}

/* Shrinks to the bar — the wider of the two lines — so that flush-left inside
   this box is the alignment the real sign has, while the box itself stays
   centred on the screen. */
.front__leave-board {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.06em;
  /* THE WHOLE SIGN ARRIVES AT ONCE. See the note on the three lines below. */
  animation: front-board-in 0.44s cubic-bezier(0.16, 0.84, 0.3, 1) 0.3s backwards;
}

@keyframes front-board-in {
  from {
    opacity: 0;
    transform: translate3d(0, 0.42em, 0);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes front-words-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

/* THE SAME BLACK for all three lines, not --brand-ink. --brand-ink is the
   green-tinted near-black used for text sitting ON the brand colour, and at
   this size it went muddy against the wash and read as a third colour next to
   the bar's true black. The sign uses one black throughout.

   THEY NO LONGER ANIMATE SEPARATELY, and that is the whole fix here. Each line
   used to fly in from an alternating side on its own delay — 0.34s, 0.40s,
   0.46s. On paper that is a 60ms stagger and reads as one gesture; on screen,
   with each line travelling nearly half the viewport, it read as three
   arrivals in a row with dead air between them (operator, 2026-08-12:
   "'Welcome to the' renders first pause 'Best Hour of' pause 'Your day!'").

   The thing being shown is a BOARD — one physical object he carries around
   both gyms — and the lines painted on a board do not arrive one at a time. So
   the board is what moves now, as a single unit, and the lines are just its
   paint. */
.front__leave-lede,
.front__leave-hero,
.front__leave-tail {
  display: block;
  color: #0c0d0a;
}

/* The small line above. Held well back in size — it is the run-up, and giving
   it equal weight would make the board read as a paragraph. */
.front__leave-lede {
  font-size: 0.34em;
  letter-spacing: 0.01em;
  margin-bottom: 0.06em;
}

/* THE KNOCKED-OUT BAR — the half of the board people remember. */
.front__leave-hero {
  background: #0c0d0a;
  color: #ffffff;
  padding: 0.04em 0.16em 0.09em;
}

.front__leave-tail {
  font-size: 0.44em;
  letter-spacing: 0.01em;
  margin-top: 0.1em;
}

.front__star {
  position: absolute;
  top: 0;
  display: block;
  color: var(--brand-ink);
  /* THE RAIN IS NOW AN OPENING, NOT THE WHOLE ACT. At 1.3s it was still
     dropping stars at 1.8s, long after the green had swallowed them, which is
     most of why the sequence felt like it was waiting. Shortened to 0.7s and
     with the delays packed into the first 200ms, the last star lands at 0.9s
     — under the words as they settle. It reads faster without reading
     flicked, which was the 2026-08-12 complaint about the original 0.92s,
     because the curve stays a shallow ease-IN and the fall is shorter rather
     than merely quicker. */
  animation: front-star-fall 0.7s cubic-bezier(0.45, 0, 0.72, 0.52) both;
}

/* Half the field is paler, so the fall reads as depth rather than as one
   flat sheet of confetti. */
.front__star--soft {
  color: color-mix(in srgb, var(--brand-ink) 45%, var(--brand));
}

.front__star svg {
  display: block;
  width: 100%;
  height: auto;
  fill: currentColor;
}

@media (min-width: 760px) {
  .front__shield {
    width: 12.5rem;
  }

  .front__wordmark {
    width: 21rem;
  }
}

/* Reduced motion: the entrance and the ticker stop. Killing the animation
   outright is safe because every one of these keyframes ends on the element's
   OWN base styles — opacity 1, no transform — so the page renders complete
   rather than blank, whatever fill mode the animation would have used.

   The panel's slide and the mark's FLIP are covered too: the FLIP checks the
   query itself before recording anything (see LandingGate.tsx), so the lockup
   changes position in one frame instead of travelling. */
@media (prefers-reduced-motion: reduce) {
  .front::before,
  .front::after,
  .front__mark,
  .front__runner,
  .front__wordmark,
  .front__hint,
  .front__locked,
  .front__ticker,
  .front__ticker-track,
  .front__footer,
  .front__panel,
  .front__leave-paper,
  .front__leave-wash,
  .front__leave-words,
  .front__leave-board,
  .front__leave-lede,
  .front__leave-hero,
  .front__leave-tail,
  .front__star {
    animation: none;
  }

  /* The departure does not mount at all under reduced motion (the link is
     left to behave like a link), but if it ever does, it must not sit there
     as a static green sheet over the page. */
  .front__leave {
    display: none;
  }
}

/* ---------------------------------------------------------------- sign-in */

.signin-wrap {
  /* Like the landing, the sign-in is a FRONT-DOOR surface wearing the real
     Isabella mark — pinned to the IF green so a lingering Varno gym cookie
     cannot put an orange button under Isabella's logo. Location-aware
     branding starts INSIDE the shell, where the toggle lives. */
  --brand: var(--gym-if);
  --brand-strong: #6aa62f;
  --brand-ink: #14230a;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem var(--gutter);
  background: var(--paper);
}

.sheet {
  width: 100%;
  max-width: 24rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.75rem 1.5rem;
}

.signin-head {
  text-align: center;
}

/* The front door wears the real Isabella mark at brand scale. */
.signin-head__logo {
  display: block;
  width: auto;
  height: 3.5rem;
  margin: 0 auto 0.75rem;
}

/* -------------------------------------------------------------- logomark */

/* The clients' REAL marks (public/media/, fetched untouched from the live
   sites). The bundles keep app chrome quiet — the headline weight on an app
   screen belongs to that screen's hero — so the header logo stays compact.
   Fixed height + intrinsic width/height on the <img> = no layout shift. */
.logomark {
  display: block;
  flex: none;
  width: auto;
  height: 1.875rem; /* compact chrome on a 375px phone */
}

@media (min-width: 760px) {
  .logomark {
    height: 2.25rem;
  }
}

/* The shell renders BOTH gyms' logos; the gym context shows exactly one.
   Location-aware branding with zero client JS in the server shell. */
.logomark--vf {
  display: none;
}

/* Scoped to the SHELL, not to any [data-gym] ancestor, so which mark shows is
   decided by specificity rather than by which of two nested `data-gym`
   attributes happens to be written later in this file. `.logomark` exists
   only inside the shell (the front doors draw their own marks), so nothing
   else needs the looser selector — and an owner whose brand cookie is stale
   can no longer end up wearing one gym's logo over the other gym's numbers. */
.shell[data-gym="if"] .logomark--if {
  display: block;
}

.shell[data-gym="if"] .logomark--vf {
  display: none;
}

.shell[data-gym="vf"] .logomark--if {
  display: none;
}

.shell[data-gym="vf"] .logomark--vf {
  display: block;
}

/* ------------------------------------------------------------ gym toggle */

/* The bundles draw a two-state selector as a ROUNDED-RECT segmented
   control (4px tray, 9px segments, 8/16 padding), not a pill — a pill at
   44px turns a two-letter label into a balloon. 36px tall by ~54px wide
   still clears the 44x44 area an adult thumb needs; it is the one chrome
   control that does not take the full 44px height, deliberately, so the
   header stays 60px. */
.gym-toggle {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--paper);
  padding: 3px;
  flex-shrink: 0;
}

.gym-toggle__opt {
  font: inherit;
  font-size: var(--t-caption);
  font-weight: 800;
  letter-spacing: 0.05em;
  border: none;
  background: transparent;
  color: var(--ink-faint);
  border-radius: 9px;
  padding: 0 0.9rem;
  min-height: var(--tap);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}

.gym-toggle__opt[aria-pressed="true"] {
  background: var(--brand);
  color: var(--brand-ink);
}

/* THREE-UP: the owner's one control (Both · IF · VF), in the same tray the
   two-state member switch uses, so the chrome reads as one family. It shares
   the header row with the logo and the way out, so the segments tighten to
   hold that row on a phone — measured at 375px: 24 + logo 87 + 8 + control
   ~139 + 8 + sign-out 73 + 24 = 363. */
.gym-toggle--tri .gym-toggle__opt {
  padding: 0 0.7rem;
}

/* A 320px phone (SE) has 55px less to give than the layout above needs, so
   the two flexible parts — the mark and the segment padding — give it back
   rather than letting the header overflow. */
@media (max-width: 380px) {
  .gym-toggle--tri .gym-toggle__opt {
    padding: 0 0.5rem;
  }

  .shell__header:has(.gym-toggle--tri) .logomark {
    height: 1.6rem;
  }
}

/* The "you are at" chip — the bundle's location dot + name pill
   (6px 12px, 12px w600, 100px radius). */
.gym-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  padding: 0.375rem var(--s-4);
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-soft);
  margin: 0 0 var(--s-8);
}

.gym-chip__dot {
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: var(--brand);
  flex-shrink: 0;
}

/* "HOME GYM" — the eyebrow that names what the header switch controls. It is
   set apart from the gym's name (tracked-out, 800, faint) so the chip reads as
   a LABELLED value rather than as one more sentence of chrome. */
.gym-chip__label {
  font-size: var(--t-nano);
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border-right: 1px solid var(--line);
  padding-right: var(--s-3);
  margin-right: var(--s-1);
}

/* ------------------------------------------------------------------ page */

/* Screen title — the bundles set these in MIXED CASE Anton at 30px ("Hi,
   Tapesh", "Book a Class", "Schedule", "Train with Ben"). Uppercase Anton
   is the WEBSITE's poster voice (64–150px there); inside the app it eats
   the line and flattens the hierarchy, so the app keeps sentence case. */
.page__title {
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-title);
  line-height: 1.05;
  letter-spacing: 0;
  margin: 0 0 var(--s-1);
}

.page__lede {
  color: var(--ink-faint);
  margin: 0 0 var(--s-7);
  font-size: var(--t-meta);
  font-weight: 600;
}

/* Section label — deliberately QUIET. In the bundles a section marker is a
   13px 800-weight tracked-out Archivo eyebrow ("TODAY'S WORKOUT", "COMING
   UP"), never a second display headline. That is what lets the one hero
   element on each screen dominate. */
.section-title {
  font-family: var(--font-body-stack);
  font-weight: 800;
  font-size: var(--t-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: var(--s-9) 0 var(--s-3);
}

/* A FOLDING section marker. The eyebrow keeps every one of its own styles —
   the button inherits them rather than restating them — so a section that can
   fold and one that cannot are visually identical apart from the chevron. */
.section-title__toggle {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
}

.section-title__toggle:disabled {
  /* Disabled only while a search overrides the fold, so it must not look
     broken — no dimming, just an ordinary label that cannot be pressed. */
  cursor: default;
  opacity: 1;
}

.section-title__chevron {
  font-size: 0.85em;
  color: var(--brand-text);
}

/* The search box above the directory. */
.directory__search {
  margin-bottom: var(--s-5);
}

/* A whole row that is a link. The chevron is the affordance; the row keeps the
   same internals as a static one so a tappable list and a flat list do not
   drift apart. */
.row-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  width: 100%;
  color: inherit;
  text-decoration: none;
}

.row-link:hover .who__name,
.row-link:focus-visible .who__name {
  color: var(--brand-text);
}

.row-link__chevron {
  align-self: center;
  flex-shrink: 0;
  color: var(--ink-faint);
  font-size: var(--t-body-lg);
  line-height: 1;
}

/* The row's own content takes the slack, so the chevron stays pinned right. */
.row-link > .who {
  flex: 1 1 auto;
  min-width: 0;
}

/* A labelled fact on a detail page: label left, value right, one per line. */
.detail {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  margin: 0;
}

.detail__label {
  font-size: var(--t-meta);
  font-weight: 700;
  color: var(--ink-faint);
}

.detail__value {
  font-size: var(--t-sm);
  font-weight: 600;
  text-align: right;
}

/* The way back from a detail page to the list it came from. */
.page__back {
  margin: 0 0 var(--s-3);
  font-size: var(--t-sm);
  font-weight: 700;
}

.page__back a {
  color: var(--ink-faint);
  text-decoration: none;
}

.page__back a:hover,
.page__back a:focus-visible {
  color: var(--brand-text);
}

/* An eyebrow above a screen title ("Welcome back"). */
.page__eyebrow {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-faint);
  margin: 0;
}

/* The greeting block: eyebrow + name on the left, avatar on the right —
   the bundle's member-home header. */
.page__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
}

.page__head .avatar--lg {
  width: 2.875rem;
  height: 2.875rem;
  font-size: var(--t-lg);
  font-weight: 800;
  flex-shrink: 0;
}

/* -------------------------------------------------------------- identity */

/* The bundle's profile header: a 74px avatar, the name in Anton at 24px,
   one 13px supporting line, all centered. */
.identity {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin-bottom: var(--s-8);
}

/* .avatar sets a chrome-scale default further down the sheet, so the
   profile's 74px portrait has to out-specify it rather than merely
   follow it. */
.identity .identity__avatar {
  width: 4.625rem;
  height: 4.625rem;
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h2);
}

.identity__name {
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h3);
  line-height: 1.05;
  letter-spacing: 0;
  margin: var(--s-4) 0 0;
}

.identity__sub {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-faint);
  margin: var(--s-1) 0 0;
  overflow-wrap: anywhere;
}

/* ----------------------------------------------------------------- forms */

.stack {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.field__label {
  font-weight: 600;
  font-size: 0.9rem;
}

.field__hint {
  font-size: 0.8rem;
  color: var(--ink-faint);
}

input[type="tel"],
input[type="text"] {
  font: inherit;
  padding: 0.7rem 0.8rem;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--card);
  color: var(--ink);
}

input:focus-visible {
  outline: 2px solid var(--brand-strong);
  outline-offset: 1px;
  border-color: var(--brand-strong);
}

.otp-input {
  letter-spacing: 0.4em;
  font-size: 1.3rem;
  text-align: center;
}

/* --------------------------------------------------------------- buttons */

/* The bundles' button treatment: 800-weight Archivo, 13–16px, 13px of
   vertical padding (so it lands on the 44px floor without help), an 11px
   radius, and never a hairline outline on the primary. */
.btn {
  font: inherit;
  font-weight: 800;
  font-size: var(--t-sm);
  line-height: 1.15;
  border: none;
  border-radius: var(--radius-small);
  background: var(--brand);
  color: var(--brand-ink);
  padding: 0.8125rem 1.25rem;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
}

.btn:hover {
  background: var(--brand-strong);
}

.btn:disabled {
  opacity: 0.55;
  cursor: default;
}

/* The bundle's full-bleed primary CTA ("Log your score", "Confirm"): 16px
   type, 16px of padding, ~54px tall. Capped so it stays a BUTTON on a wide
   window rather than stretching across the whole content column. */
.btn--big {
  width: 100%;
  max-width: 20rem;
  font-size: var(--t-body-lg);
  padding: var(--s-6);
  border-radius: 14px;
}

.btn--quiet {
  background: transparent;
  color: var(--ink-soft);
  border: 1.5px solid var(--line);
  font-weight: 700;
}

.btn--quiet:hover {
  background: var(--paper);
}

/* Row-level control (Book / Cancel on a class card). Smaller TYPE, same
   44px target — a shrunken font is fine, a shrunken hit area is not. */
.btn--small {
  font-size: var(--t-meta);
  padding: 0.625rem 0.75rem;
}

/* On the brand-solid hero the primary inverts to an ink field — and the
   letters are PAPER, not brand.
   
   Brand letters were the original call and they read dull (operator,
   2026-08-12), worst in the Varno build: #EA580C on #131417 clears the
   contrast floor on paper but has almost no perceived brightness, so the label
   sinks into the button instead of sitting on it. Green fared better and still
   looked muted. Nothing was gained for the loss — the button is already inside
   a brand-solid card, so it does not need to carry the hue a second time, and
   paper on ink is the same crisp ~17:1 in both gyms. */
.btn--on-brand {
  background: var(--ink);
  color: var(--paper);
}

.btn--on-brand:hover {
  background: var(--ink);
  filter: brightness(1.25);
}

.btn--ghost-on-brand {
  background: color-mix(in srgb, var(--brand-ink) 14%, transparent);
  color: var(--brand-ink);
  font-weight: 700;
}

.btn--ghost-on-brand:hover {
  background: color-mix(in srgb, var(--brand-ink) 22%, transparent);
}

.actions {
  display: flex;
  gap: var(--s-3);
  flex-wrap: wrap;
}

/* --------------------------------------------------------------- notices */

.notice {
  border-radius: var(--radius-small);
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-6);
  font-size: var(--t-meta);
  font-weight: 600;
  border: 1px solid var(--line);
  background: var(--paper);
}

.notice strong {
  display: block;
  font-size: var(--t-body);
  font-weight: 800;
  margin-bottom: var(--s-1);
}

.notice--error {
  background: var(--bad-soft);
  border-color: rgb(165 21 40 / 0.3);
  color: var(--bad);
}

.notice--ok {
  background: var(--ok-soft);
  border-color: rgb(31 122 63 / 0.3);
  color: var(--ok);
}

.notice--info {
  background: var(--warn-soft);
  border-color: rgb(161 92 0 / 0.3);
  color: var(--warn);
}

.notice--compact {
  padding: 0.5rem 0.7rem;
  font-size: 0.82rem;
}

/* --------------------------------------------------------- persona doors */

.personas {
  margin-top: 1.5rem;
  border-top: 1px dashed var(--line);
  padding-top: 1.25rem;
}

.personas__label {
  font-size: 0.8rem;
  color: var(--ink-faint);
  margin: 0 0 0.6rem;
}

.personas__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: 0.5rem;
  gap: 0.5rem;
}

.persona {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-small);
  padding: 0.55rem 0.7rem;
  text-decoration: none;
  background: var(--card);
}

.persona:hover {
  border-color: var(--brand);
}

.persona strong {
  font-size: 0.92rem;
}

.persona span {
  font-size: 0.75rem;
  color: var(--ink-faint);
}

/* ----------------------------------------------------------------- shell */

.shell {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.shell__header {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  background: var(--card);
  color: var(--ink);
  border-bottom: 1px solid var(--line);
  /* Same --gutter as the content below it, so the logo starts on the same
     line as the screen title rather than 8px outside it. */
  padding: 0.375rem var(--gutter);
  padding-top: calc(0.375rem + env(safe-area-inset-top));
}

.shell__brand {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.shell__who {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.shell__whoname {
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 9rem;
}

@media (max-width: 700px) {
  /* keep wordmark + toggle + sign-out on one compact line */
  .shell__whoname {
    display: none;
  }
}

/* The bundles put identity IN THE SCREEN (the greeting block, the profile
   header), never in the permanent chrome — and two avatars on one page
   read as a bug. The chrome keeps the wordmark, the gym toggle and the
   way out. */
.shell__who > .avatar {
  display: none;
}

.shell__signout {
  font: inherit;
  font-size: var(--t-caption);
  font-weight: 700;
  background: transparent;
  border: 1px solid var(--line);
  color: var(--ink-soft);
  border-radius: 999px;
  padding: 0 var(--s-4);
  min-height: var(--tap);
  white-space: nowrap;
  cursor: pointer;
}

.shell__signout:hover {
  border-color: var(--brand);
  color: var(--ink);
}

.avatar {
  width: 2.125rem;
  height: 2.125rem;
  border-radius: 50%;
  background: var(--brand);
  color: var(--brand-ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--t-caption);
  font-weight: 800;
  flex-shrink: 0;
}

/* The avatar as a door, not a decoration. It is now the only way into Profile
   from Home (the tile that used to do the job was a second copy of it), so it
   has to answer a tap and show that it can. */
.avatar--link {
  text-decoration: none;
  transition: box-shadow 0.16s ease, transform 0.16s ease;
}

.avatar--link:hover,
.avatar--link:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 28%, transparent);
}

.avatar--link:active {
  transform: scale(0.96);
}

@media (prefers-reduced-motion: reduce) {
  .avatar--link {
    transition: none;
  }
  .avatar--link:active {
    transform: none;
  }
}

/* --------------------------------------------------------- notices bell */

/* The bell and the avatar travel together at the top-right of Home: the two
   controls that are about YOU rather than about the page. */
/* THIS is the panel's anchor, not the bell — see .notices__panel. */
.page__head-tools {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-shrink: 0;
}

.notices {
  display: flex;
}

.notices__bell {
  position: relative;
  width: 2.125rem;
  height: 2.125rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink-soft);
  cursor: pointer;
}

.notices__bell:hover,
.notices__bell[aria-expanded="true"] {
  border-color: var(--brand);
  color: var(--brand-text);
}

.notices__glyph {
  width: 1.125rem;
  height: 1.125rem;
}

/* A 34px circle is the right WEIGHT beside the greeting; 44px is the floor a
   thumb needs. Both are true at once: the drawn circle stays 34px and an
   invisible pad carries the hit area out to 44. */
.avatar--link {
  position: relative;
}

.notices__bell::after,
.avatar--link::after {
  content: "";
  position: absolute;
  inset: -0.3125rem;
  border-radius: 50%;
}

/* The count rides the bell's shoulder, absolutely placed, so an announcement
   arriving cannot nudge the greeting's layout. */
.notices__count {
  position: absolute;
  top: -0.25rem;
  right: -0.25rem;
  min-width: 1.05rem;
  height: 1.05rem;
  padding: 0 0.2rem;
  border-radius: 999px;
  background: var(--brand);
  color: var(--brand-ink);
  font-size: 0.625rem;
  font-weight: 800;
  line-height: 1.05rem;
  text-align: center;
}

/* Transparent, deliberately: a notification list is not modal, and dimming
   the page to read a note about t-shirts would be the wrong ceremony. */
.notices__away {
  position: fixed;
  inset: 0;
  z-index: 40;
}

/* Right-aligned to the TOOLS CLUSTER, not to the bell. Anchored to the bell it
   hung ~42px (avatar + gap) further left, so a 20rem panel on a 390px phone
   ran off the left edge and clipped its own first column of text. The cluster's
   right edge is the page gutter, which is the widest honest anchor there is. */
.notices__panel {
  position: absolute;
  top: calc(100% + var(--s-3));
  right: 0;
  z-index: 41;
  width: min(20rem, calc(100vw - 2 * var(--gutter)));
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px rgb(19 20 23 / 0.14);
  padding: var(--s-4);
}

.notices__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.notices__item {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-3) 0;
}

.notices__item + .notices__item {
  border-top: 1px solid var(--line);
}

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

.notices__title {
  margin: 0;
  font-size: var(--t-meta);
  font-weight: 800;
}

.notices__text {
  margin: 0.15rem 0 0;
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

.notices__dismiss {
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink-faint);
  cursor: pointer;
}

.notices__dismiss:hover {
  background: var(--paper);
  color: var(--ink);
}

.notices__x {
  width: 0.875rem;
  height: 0.875rem;
}

.notices__clear {
  margin: 0;
  padding: var(--s-3) 0;
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

/* Dismissing is per-device and the archive is not — so the panel always says
   where the full list lives. */
.notices__all {
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
  width: 100%;
}

/* THE screen margin (--gutter, decided once at the top of this sheet) — and
   enough bottom room to clear the fixed tab bar plus the home indicator.
   Every signed-in screen is a child of this element, so one token here is
   what makes the whole app breathe the same. */
.shell__main {
  flex: 1 1;
  width: 100%;
  max-width: 46rem;
  margin: 0 auto;
  padding: var(--s-8) var(--gutter) 7rem;
}

/* ------------------------------------------------------------- tab bar */

.shell__tabs {
  /* ONE SIZE FOR EVERY LABEL IN THE BAR, declared where the bar begins. The
     Menu is a <button> and needs `font: inherit` to shed the UA font, which
     also resets its size — so both it and the <a> tabs read this instead of
     each pinning a number and drifting apart. */
  --tab-label: 0.8125rem;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  background: var(--card);
  border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
}

/* Bundle bar: 11px above the glyph, 5px between glyph and label, a 700-weight
   label, and 30px of breathing room below for the home indicator. Reads at a
   glance and clears 44px comfortably.

   THE BAR RUNS A SIZE ABOVE THE REST OF THE APP (operator, 2026-08-12: "maybe
   make the text and icons larger in general"). 13px rather than the 12px
   caption token, because this is the one control that gets used at arm's
   length, one-handed, while walking into a gym — and it is a fixed set of four
   or five short words, so the extra pixel cannot reflow anything. The token
   itself is unchanged: --t-caption is captions, and this is not a caption. */
.tab {
  position: relative;
  flex: 1 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  padding: 0.6875rem 0.125rem 0.75rem;
  text-decoration: none;
  color: var(--ink-faint);
  font-size: var(--tab-label);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-align: center;
  border-top: 2px solid transparent;
  margin-top: -1px;
}

/* The bar's icon (src/components/icons.tsx). 21px inside the 20–22px band the
   family is drawn for; `currentColor` on the stroke is what lets the ACTIVE
   tab take the gym's brand color, which is why the bar was monochrome in the
   first place. `display:block` kills the inline-SVG baseline gap that would
   otherwise push the label a pixel off centre. */
.tab__glyph {
  width: 1.625rem;
  height: 1.625rem;
  display: block;
  flex: none;
}

.tab__label {
  line-height: 1.1;
}

.nav-unread {
  position: absolute;
  top: 0.35rem;
  left: calc(50% + 0.35rem);
  min-width: 1.15rem;
  height: 1.15rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 0.25rem;
  border: 2px solid var(--card);
  border-radius: 999px;
  background: var(--bad);
  color: #fff;
  font-size: 0.625rem;
  font-weight: 900;
  line-height: 1;
}

.tab[aria-current="page"] {
  color: var(--brand-text);
  border-top-color: var(--brand);
}

/* The Menu sits in the bar as a peer of the tabs, so it inherits `.tab` and
   only has to undo what being a <button> brings with it. It is a control
   rather than a link, and it never takes `aria-current` — see NavTabs.

   IT DOES NOT PICK ITS OWN SIZE. `font: inherit` sheds the UA button font but
   resets font-size with it, so the size has to be restated — from the same
   --tab-label the <a> tabs read. It used to restate --t-nano, which rendered
   "Menu" two pixels smaller than the three words beside it: same row, same
   target, visibly lighter. */
.tab--menu {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  background: none;
  border: none;
  border-top: 2px solid transparent;
  font: inherit;
  font-size: var(--tab-label);
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
}

/* Open is the Menu's version of current: the drawer it controls is on screen,
   which is the only state this control has to report. */
.tab--menu[aria-expanded="true"] {
  color: var(--brand-text);
  border-top-color: var(--brand);
}

/* ---------------------------------------------------------------- drawer */

/* THE OVERFLOW DRAWER — a native <dialog>, so the scrim is ::backdrop and the
   focus handling is the platform's. Reset the UA's centred box into a
   full-height sheet pinned to the right. */
.drawer {
  margin: 0 0 0 auto;
  padding: 0;
  border: none;
  background: none;
  max-width: none;
  max-height: none;
  width: min(20rem, 84vw);
  height: 100dvh;
  overflow: visible;
  color: var(--ink);
}

.drawer::-webkit-backdrop {
  background: rgb(12 13 10 / 0.38);
  animation: drawer-scrim 0.2s ease both;
}

.drawer::backdrop {
  background: rgb(12 13 10 / 0.38);
  animation: drawer-scrim 0.2s ease both;
}

/* `backwards`, NOT `both`. A fill of `both` keeps the keyframes' final
   transform applied for the life of the element, at a priority that beats the
   inline transform the drag writes — so the panel would enter beautifully and
   then refuse to move a pixel under the finger. `backwards` holds the
   from-state before the animation starts and lets go at the end, which is all
   the entrance ever needed. */
.drawer[open] .drawer__panel {
  animation: drawer-in 0.26s cubic-bezier(0.22, 0.7, 0.3, 1) backwards;
}

@keyframes drawer-scrim {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes drawer-in {
  from {
    transform: translate3d(100%, 0, 0);
  }
  to {
    transform: none;
  }
}

.drawer__panel {
  display: flex;
  flex-direction: column;
  height: 100%;
  /* Vertical panning stays the browser's (the list can be longer than the
     screen); the horizontal gesture is ours, and this is what stops the UA
     claiming it for an overscroll or a back-swipe. */
  touch-action: pan-y;
  background: var(--card);
  border-left: 1px solid var(--line);
  /* The bar is fixed to the bottom on a phone and the drawer covers it, so
     the last row must clear the home indicator on its own. */
  padding-bottom: env(safe-area-inset-bottom);
  box-shadow: -18px 0 44px rgb(12 13 10 / 0.16);
}

.drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: calc(var(--s-7) + env(safe-area-inset-top)) var(--s-7) var(--s-5);
  border-bottom: 1px solid var(--line);
}

.drawer__title {
  margin: 0;
  font-family: var(--font-display-stack);
  font-size: var(--t-h2);
  font-weight: 400;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

.drawer__close {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: var(--s-3) var(--s-6);
  min-height: var(--tap);
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--ink-soft);
  cursor: pointer;
}

.drawer__close:hover,
.drawer__close:focus-visible {
  color: var(--ink);
  border-color: var(--ink-faint);
}

.drawer__list {
  list-style: none;
  margin: 0;
  padding: var(--s-3) 0;
  overflow-y: auto;
  flex: 1 1;
}

/* Full-width rows with the icon leading — the same reading order as the bar,
   so the drawer feels like the bar continued rather than a different menu. */
.drawer__link {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  padding: var(--s-5) var(--s-7);
  min-height: 3.75rem;
  text-decoration: none;
  color: var(--ink);
  font-size: 1.125rem;
  font-weight: 750;
}

.drawer__link:hover,
.drawer__link:focus-visible {
  background: var(--paper);
}

.drawer__link[aria-current="page"] {
  color: var(--brand-text);
  box-shadow: inset 3px 0 0 var(--brand);
}

.drawer__glyph {
  width: 1.625rem;
  height: 1.625rem;
  display: block;
  flex: none;
}

.drawer__chevron {
  margin-left: 0;
  color: var(--ink-faint);
  font-size: 1.1rem;
  line-height: 1;
}

.drawer__unread {
  margin-left: auto;
  min-width: 1.5rem;
  height: 1.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 0.4rem;
  border-radius: 999px;
  background: var(--bad);
  color: #fff;
  font-size: var(--t-micro);
  font-weight: 900;
}

/* ── NEW-MEMBER BASICS GATE ───────────────────────────────────────────── */
.basics-panel {
  margin-bottom: var(--s-5);
  border-color: color-mix(in srgb, var(--brand) 42%, var(--line));
}

.basics-home {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
  color: var(--ink);
  text-decoration: none;
  border-color: color-mix(in srgb, var(--brand) 42%, var(--line));
}

.basics-home > span {
  display: grid;
  grid-gap: 0.2rem;
  gap: 0.2rem;
}

.basics-home > svg {
  width: 1.25rem;
  height: 1.25rem;
  color: var(--brand-text);
}

.basics-panel__title {
  margin: 0;
  font-family: var(--font-display-stack);
  font-size: var(--t-h3);
  line-height: 1.05;
}

.basics-panel__row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
}

.basics-panel__row > span:first-child {
  min-width: 0;
  display: grid;
  grid-gap: 0.15rem;
  gap: 0.15rem;
  flex: 1 1;
}

.basics-panel__row small {
  color: var(--ink-faint);
}

.basics-panel__row select {
  width: auto;
  min-width: 7.4rem;
}

.basics-schedule {
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
}

@media (max-width: 520px) {
  .basics-panel__row {
    align-items: stretch;
    flex-wrap: wrap;
  }

  .basics-panel__row > span:first-child {
    flex-basis: 100%;
  }

  .basics-panel__row select,
  .basics-panel__row .btn {
    flex: 1 1;
  }
}

/* ── COACH CALENDAR ACTIONS ───────────────────────────────────────────── */
.coach-private-open {
  margin: 0 0 var(--s-5);
}

.coach-private-empty {
  margin: calc(-1 * var(--s-3)) 0 var(--s-5);
  color: var(--ink-faint);
  font-size: var(--t-caption);
}

.class-card__coach-actions {
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  gap: var(--s-2);
  flex-wrap: wrap;
}

.class-card__coach-actions .btn {
  white-space: nowrap;
}

.shift-open {
  color: var(--warn);
  font-size: var(--t-caption);
  font-weight: 800;
}

.sheet-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-5);
}

.sheet-kicker {
  margin: 0 0 0.15rem;
  color: var(--brand-text);
  font-size: var(--t-caption);
  font-weight: 850;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.sheet-title {
  margin: 0;
  font-family: var(--font-display-stack);
  font-size: var(--t-h3);
  font-weight: 400;
  line-height: 1;
}

.sheet-close {
  min-height: var(--tap);
  padding: 0 var(--s-3);
  border: 0;
  background: transparent;
  color: var(--ink-faint);
  font: inherit;
  font-weight: 750;
}

.coach-private-form {
  display: grid;
  grid-gap: var(--s-4);
  gap: var(--s-4);
}

.coach-private-form label {
  display: grid;
  grid-gap: var(--s-2);
  gap: var(--s-2);
  color: var(--ink-soft);
  font-size: var(--t-caption);
  font-weight: 800;
}

.coach-private-form input,
.coach-private-form select,
.coach-private-form textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-weight: 600;
}

.coach-private-form textarea {
  min-height: 5.5rem;
  resize: vertical;
}

.coach-private-form__split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: var(--s-3);
  gap: var(--s-3);
}

.money-input {
  position: relative;
  display: block;
}

.money-input > span {
  position: absolute;
  z-index: 1;
  left: 0.8rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ink-faint);
}

.money-input input {
  padding-left: 1.6rem;
}

.sheet-caption {
  margin: 0;
  color: var(--ink-faint);
  font-size: var(--t-caption);
  line-height: 1.45;
}

@media (max-width: 520px) {
  .class-card__coach-actions {
    width: 100%;
  }

  .class-card__coach-actions form,
  .class-card__coach-actions .btn {
    width: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .drawer::-webkit-backdrop {
    animation: none;
  }
  .drawer::backdrop,
  .drawer[open] .drawer__panel {
    animation: none;
  }
}

/* Wide window: the bar becomes a top strip under the header. */
@media (min-width: 760px) {
  .shell__tabs {
    /* A row of labelled pills has room to read a size up, and retargeting the
       token moves the links and the Menu together. */
    --tab-label: var(--t-sm);
    position: -webkit-sticky;
    position: sticky;
    top: 3.4rem;
    bottom: auto;
    border-top: none;
    border-bottom: 1px solid var(--line);
    justify-content: center;
    gap: 0.5rem;
  }

  .tab {
    flex: 0 0 auto;
    flex-direction: row;
    gap: 0.45rem;
    padding: 0.6875rem var(--s-6);
    border-top: none;
    border-bottom: 2px solid transparent;
    margin-top: 0;
  }

  .tab--menu {
    border-top: none;
    border-bottom: 2px solid transparent;
  }

  .tab[aria-current="page"],
  .tab--menu[aria-expanded="true"] {
    border-bottom-color: var(--brand);
  }

  .shell__main {
    padding: var(--s-11) var(--gutter) var(--s-12);
  }
}

/* ----------------------------------------------------------------- cards */

/* A card is a CARD, not a table row: --card-pad all round (the bundles'
   block-card value), a 16px radius, 10px between siblings. */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--card-pad);
}

.card + .card {
  margin-top: var(--s-3);
}

/* Brand-solid card — the bundles' accent hero treatment (the "YOUR NEXT
   CLASS" / workout headline card), in the active gym's color. */
.card--brand {
  background: var(--brand);
  border-color: transparent;
  color: var(--brand-ink);
}

.card--brand .card__label {
  color: color-mix(in srgb, var(--brand-ink) 65%, transparent);
}

.card__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
}

/* Card eyebrow — 11px, 800, tracked out. The bundles' "YOUR NEXT CLASS" /
   "ACTIVE PASS" marker. */
.card__label {
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 var(--s-3);
}

/* ------------------------------------------------------------- kpi grid */

.kpis {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: var(--s-4);
  gap: var(--s-4);
}

/* The owner portal's KPI tile: 20px padding, a 12px 700-weight label, and
   a 38px Anton numeral that is the loudest thing on the screen. */
.kpi {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--s-8);
}

.kpi__number {
  font-family: var(--font-display-stack);
  font-size: var(--t-display);
  line-height: 1.05;
  color: var(--ink);
  margin-bottom: var(--s-1);
}

/* One tile per screen carries the accent — the bundle colors "Revenue"
   and leaves the rest in ink, so the number that matters stands out. */
.kpi--accent .kpi__number {
  color: var(--brand-text);
}

.kpi__label {
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--ink-faint);
}

@media (min-width: 760px) {
  .kpis {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--s-6);
  }
}

/* The 4-up quick-action grid that lived here is GONE (2026-08). Two of its
   four tiles (Book, WOD) were tabs in the bar an inch below, redrawn as
   squares; Profile is the avatar in the greeting; Pass is Membership, in the
   Menu. A shortcut grid whose every shortcut is already on screen is not a
   shortcut, it is a second navigation bar competing with the first. */

/* A quiet closing line under a section — status plus the way onward. */
.page__footlink {
  margin: var(--s-5) 0 0;
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

/* ------------------------------------------------------------- stat card */

/* One numeral, full width. This used to be half of a pair, the other half
   being the Active pass tile — but a member with a working pass does not open
   the app to check the pass works, and the pass has a page of its own. */
.stat-card {
  min-width: 0;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--s-6);
}

.stat-card__number {
  font-family: var(--font-display-stack);
  font-size: var(--t-title);
  line-height: 1;
  color: var(--brand-text);
}

.stat-card__label {
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
  margin-top: var(--s-1);
}

/* Alone on the row, the numeral and its label sit side by side rather than
   stacked — stacked in a full-width box leaves a band of empty card to its
   right, which reads as a missing sibling. */
.stat-card--solo {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  margin-top: var(--s-5);
  padding: var(--s-5) var(--s-6);
}

.stat-card--solo .stat-card__label {
  margin-top: 0;
}

/* The in-page "Showing" control that used to live here is GONE (2026-08).
   It drew a second gym control directly under the header's gym switch on the
   owner screens — two adjacent affordances for one job, free to disagree with
   each other. Its three states moved INTO the header control
   (.gym-toggle--tri above); the page keeps none of its own. */

/* ------------------------------------------------------------ week strip */

/* The bundle's day strip: 46px-wide chips, an 11px 700 day-of-week over a
   20px ANTON numeral, 10px of vertical padding — a 56px chip that scrolls
   horizontally and reads as a date, not a link. */
.week-strip {
  display: flex;
  gap: var(--s-2);
  overflow-x: auto;
  padding: 0 0 var(--s-2);
  margin-bottom: var(--s-6);
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.week-strip::-webkit-scrollbar {
  display: none;
}

.week-strip a {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.15rem;
  text-decoration: none;
  text-align: center;
  border: 1.5px solid var(--line);
  border-radius: 12px;
  padding: var(--s-3) var(--s-1);
  color: var(--ink);
  background: var(--card);
  min-width: 3.125rem;
}

.week-strip a[aria-current="date"] {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
}

.week-strip a .dow {
  display: block;
  font-size: var(--t-micro);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.week-strip a[aria-current="date"] .dow {
  color: inherit;
  opacity: 0.75;
}

.week-strip a .dnum {
  display: block;
  font-family: var(--font-display-stack);
  font-size: var(--t-h5);
  line-height: 1.05;
}

/* ------------------------------------------------------------ class rows */

/* The bundle's bookable class row: 16px padding, a 16px radius, a centered
   time column, a hairline rule, then name + coach + spots, then the
   action. 14px between the columns; 10px between cards. */
.class-card {
  display: flex;
  align-items: center;
  /* 10px between columns, not 14: at 375px the row has ~300px of inner
     width to spend on time + class + coach + spots + action, and every
     px of gutter comes straight out of the class name. */
  gap: var(--s-3);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--s-6);
}

.class-card + .class-card {
  margin-top: var(--s-3);
}

.class-card--link {
  color: inherit;
  text-decoration: none;
}

.class-card--link:hover {
  border-color: color-mix(in srgb, var(--brand) 55%, var(--line));
}

.class-card__time {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  font-size: var(--t-body-lg);
  font-weight: 800;
  line-height: 1.1;
  min-width: 2.75rem;
  flex-shrink: 0;
}

.class-card__time small {
  display: block;
  font-size: var(--t-micro);
  font-weight: 600;
  color: var(--ink-faint);
  margin-top: 0.1rem;
}

/* The bundle's vertical hairline between the time and the class. */
.class-card__rule {
  width: 1px;
  align-self: stretch;
  background: var(--line);
  flex-shrink: 0;
}

.class-card__body {
  flex: 1 1;
  min-width: 0;
}

.class-card__name {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
  font-size: var(--t-body-lg);
  font-weight: 700;
  line-height: 1.2;
  margin: 0;
}

/* Coach and spots-remaining are the two facts the incumbent hides — the
   bundles put them on their own line under the class name at 12px 600. */
.class-card__meta {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-1);
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
  flex-wrap: wrap;
}

.class-card__aside {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s-2);
  flex-shrink: 0;
}

.coach-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--ink-soft);
  font-weight: 600;
}

.coach-chip .avatar {
  width: 1.5rem;
  height: 1.5rem;
  font-size: 0.5625rem;
  font-weight: 800;
  background: var(--ink);
  color: var(--dark-ink);
}

/* Spots-remaining sits INLINE with the coach on the class row (the
   bundle's "Coach Ben · 4 left"), colored by urgency rather than boxed —
   a pill there would compete with the Book button beside it. */
.spots {
  font-weight: 800;
}

.spots--open {
  color: var(--ok);
}

.spots--low {
  color: var(--warn);
}

.spots--full {
  color: var(--bad);
}

.spots--booked {
  color: var(--brand-text);
}

/* --------------------------------------------------------- next-class hero

   The single loudest element on member Home. The bundle draws it as a
   brand-SOLID poster: an 11px tracked eyebrow, a 40px Anton class name
   sharing a baseline with the time, a supporting line, then the actions —
   22px of padding inside a 20px radius, with the corner-circle motif. */

.next-card {
  position: relative;
  overflow: hidden;
  background: var(--brand);
  color: var(--brand-ink);
  border-radius: var(--radius-hero);
  padding: var(--s-9);
  box-shadow: var(--shadow);
  margin-bottom: var(--s-4);
}

.next-card::after {
  content: "";
  position: absolute;
  top: -30px;
  right: -30px;
  width: 140px;
  height: 140px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--brand-ink) 7%, transparent);
  pointer-events: none;
}

.next-card > * {
  position: relative;
}

.next-card__label {
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 0 0 var(--s-3);
  color: color-mix(in srgb, var(--brand-ink) 78%, transparent);
}

.next-card__headline {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  flex-wrap: wrap;
  margin: 0;
}

.next-card__name {
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-hero);
  line-height: 1;
  letter-spacing: 0;
}

.next-card__when {
  font-size: var(--t-body);
  font-weight: 700;
}

.next-card__meta {
  font-size: var(--t-sm);
  font-weight: 600;
  margin: var(--s-2) 0 var(--s-7);
  color: color-mix(in srgb, var(--brand-ink) 85%, transparent);
}

.next-card__actions {
  display: flex;
  gap: var(--s-3);
  flex-wrap: wrap;
}

.next-card__actions .btn {
  flex: 1 1 auto;
  white-space: nowrap;
  padding-inline: var(--s-4);
}

/* THE TODAY-STATE'S EMPTY FACE (client, this wave): the same card, DRAWN
   RATHER THAN FILLED. Solid green means "you train today"; this outline
   means "not yet" — border, transparent ground, no shadow — so the answer
   arrives before a word is read. The headline drops two steps from hero
   scale: an absence should not shout at poster volume. */
.next-card--outline {
  background: transparent;
  color: var(--ink);
  border: 1.5px solid color-mix(in srgb, var(--brand) 60%, var(--line));
  box-shadow: none;
}

.next-card--outline::after {
  background: color-mix(in srgb, var(--brand) 9%, transparent);
}

.next-card--outline .next-card__label {
  color: var(--brand-text);
}

.next-card--outline .next-card__name {
  font-size: var(--t-h3);
}

.next-card--outline .next-card__meta {
  color: var(--ink-soft);
}

/* "Next: VF60 · Sat, Aug 15 · 8:45 AM" — the one quiet line naming the
   future booking, so an empty TODAY never reads as an empty week. */
.next-card__next-up {
  font-weight: 700;
}

/* ---------------------------------------------------------------- badges */

.badge {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 0.22rem 0.6rem;
  font-size: var(--t-micro);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

/* Location chips are LOCATION-aware, not context-aware: an IF chip is
   always Isabella green, a VF chip always Varno orange, so mixed lists
   stay legible whichever gym context is active. */
.badge--loc {
  background: var(--brand-soft);
  color: var(--brand-text);
}

.badge--loc[data-loc="IF"] {
  background: var(--gym-if-soft);
  color: var(--gym-if-text);
}

.badge--loc[data-loc="VF"] {
  background: var(--gym-vf-soft);
  color: var(--gym-vf-text);
}

.badge--open {
  background: var(--ok-soft);
  color: var(--ok);
}

.badge--low {
  background: var(--warn-soft);
  color: var(--warn);
}

.badge--full {
  background: var(--bad-soft);
  color: var(--bad);
}

.badge--booked {
  background: var(--brand);
  color: var(--brand-ink);
}

.badge--active {
  background: var(--ok-soft);
  color: var(--ok);
}

.badge--expiring {
  background: var(--warn-soft);
  color: var(--warn);
}

.badge--expired,
.badge--none {
  background: var(--line);
  color: var(--ink-faint);
}

.badge--demo {
  background: var(--warn-soft);
  color: var(--warn);
  border: 1px dashed rgb(161 92 0 / 0.4);
}

.badge--role-owner { background: var(--ink); color: var(--dark-ink); }
.badge--role-coach { background: var(--brand); color: var(--brand-ink); }
.badge--role-member { background: var(--ok-soft); color: var(--ok); }
.badge--role-lead { background: var(--warn-soft); color: var(--warn); }
.badge--role-unknown { background: var(--line); color: var(--ink-faint); }

/* -------------------------------------------------------------- pass card */

.pass-card {
  position: relative;
  overflow: hidden;
  background: var(--brand);
  color: var(--brand-ink);
  border-radius: var(--radius-hero);
  padding: var(--s-9);
  box-shadow: var(--shadow);
}

.pass-card--receipt {
  width: 100%;
  display: block;
  margin-bottom: 1.25rem;
  border: 0;
  color: inherit;
  text-align: left;
  font: inherit;
  cursor: pointer;
}

.pass-card--receipt:hover,
.pass-card--receipt:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--brand) 55%, transparent);
  outline-offset: 2px;
}

.pass-card--receipt > span {
  display: block;
}

.pass-card--receipt .pass-card__meta {
  display: flex;
}

.pass-card__receipt-hint {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-left: auto;
  color: var(--brand-text);
  font-size: var(--t-caption);
  font-weight: 850;
}

.pass-card__receipt-hint svg {
  width: 1rem;
  height: 1rem;
}

.pass-history {
  padding: 0;
  overflow: hidden;
}

.pass-history__button {
  width: 100%;
  min-height: 3.75rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  border: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  text-align: left;
  font: inherit;
  cursor: pointer;
}

.pass-history__button:last-child {
  border-bottom: 0;
}

.pass-history__button:hover,
.pass-history__button:focus-visible {
  background: var(--paper);
}

.pass-history__button > span:first-child {
  min-width: 0;
  display: grid;
  grid-gap: 0.15rem;
  gap: 0.15rem;
}

.pass-history__button small {
  color: var(--ink-faint);
}

/* the bundle's corner-circle decoration */
.pass-card::after {
  content: "";
  position: absolute;
  top: -30px;
  right: -30px;
  width: 140px;
  height: 140px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--brand-ink) 7%, transparent);
  pointer-events: none;
}

.pass-card__label {
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--brand-ink) 75%, transparent);
  margin: 0;
}

.pass-card__name {
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h3);
  line-height: 1.05;
  letter-spacing: 0;
  margin: var(--s-3) 0 var(--s-1);
}

.pass-card__billing {
  font-size: var(--t-sm);
  font-weight: 600;
  color: color-mix(in srgb, var(--brand-ink) 85%, transparent);
  margin: 0;
}

.pass-card__meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-7);
  font-size: var(--t-caption);
  font-weight: 600;
  color: color-mix(in srgb, var(--brand-ink) 80%, transparent);
}

.pass-card .badge--active {
  background: color-mix(in srgb, var(--brand-ink) 16%, transparent);
  color: var(--brand-ink);
}

/* --------------------------------------------------------------- wod view */

/* WOD hero — the bundle gives today's programming a full-width headline
   band (accent eyebrow, 40px Anton title, supporting line) rather than a
   card. clamp() keeps a long title like "Heavy Front Squats + Firehouse"
   from swallowing a 375px screen. */
.wod-hero {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-hero);
  background: linear-gradient(
    160deg,
    color-mix(in srgb, var(--brand) 26%, var(--card)),
    var(--card) 78%
  );
  border: 1px solid color-mix(in srgb, var(--brand) 30%, transparent);
  padding: var(--s-8) var(--s-9) var(--s-9);
  margin-bottom: var(--s-8);
}

.wod-hero__label {
  font-size: var(--t-caption);
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--brand-text);
  margin: 0 0 var(--s-2);
}

.wod-hero__title {
  font-family: var(--font-display-stack);
  font-weight: 400;
  /* 10vw lands on the bundle's flat 40px at its own 402px device width and
     eases down on a narrower phone, so a long title ("Heavy Front Squats
     + Firehouse") stays two lines instead of three. */
  font-size: clamp(1.75rem, 10vw, var(--t-hero));
  line-height: 0.98;
  letter-spacing: 0;
  margin: 0;
}

.wod-hero__sub {
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--ink-soft);
  margin: var(--s-2) 0 0;
}

/* HIS NOTES FOR THE DAY — deliberately NOT a programming block. A block is a
   card with the brand rail down its left edge, and these must not read as work
   to be done; they are the gym talking (a closure, the cycle plan, someone's
   anniversary). So: no rail, no card shadow, a flat tinted panel that reads as
   an aside. It sits above the warm-up because a class-time change is the one
   thing on this screen that is worth reading before the workout. */
.wod-notes {
  background: color-mix(in srgb, var(--brand) 9%, var(--card));
  border: 1px solid color-mix(in srgb, var(--brand) 24%, transparent);
  border-radius: var(--radius);
  padding: var(--s-5) var(--s-6);
  margin-bottom: var(--s-5);
}

.wod-notes p {
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--ink-soft);
  margin: 0;
}

.wod-notes p + p {
  margin-top: var(--s-2);
}

/* A programming block: 18px padding, a 12px 800 accent block name, a 12px
   700 format on the right, then the movements at 15px with an 800 scheme
   right-aligned — the bundle's exact hierarchy. */
.wod-block {
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 4px solid var(--brand);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--s-7);
}

.wod-block + .wod-block {
  margin-top: var(--s-5);
}

/* A HEADER HAS TO OUTRANK WHAT IT HEADS (operator, 2026-08-12: "WARM UP
   STREGFNTH dont look like headers"). It was caption-sized green text sitting
   above body-sized black movement names, so the first thing on the card was
   quieter than the third — the eye read the moves and never registered which
   block it was in.

   It is now the display face at h4, in ink, on a tinted band that runs the
   full width of the card with a rule under it. Same reasoning as a whiteboard:
   the section title is written big and boxed, and the work is listed under it.
   The green stays as the left edge of the card, where it marks the block
   without competing with the word. */
.wod-block__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin: calc(var(--s-7) * -1) calc(var(--s-7) * -1) var(--s-2);
  padding: var(--s-4) var(--s-7);
  background: color-mix(in srgb, var(--brand) 8%, transparent);
  border-bottom: 2px solid color-mix(in srgb, var(--brand) 30%, transparent);
  border-radius: 0 var(--radius) 0 0;
}

.wod-block__kind {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  font-family: var(--font-display-stack);
  font-size: var(--t-h4);
  font-weight: 400;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink);
  line-height: 1.1;
}

.wod-block__kind-icon {
  width: 1.55rem;
  height: 1.55rem;
  flex: none;
  color: var(--brand-text);
  stroke-width: 2;
}

/* THE SAME COLUMN THE SCHEMES USE. A block's format ("AMRAP 12", "4 rounds")
   and a movement's scheme ("15 @ 20/14") answer the same question at two
   scales — how much — so they line up on the same right edge and are set in
   the same face. They used to be a dim grey aside in a different weight,
   which is what made the card feel like it had two unrelated grids. */
.wod-block__format {
  font-size: var(--t-caption);
  font-weight: 800;
  color: var(--ink);
  text-align: right;
  flex-shrink: 0;
  white-space: nowrap;
}

/* A REAL COACHING CUE, and only that. Anything that merely restates the header
   ("Coach-led primer" under WARM-UP) is not a note and has been taken out of
   the data rather than styled down. */
.wod-block__note {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-faint);
  margin: var(--s-3) 0 0;
}

.wod-move {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-top: 1px solid var(--line);
  font-size: var(--t-body);
  font-weight: 600;
}

/* The first row sits directly under the header band, which is already a rule —
   a second line immediately below it reads as a mistake. */
.wod-block__note + .wod-move,
.wod-block__head + .wod-move {
  border-top: 0;
}

.wod-move__scheme {
  font-weight: 800;
  color: var(--ink);
  text-align: right;
  flex-shrink: 0;
  /* Tabular figures so "4x4 @ 75%" and "15 @ 20/14" stack as a column of
     numbers rather than as ragged text that happens to be right-aligned. */
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
}

/* The bundle's "▶ DEMO" pill — drawn only when the movement HAS a video. */
.wod-move__demo {
  display: inline-flex;
  align-items: center;
  margin-left: var(--s-2);
  font-size: var(--t-nano);
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--brand-text);
  border: 1px solid color-mix(in srgb, var(--brand) 45%, transparent);
  border-radius: 999px;
  padding: 0.15rem 0.5rem;
  text-decoration: none;
}

/* ------------------------------------------------------------ list rows */

.row-list {
  list-style: none;
  margin: 0;
  /* Clears the browser's default <ul> indent. */
  padding: 0;
}

/* A LIST THAT IS A CARD KEEPS THE CARD'S INSET. The reset above is declared
   later in this sheet than `.card` and at equal specificity, so it was
   quietly winning on every `class="row-list card"` — every owner list drew
   its rows flush against the card border, which put "312 check-ins" and
   "ends Aug 14, 2026" hard on the edge of the screen. Restated here as a
   compound selector so the fix depends on specificity, not on which block
   happens to come first. */
.row-list.card {
  padding: var(--card-pad);
}

.row-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) 0;
  min-height: var(--tap);
  border-bottom: 1px solid var(--line);
  font-size: var(--t-sm);
}

.row-list li:last-child {
  border-bottom: none;
}

.row-list .who {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-width: 0;
}

.row-list .who__name {
  font-size: var(--t-sm);
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.row-list .who__sub {
  display: block;
  font-size: var(--t-caption);
  color: var(--ink-faint);
  font-weight: 600;
}

.row-list .end {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.2rem;
  flex-shrink: 0;
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

.row-list .end strong {
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h4);
  line-height: 1;
  color: var(--ink);
}

/* -------------------------------------------------------------- honesty */

/* One QUIET muted line (beauty pass, 2026-08): the honesty caption stays on
   every seeded surface, but as a footnote, not a warning box — the loud
   dashed amber panel read as a broken state and shouted over the screen. */
.demo-note {
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
  text-align: center;
  margin: var(--s-11) 0 0;
}

/* Empty states get the same room a real card would take, plus a headline
   and a next step — an empty screen should still look designed. */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  text-align: center;
  color: var(--ink-faint);
  padding: var(--s-11) var(--s-8);
  border: 1.5px dashed var(--line);
  border-radius: var(--radius);
  font-size: var(--t-meta);
  font-weight: 600;
  background: var(--card);
}

.empty strong {
  display: block;
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h6);
  line-height: 1.1;
  color: var(--ink);
}

.empty a {
  color: var(--brand-text);
  font-weight: 800;
}

/* An announcement's link row — a real link, or the demo chip below. */
.announce__link {
  margin: var(--s-4) 0 0;
  font-size: var(--t-caption);
}

.announce__link a {
  color: var(--brand-text);
  font-weight: 800;
}

/* A PLACEHOLDER destination, drawn as a fact rather than as a link that
   404s (see src/lib/links.ts). Deliberately not blue, not underlined, and not
   a control: nothing about it invites the tap that used to fail. */
.demo-link {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: 1px dashed var(--line);
  border-radius: 999px;
  padding: 0.3rem var(--s-4);
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--ink-faint);
  background: var(--paper);
  cursor: default;
}

/* ------------------------------------------------ today's board (class club) */

.board-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.board-card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  background: var(--brand);
  color: var(--brand-ink);
  padding: var(--s-5) var(--s-6);
}

.board-card__club {
  font-family: var(--font-display-stack);
  font-size: var(--t-h4);
  line-height: 1;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.board-card__month {
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.85;
}

.board-record {
  margin: 0;
  padding: var(--s-4) var(--s-6);
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--brand-text);
  background: var(--brand-soft);
  border-bottom: 1px solid var(--line);
}

.board-list {
  list-style: none;
  margin: 0;
  padding: var(--s-2) var(--s-6);
}

.board-row {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--t-sm);
  font-weight: 600;
}

.board-list .board-row:last-child {
  border-bottom: none;
}

.board-row__rank {
  font-family: var(--font-display-stack);
  font-size: var(--t-body);
  min-width: 1.75rem;
  text-align: center;
  color: var(--ink-faint);
  flex-shrink: 0;
}

.board-row--top .board-row__rank {
  color: var(--brand-ink);
  background: var(--brand);
  border-radius: 50%;
  width: 1.75rem;
  height: 1.75rem;
  line-height: 1.75rem;
}

.board-row__name {
  flex: 1 1;
  min-width: 0;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.board-row--top .board-row__name {
  font-weight: 700;
}

.board-row__count {
  font-family: var(--font-display-stack);
  font-size: var(--t-h6);
  line-height: 1;
  color: var(--brand-text);
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
  flex-shrink: 0;
}

.board-row__count small {
  font-family: var(--font-body-stack);
  font-size: var(--t-nano);
  font-weight: 700;
  color: var(--ink-faint);
}

.board-card__note {
  margin: 0;
  padding: var(--s-4) var(--s-6) var(--s-5);
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

/* Ranks 4+ behind a disclosure — the podium is what people scan, and ten rows
   under the workout turned WOD into a scroll. A real <summary>, so it opens
   with no JS and keeps a native keyboard affordance; the marker is replaced
   with a chevron drawn from the same ink as the rest of the card. */
.board-more {
  border-top: 1px solid var(--line);
}

.board-more__toggle {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding: var(--s-3) var(--s-6);
  font-size: var(--t-caption);
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--brand-text);
  cursor: pointer;
  list-style: none;
}

.board-more__toggle::-webkit-details-marker {
  display: none;
}

.board-more__toggle::after {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
}

.board-more[open] .board-more__toggle::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}

.board-more .board-list {
  border-top: 1px solid var(--line);
}

/* ----------------------------------------------------------------- payroll */

.payroll-state {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.9rem;
  flex-wrap: wrap;
  margin-bottom: 1.1rem;
}

.payroll-state__word {
  margin: 0;
  font-size: 0.92rem;
}

.badge--sent {
  background: var(--ok-soft);
  color: var(--ok);
  font-size: 0.8rem;
  padding: 0.35rem 0.75rem;
}

.payroll-section {
  margin-top: var(--s-11);
}

.payroll-section__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-3);
}

.payroll-section__head .who {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.payroll-section__account {
  font-family: var(--font-display-stack);
  font-size: var(--t-body-lg);
  line-height: 1.15;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

/* The settlement total is the number an owner scans for — Anton, 30px. */
.payroll-section__total {
  font-family: var(--font-display-stack);
  font-size: var(--t-title);
  line-height: 1;
  color: var(--brand-text);
  text-align: right;
}

.payroll-section__total small {
  display: block;
  font-family: var(--font-body-stack);
  font-size: var(--t-micro);
  font-weight: 700;
  color: var(--ink-faint);
  letter-spacing: 0;
  margin-top: var(--s-1);
}

.payroll-table-wrap {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow-x: auto;
}

.payroll-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-meta);
  font-weight: 600;
}

.payroll-table th,
.payroll-table td {
  padding: var(--s-4) var(--s-5);
  text-align: left;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

.payroll-table thead th {
  font-size: var(--t-nano);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 800;
}

.payroll-table tbody tr:last-child td {
  border-bottom: none;
}

.payroll-table .num {
  text-align: right;
}

.payroll-table__coach {
  font-weight: 700;
}

/* The session count under a private-pay figure — the amount is the number
   being paid, the count is only how it was arrived at. */
.payroll-table__sub {
  display: block;
  font-size: var(--t-nano);
  font-weight: 600;
  color: var(--ink-faint);
}

.badge--emp-w2 {
  background: var(--brand-soft);
  color: var(--brand-text);
}

.badge--emp-1099 {
  background: var(--warn-soft);
  color: var(--warn);
}

.payroll-grand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-top: var(--s-6);
  font-size: var(--t-meta);
  font-weight: 700;
}

.payroll-grand strong {
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-display);
  line-height: 1;
  color: var(--brand-text);
}

.payroll-seam-note {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-soft);
  margin: 0 0 var(--s-3);
}

.payload-preview {
  background: var(--ink);
  color: var(--dark-ink);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
  font-size: 0.72rem;
  line-height: 1.5;
  overflow-x: auto;
  max-height: 24rem;
  overflow-y: auto;
}

.payroll-nav {
  display: flex;
  gap: var(--s-2);
  margin: 0 0 var(--s-7);
  padding-bottom: var(--s-1);
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scrollbar-width: thin;
}

.payroll-nav__link {
  min-height: var(--tap);
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  color: var(--ink-soft);
  background: var(--card);
  font-size: var(--t-meta);
  font-weight: 800;
  text-decoration: none;
}

.payroll-nav__link[aria-current="page"] {
  color: var(--brand-text);
  border-color: var(--brand);
  background: var(--brand-soft);
}

.payroll-period {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  margin-bottom: var(--s-6);
  padding: var(--card-pad);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow);
}

.payroll-period__label {
  margin: 0;
  font-family: var(--font-display-stack);
  font-size: var(--t-h2);
  line-height: 1.05;
}

.payroll-period__meta {
  margin: var(--s-1) 0 0;
  color: var(--ink-faint);
  font-size: var(--t-caption);
  font-weight: 700;
}

.payroll-period__controls {
  display: flex;
  gap: var(--s-2);
  flex-wrap: wrap;
  justify-content: flex-end;
}

.payroll-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  grid-gap: var(--s-3);
  gap: var(--s-3);
  margin-bottom: var(--s-8);
}

.payroll-kpi {
  min-width: 0;
  padding: var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow);
}

.payroll-kpi__value {
  display: block;
  font-family: var(--font-display-stack);
  font-size: clamp(1.6rem, 5vw, var(--t-title));
  line-height: 1;
  overflow-wrap: anywhere;
}

.payroll-kpi--accent .payroll-kpi__value {
  color: var(--brand-text);
}

.payroll-kpi__label {
  display: block;
  margin-top: var(--s-2);
  color: var(--ink-faint);
  font-size: var(--t-caption);
  font-weight: 700;
}

.payroll-exceptions,
.payroll-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  grid-gap: var(--s-3);
  gap: var(--s-3);
  margin-bottom: var(--s-7);
}

.payroll-exception {
  padding: var(--s-5);
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--card);
}

.payroll-exception--error {
  border-left-color: var(--bad);
}

.payroll-exception--warning {
  border-left-color: var(--warn);
}

.payroll-exception p {
  margin: var(--s-3) 0;
  font-size: var(--t-meta);
  font-weight: 600;
}

.payroll-exception a,
.payroll-table__link {
  color: var(--brand-text);
  font-weight: 800;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}

.payroll-provider {
  display: grid;
  grid-gap: var(--s-4);
  gap: var(--s-4);
  margin-bottom: var(--s-7);
  border-left: 4px solid var(--warn);
}

.payroll-provider--ready {
  border-left-color: var(--ok);
}

.payroll-provider p {
  margin: var(--s-1) 0 0;
  font-size: var(--t-meta);
  font-weight: 600;
}

.payroll-table--run,
.payroll-table--history,
.payroll-table--lines,
.payroll-table--staff {
  min-width: 58rem;
}

.payroll-table__description {
  min-width: 16rem;
  max-width: 26rem;
  white-space: normal !important;
}

.payroll-amount--negative {
  color: var(--bad);
}

.payroll-amount--positive {
  color: var(--ok);
}

.payroll-section__foot {
  margin-top: var(--s-3);
}

.payroll-technical {
  margin-top: var(--s-5);
}

.payroll-technical summary {
  min-height: var(--tap);
  display: flex;
  align-items: center;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: var(--t-meta);
  font-weight: 800;
}

.payroll-technical[open] summary {
  margin-bottom: var(--s-3);
}

.payroll-backlinks {
  display: flex;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
}

.payroll-snapshot {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}

.payroll-snapshot p {
  margin: 0;
  color: var(--ink-faint);
  font-size: var(--t-meta);
  font-weight: 600;
}

.payroll-badge--warn {
  color: var(--warn);
  background: var(--warn-soft);
}

.payroll-badge--error {
  color: var(--bad);
  background: var(--bad-soft);
}

.payroll-history__actions {
  margin-bottom: var(--s-5);
}

.payroll-summary-card,
.payroll-form-card,
.payroll-boundary-card {
  margin-bottom: var(--s-7);
}

.payroll-detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  grid-gap: 0 var(--s-6);
  gap: 0 var(--s-6);
}

.payroll-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: end;
  grid-gap: var(--s-4);
  gap: var(--s-4);
}

.payroll-form__wide {
  grid-column: 1 / -1;
}

.payroll-form input,
.payroll-form select,
.payroll-form textarea,
.payroll-inline-form input {
  width: 100%;
  min-width: 0;
  min-height: var(--tap);
  padding: 0.7rem 0.8rem;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--card);
  color: var(--ink);
  font: inherit;
}

.payroll-form textarea {
  min-height: 5rem;
  resize: vertical;
}

.payroll-form .btn,
.payroll-inline-form .btn {
  justify-self: start;
}

.payroll-adjustment,
.payroll-private-split {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.payroll-adjustment p,
.payroll-private-split p {
  margin: var(--s-2) 0 0;
  font-size: var(--t-meta);
}

.payroll-adjustment__amount {
  font-family: var(--font-display-stack);
  font-size: var(--t-title);
}

.payroll-inline-form {
  display: grid;
  grid-gap: var(--s-3);
  gap: var(--s-3);
  margin-top: auto;
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}

.payroll-private-split h3,
.payroll-form-card h3,
.payroll-config-columns h3 {
  margin: 0;
  font-size: var(--t-body-lg);
}

.payroll-cadence-guide {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: var(--s-3);
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}

.payroll-cadence-guide p {
  margin: 0;
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
  color: var(--ink-soft);
  font-size: var(--t-caption);
  font-weight: 600;
}

.payroll-cadence-guide strong {
  display: block;
  margin-bottom: var(--s-1);
  color: var(--ink);
}

.payroll-config-columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-gap: var(--s-6);
  gap: var(--s-6);
}

.payroll-config-list {
  list-style: none;
  margin: var(--s-3) 0 0;
  padding: 0;
}

.payroll-config-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  min-height: var(--tap);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--line);
}

.payroll-config-list code {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--ink-soft);
  font-size: var(--t-micro);
}

.payroll-boundary-card p {
  color: var(--ink-soft);
  font-size: var(--t-meta);
  font-weight: 600;
}

@media (max-width: 700px) {
  .payroll-period {
    align-items: stretch;
    flex-direction: column;
  }

  .payroll-period__controls {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
  }

  .payroll-period__controls .btn {
    padding-inline: var(--s-2);
    text-align: center;
  }

  .payroll-form,
  .payroll-cadence-guide,
  .payroll-config-columns {
    grid-template-columns: 1fr;
  }

  .payroll-form__wide {
    grid-column: auto;
  }

  .payroll-snapshot {
    align-items: flex-start;
    flex-direction: column;
  }
}

/* --------------------------------------------- membership picker + sheet */

.term-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: var(--s-4);
  gap: var(--s-4);
}

.workout-row__actions {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
}

/* A term is a priced offer, so the PRICE is the hero of the tile — Anton
   at 30px, with the term itself as a quiet 12px eyebrow above it. */
.term-card {
  font: inherit;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  background: var(--card);
  border: 1.5px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--s-6);
  cursor: pointer;
  color: var(--ink);
  transition: border-color 120ms ease;
}

.term-card:hover {
  border-color: var(--brand);
}

.term-card--readonly {
  cursor: default;
}

.term-card--readonly:hover {
  border-color: var(--line);
}

.term-card__months {
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.term-card__price {
  font-family: var(--font-display-stack);
  font-size: var(--t-title);
  line-height: 1.05;
  color: var(--brand-text);
  margin-top: var(--s-1);
}

.term-card__per {
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

.term-card__cta {
  margin-top: var(--s-3);
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-caption);
  font-weight: 800;
  color: var(--brand-text);
}

/* A NATIVE <dialog>, so this selector is mostly undoing what a dialog is by
   default — the UA sheet gives it a border, a white background, its own width
   and an auto margin, all of which fight a full-bleed scrim. What we keep is
   the modality: ::backdrop, the focus trap and Escape come with showModal()
   and are the reason this is a dialog and not the div it used to be.

   The --static variant below is NOT a dialog. It is a plain section on the
   rates page, so nothing here may depend on [open]. */
.sheet-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: none;
  background: none;
  overflow: visible;
  color: var(--ink);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

/* `display: flex` above beats the UA's `dialog:not([open]) { display: none }`,
   so the closed state has to be restated or the sheet flashes on the frame
   before showModal() runs. Scoped to the element: the static variant is a div
   and would match a bare :not([open]). */
dialog.sheet-overlay:not([open]) {
  display: none;
}

.sheet-overlay::-webkit-backdrop {
  background: rgb(19 20 23 / 0.45);
  animation: sheet-scrim 0.18s ease both;
}

.sheet-overlay::backdrop {
  background: rgb(19 20 23 / 0.45);
  animation: sheet-scrim 0.18s ease both;
}

@keyframes sheet-scrim {
  from {
    opacity: 0;
  }
}

.checkout-sheet {
  width: 100%;
  max-width: 26rem;
  max-height: 88dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--card);
  border-radius: var(--radius) var(--radius) 0 0;
  padding: 0.35rem 1.25rem calc(1.25rem + env(safe-area-inset-bottom));
  box-shadow: 0 -8px 40px rgb(19 20 23 / 0.25);
  /* The browser's own vertical panning is the sheet's scroll; the drag that
     dismisses starts on the handle, which opts out of it. Without this the
     UA claims the gesture and our pointermove stream stops mid-pull. */
  touch-action: pan-y;
}

.sheet-overlay[open] .checkout-sheet {
  animation: sheet-rise 0.24s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

@keyframes sheet-rise {
  from {
    transform: translateY(12%);
    opacity: 0;
  }
}

/* The entrance is decoration and goes. The spring-back after a drag does not:
   it is the panel answering a finger that is still on the screen, and cutting
   it leaves the sheet teleporting instead of returning. */
@media (prefers-reduced-motion: reduce) {
  .sheet-overlay::-webkit-backdrop, .drawer::-webkit-backdrop {
    animation: none;
  }
  .sheet-overlay[open] .checkout-sheet,
  .sheet-overlay::backdrop,
  .drawer[open] .drawer__panel,
  .drawer::backdrop {
    animation: none;
  }
}

/* ── THE GRAB HANDLE ──────────────────────────────────────────────────────
   The visible half of pull-to-dismiss. The bar is small; the target around it
   is not — 100% wide and ~28px tall, because the gesture is started with a
   thumb in motion, not aimed.

   `touch-action: none` is load-bearing: it tells the browser to send us the
   vertical gesture instead of scrolling the sheet with it. */
.sheet-grab {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 1.65rem;
  margin: 0 -1.25rem 0.25rem;
  cursor: -webkit-grab;
  cursor: grab;
  touch-action: none;
}

.sheet-grab:active {
  cursor: -webkit-grabbing;
  cursor: grabbing;
}

.sheet-grab > span {
  display: block;
  width: 2.4rem;
  height: 0.28rem;
  border-radius: 999px;
  background: var(--line-strong, var(--line));
}

/* On a wide window the sheet is a centred card, so there is nothing to pull
   down to and the handle would be a lie. */
@media (min-width: 760px) {
  .sheet-grab {
    display: none;
  }
}

@media (min-width: 760px) {
  .sheet-overlay {
    align-items: center;
    padding: 2rem 1rem;
  }

  .checkout-sheet {
    border-radius: var(--radius);
  }
}

.checkout-sheet__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.9rem;
}

.checkout-sheet__product {
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h3);
  line-height: 1.05;
  letter-spacing: 0;
  margin: 0;
}

.checkout-sheet__price {
  font-size: var(--t-meta);
  font-weight: 700;
  margin: var(--s-1) 0 0;
}

.checkout-sheet__price span {
  color: var(--ink-faint);
  font-size: var(--t-caption);
  font-weight: 600;
}

.checkout-sheet__close {
  font: inherit;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink-soft);
  border-radius: 999px;
  width: 1.9rem;
  height: 1.9rem;
  cursor: pointer;
  flex-shrink: 0;
}

/* -- the plan-change review (step one of the sheet) --------------------- */

/* A LEDGER, not prose: label left, value right, one fact per line, so
   "starts", "current pass", "charged today" and "next charge" can each be
   read in isolation. On a 375px phone the pair stacks — a two-column money
   row that wraps mid-value is worse than one that never tried. */
.review {
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--paper);
  padding: var(--s-2) var(--s-6);
  margin-bottom: var(--s-6);
}

.review-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5) 0;
}

.review-row + .review-row {
  border-top: 1px solid var(--line);
}

.review-row__label {
  font-size: var(--t-nano);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  flex: none;
}

.review-row__value {
  text-align: right;
  font-size: var(--t-meta);
  font-weight: 700;
  min-width: 0;
}

.review-row__value small {
  display: block;
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
  margin-top: 0.1rem;
}

@media (max-width: 380px) {
  .review-row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-1);
  }

  .review-row__value {
    text-align: left;
  }
}

/* The quiet way back — out of the review, or out of the payment step and
   into the review again. Never styled as the primary action. */
.checkout-sheet__back {
  font: inherit;
  display: block;
  width: 100%;
  margin-top: var(--s-4);
  background: transparent;
  border: none;
  min-height: var(--tap);
  font-size: var(--t-meta);
  font-weight: 700;
  color: var(--ink-faint);
  cursor: pointer;
}

.checkout-sheet__back:hover {
  color: var(--ink);
}

.rail-tabs {
  display: flex;
  gap: 2px;
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--paper);
  padding: 3px;
  margin-bottom: 1rem;
}

.rail-tab {
  flex: 1 1;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 700;
  border: none;
  background: transparent;
  color: var(--ink-faint);
  border-radius: calc(var(--radius-small) - 3px);
  padding: 0.5rem 0.6rem;
  cursor: pointer;
}

.rail-tab[aria-selected="true"] {
  background: var(--brand);
  color: var(--brand-ink);
}

.checkout-sheet__row {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-gap: 0.6rem;
  gap: 0.6rem;
}

/* Inputs default to an intrinsic min-width that overflows a 3-up grid on a
   narrow phone; let the grid own the width. */
.checkout-sheet__row input {
  min-width: 0;
  width: 100%;
}

.checkout-sheet__row--two {
  grid-template-columns: 1fr 1fr;
}

.checkout-sheet__confirm {
  width: 100%;
  margin-top: 1rem;
}

.checkout-sheet select {
  font: inherit;
  padding: 0.7rem 0.8rem;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--card);
  color: var(--ink);
}

.field__error {
  color: var(--bad);
  font-weight: 600;
  font-size: 0.8rem;
}

/* ---------------------------------------------------------------- toasts */

.toast-stack {
  position: fixed;
  bottom: calc(4.2rem + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: min(92vw, 22rem);
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  background: var(--ink);
  color: var(--dark-ink);
  border-radius: var(--radius-small);
  padding: 0.6rem 0.8rem;
  font-size: 0.88rem;
  box-shadow: var(--shadow);
}

.toast--error {
  background: var(--bad);
}

.toast__dismiss {
  font: inherit;
  background: transparent;
  border: none;
  color: inherit;
  opacity: 0.7;
  cursor: pointer;
  padding: 0.1rem 0.25rem;
}

.toast__dismiss:hover {
  opacity: 1;
}

@media (min-width: 760px) {
  .toast-stack {
    bottom: 1.5rem;
  }
}

/* =========================================================================
   RATES · SHOP · VIDEOS · BLOG · the owner's hub
   -------------------------------------------------------------------------
   The five surfaces this wave added, in the SAME language as everything
   above: --gutter/--card-pad spacing, Anton (--font-display-stack) for the
   one number or word that is the point of a card, Archivo for the rest,
   ONE brand accent driven by [data-gym]. No new hex, no new radius, no new
   type step — a second designer's opinion bolted on is the failure mode the
   client already named once.
   ========================================================================= */

/* --------------------------------------------------------- shared bits */

/* A quiet explanatory line under a section title. Sits where a lede would
   if the section were a page. */
.page__footnote {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-faint);
  margin: calc(-1 * var(--s-2)) 0 var(--s-4);
}

.empty--tight {
  padding: var(--s-7) var(--s-6);
}

/* The prev/next row under a long list. */
.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-top: var(--s-8);
}

.pager__count {
  font-size: var(--t-meta);
  font-weight: 700;
  color: var(--ink-faint);
}

/* A search box + its button on one line, wrapping on a narrow phone. */
.searchbar {
  display: flex;
  align-items: flex-end;
  gap: var(--s-3);
  margin: var(--s-5) 0;
}

.searchbar .field {
  flex: 1 1;
  min-width: 0;
}

/* An overlay that is part of the PAGE rather than a client-side modal: the
   rate sheets are plain forms reached by a query param, so there is no
   backdrop click to dismiss and the sheet must not trap scroll. */
.sheet-overlay--static {
  position: static;
  background: none;
  padding: 0;
  display: block;
  height: auto;
  margin-bottom: var(--s-8);
}

/* No scrim, no modality, nothing to pull. */
.sheet-overlay--static .sheet-grab {
  display: none;
}

.sheet-overlay--static .checkout-sheet {
  padding-top: 1.15rem;
}

/* ------------------------------------------------------------- the hub */

.hub {
  list-style: none;
  margin: var(--s-6) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.hub__row {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--card-pad);
  text-decoration: none;
  color: var(--ink);
  min-height: var(--tap);
}

.hub__row:hover {
  border-color: var(--brand);
}

.hub__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-small);
  background: var(--brand-soft);
  color: var(--brand-text);
}

.hub__icon svg {
  width: 21px;
  height: 21px;
}

.hub__body {
  flex: 1 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.hub__label {
  font-family: var(--font-display-stack);
  font-size: var(--t-h5);
  line-height: 1.1;
  letter-spacing: 0.01em;
}

.hub__line {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-faint);
  line-height: 1.4;
}

.hub__go {
  flex: none;
  display: inline-flex;
  align-items: center;
  color: var(--brand-text);
}

/* ------------------------------------------------------------- rates */

.rate-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--card-pad);
  margin-bottom: var(--s-8);
}

.rate-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-5);
}

.rate-card__name {
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h4);
  line-height: 1.1;
  margin: 0.1rem 0 0;
}

/* TODAY'S RATE IS THE HERO of this card — it is the number the whole screen
   is about, so it takes the display face at 38px like the KPI tiles. */
.rate-card__now {
  text-align: right;
  flex: none;
}

.rate-card__amount {
  display: block;
  font-family: var(--font-display-stack);
  font-size: var(--t-display);
  line-height: 1;
  color: var(--brand-text);
}

.rate-card__now small {
  display: block;
  font-size: var(--t-micro);
  font-weight: 700;
  color: var(--ink-faint);
  margin-top: var(--s-1);
}

.rate-card__billing {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-soft);
  margin: var(--s-3) 0 0;
}

.rate-card__sub {
  font-size: var(--t-nano);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 800;
  color: var(--ink-faint);
  margin: var(--s-8) 0 var(--s-3);
}

/* Who sits on which rate — the count that makes grandfathering concrete. */
.rate-spread {
  list-style: none;
  margin: var(--s-5) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.rate-spread li {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--paper);
  border-radius: var(--radius-small);
}

.rate-spread__price {
  font-weight: 800;
  font-size: var(--t-body);
}

.rate-spread__who {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-faint);
}

.rate-history {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.rate-history li {
  display: flex;
  gap: var(--s-4);
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--line);
}

.rate-history li:last-child {
  border-bottom: none;
}

.rate-history__v {
  flex: none;
  width: 2.25rem;
  font-family: var(--font-display-stack);
  font-size: var(--t-lg);
  line-height: 1.2;
  color: var(--ink-faint);
}

.rate-history__body {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.rate-history__price {
  font-weight: 800;
  font-size: var(--t-body);
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
}

/* An increase and a decrease must not look alike on a money screen. */
.rate-history__price em {
  font-style: normal;
  font-size: var(--t-micro);
  font-weight: 800;
  padding: 0.1rem 0.35rem;
  border-radius: 999px;
}

.rate-history__price em.up {
  background: var(--warn-soft);
  color: var(--warn);
}

.rate-history__price em.down {
  background: var(--ok-soft);
  color: var(--ok);
}

.rate-history__when,
.rate-history__note {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-faint);
}

/* The member's own rate, on their membership screen. Tinted rather than
   carded: it is a fact ABOUT the card above it, not a second card. */
.rate-note {
  background: var(--brand-soft);
  border-radius: var(--radius);
  padding: var(--card-pad);
  margin-bottom: var(--s-6);
}

.rate-note__label {
  font-size: var(--t-nano);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 800;
  color: var(--brand-text);
  margin: 0 0 var(--s-2);
}

.rate-note__line {
  font-size: var(--t-body);
  font-weight: 700;
  line-height: 1.45;
  margin: 0;
}

.rate-note__sub {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-soft);
  line-height: 1.45;
  margin: var(--s-2) 0 0;
}

/* ------------------------------------------------------------- merch */

.merch-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: var(--s-4);
  gap: var(--s-4);
  margin: var(--s-5) 0;
}

.shop-tools {
  display: grid;
  grid-gap: var(--s-3);
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}

.shop-search {
  position: relative;
  display: block;
}

.shop-search__glyph {
  position: absolute;
  left: 0.85rem;
  top: 50%;
  width: 1.1rem;
  height: 1.1rem;
  transform: translateY(-50%);
  color: var(--ink-faint);
  pointer-events: none;
}

.shop-search input {
  width: 100%;
  min-height: 3rem;
  padding: 0.7rem 0.9rem 0.7rem 2.65rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-weight: 650;
}

.shop-categories {
  display: flex;
  gap: var(--s-2);
  overflow-x: auto;
  scrollbar-width: none;
}

.shop-categories::-webkit-scrollbar {
  display: none;
}

.shop-category {
  min-height: 2.35rem;
  padding: 0.4rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink-soft);
  font: inherit;
  font-size: var(--t-caption);
  font-weight: 800;
  white-space: nowrap;
}

.shop-category[aria-pressed="true"] {
  border-color: var(--brand);
  background: var(--brand);
  color: var(--brand-ink);
}

.merch-art {
  position: relative;
  display: grid;
  place-items: center;
  overflow: hidden;
  background:
    radial-gradient(circle at 70% 25%, rgb(255 255 255 / 0.75), transparent 26%),
    linear-gradient(145deg, color-mix(in srgb, var(--brand) 22%, white), #f4f1e9);
}

.merch-art::after {
  content: "";
  position: absolute;
  width: 70%;
  height: 18%;
  left: 15%;
  bottom: 8%;
  border-radius: 50%;
  background: rgb(22 27 20 / 0.12);
  filter: blur(8px);
}

.merch-art svg {
  position: relative;
  z-index: 1;
  width: 76%;
  height: 76%;
  fill: var(--ink);
  stroke: var(--paper);
  stroke-width: 3;
  stroke-linejoin: round;
}

.merch-art > span {
  position: absolute;
  z-index: 2;
  top: 48%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: var(--brand);
  font-family: var(--font-display-stack);
  font-size: 1.15rem;
  text-shadow: 0 1px var(--ink);
}

.order-list {
  list-style: none;
  padding: 0;
  overflow: hidden;
}

.order-list li + li {
  border-top: 1px solid var(--line);
}

.order-list button {
  width: 100%;
  min-height: 4.25rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  border: 0;
  background: transparent;
  color: var(--ink);
  text-align: left;
  font: inherit;
}

.order-list button > span:first-child {
  display: grid;
  grid-gap: 0.15rem;
  gap: 0.15rem;
}

.order-list__open {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--brand-text);
  font-size: var(--t-caption);
  font-weight: 850;
}

.order-list__open svg {
  width: 1.1rem;
  height: 1.1rem;
}

.receipt-sheet {
  display: grid;
  grid-gap: var(--s-5);
  gap: var(--s-5);
}

.receipt-lines {
  margin: 0;
  border-top: 1px solid var(--line);
}

.receipt-lines > div {
  display: flex;
  justify-content: space-between;
  gap: var(--s-5);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--line);
}

.receipt-lines dt {
  color: var(--ink-faint);
  font-size: var(--t-caption);
  font-weight: 750;
}

.receipt-lines dd {
  margin: 0;
  color: var(--ink);
  font-size: var(--t-caption);
  font-weight: 800;
  text-align: right;
}

.receipt-lines__total dt,
.receipt-lines__total dd {
  color: var(--ink);
  font-size: var(--t-body);
}

.merch-card {
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.merch-card--retired {
  opacity: 0.62;
}

.merch-card__photo {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  background: var(--paper);
}

/* No photo? A branded monogram tile, never a broken image icon. */
.merch-card__photo--mono {
  display: grid;
  place-items: center;
  background: var(--brand-soft);
}

.merch-card__photo--mono span {
  font-family: var(--font-display-stack);
  font-size: var(--t-hero);
  line-height: 1;
  color: var(--brand-text);
  letter-spacing: 0.02em;
}

.merch-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-5);
  flex: 1 1;
}

.merch-card__name {
  font-weight: 800;
  font-size: var(--t-body);
  line-height: 1.25;
  margin: 0;
}

.merch-card__price {
  font-family: var(--font-display-stack);
  font-size: var(--t-h4);
  line-height: 1;
  color: var(--brand-text);
  margin: 0;
}

.merch-card__desc {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-faint);
  line-height: 1.4;
  margin: 0;
}

.merch-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-micro);
  font-weight: 700;
  color: var(--ink-faint);
  margin: 0;
  margin-top: auto;
}

.merch-card__body .btn {
  margin-top: var(--s-3);
}

.merch-card .actions {
  margin-top: var(--s-3);
}

.merch-form {
  margin-bottom: var(--s-6);
}

.merch-form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: var(--s-4);
  gap: var(--s-4);
}

.merch-form__row input {
  min-width: 0;
  width: 100%;
}

.merch-form textarea,
.editor {
  font: inherit;
  padding: 0.7rem 0.8rem;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--card);
  color: var(--ink);
  resize: vertical;
}

/* A size is a tap target, so it is a button and it clears 44px. */
.size-picker {
  border: none;
  margin: 0 0 var(--s-5);
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.size-picker legend {
  width: 100%;
  margin-bottom: var(--s-2);
}

.size-picker__opt {
  font: inherit;
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0 var(--s-4);
  font-weight: 800;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
}

.size-picker__opt[aria-pressed="true"] {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
}

.qty {
  margin-bottom: var(--s-5);
}

.qty__controls {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-2);
}

.qty__step {
  font: inherit;
  width: var(--tap);
  height: var(--tap);
  font-size: var(--t-h5);
  font-weight: 800;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
}

.qty__step:disabled {
  opacity: 0.4;
  cursor: default;
}

.qty__value {
  font-family: var(--font-display-stack);
  font-size: var(--t-h3);
  line-height: 1;
  min-width: 2rem;
  text-align: center;
}

.qty__cap {
  font-size: var(--t-meta);
  font-weight: 700;
  color: var(--ink-faint);
}

/* ------------------------------------------------------------ videos */

.movement-rail {
  display: flex;
  gap: var(--s-2);
  overflow-x: auto;
  padding: var(--s-1) 0 var(--s-4);
  /* The rail scrolls past the page gutter so the last chip is not clipped
     against the screen edge. */
  margin: 0 calc(-1 * var(--gutter));
  padding-left: var(--gutter);
  padding-right: var(--gutter);
  scrollbar-width: none;
}

.movement-rail::-webkit-scrollbar {
  display: none;
}

.movement-rail--quiet {
  flex-wrap: wrap;
  overflow: visible;
}

.movement-chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding: 0 var(--s-5);
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  text-decoration: none;
  font-size: var(--t-meta);
  font-weight: 700;
  white-space: nowrap;
}

.movement-chip small {
  font-size: var(--t-nano);
  font-weight: 800;
  color: var(--ink-faint);
}

.movement-chip[aria-current="true"] {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
}

.movement-chip[aria-current="true"] small {
  color: var(--brand-ink);
  opacity: 0.75;
}

.movement-chip--empty {
  border-style: dashed;
  color: var(--ink-faint);
}

.video-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: var(--s-4);
  gap: var(--s-4);
  margin: var(--s-4) 0;
}

/* A library tile is a thumbnail first: the whole point of this tab is
   recognising a movement at a glance. */
.video-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
  text-decoration: none;
  color: var(--ink);
}

.video-card:hover {
  border-color: var(--brand);
}

.video-card--dead {
  opacity: 0.6;
}

.video-card__thumb {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: var(--ink);
}

.video-card__thumb--none {
  background: var(--brand-soft);
}

.video-card__play {
  position: absolute;
  top: calc(37.5% - 1.1rem);
  left: 50%;
  transform: translateX(-50%);
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 999px;
  background: rgb(19 20 23 / 0.62);
  color: #fff;
  font-size: 0.85rem;
  padding-left: 2px;
}

.video-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-5);
  flex: 1 1;
}

.video-card__title {
  font-weight: 700;
  font-size: var(--t-meta);
  line-height: 1.3;
}

.video-card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
  margin-top: auto;
}

.tagpill {
  font-size: var(--t-nano);
  font-weight: 800;
  letter-spacing: 0.02em;
  padding: 0.15rem 0.4rem;
  border-radius: 999px;
  background: var(--brand-soft);
  color: var(--brand-text);
}

.tagpill--pending {
  background: var(--warn-soft);
  color: var(--warn);
}

.tagpill--none {
  background: var(--paper);
  color: var(--ink-faint);
}

/* The review queue: a question, the reason it is a question, two buttons. */
.queue {
  list-style: none;
  margin: 0 0 var(--s-8);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.queue__row {
  display: flex;
  gap: var(--s-4);
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--s-5);
}

.queue__thumb {
  flex: none;
  width: 5rem;
  height: 3.75rem;
  object-fit: cover;
  border-radius: var(--radius-small);
  background: var(--ink);
}

.queue__body {
  flex: 1 1;
  min-width: 0;
}

.queue__q {
  font-size: var(--t-body);
  font-weight: 600;
  line-height: 1.35;
  margin: 0 0 var(--s-2);
}

.queue__why {
  font-size: var(--t-micro);
  font-weight: 700;
  color: var(--warn);
  margin: 0 0 var(--s-3);
}

/* The WOD's per-movement demo: a small still with a play mark, beside the
   movement name. This is the loop that makes the board self-explanatory, so
   it gets a picture rather than the word DEMO. */
.wod-move__thumb {
  position: relative;
  flex: none;
  display: block;
  width: 3.5rem;
  height: 2.625rem;
  border-radius: 8px;
  overflow: hidden;
  background: var(--ink);
  /* No margin: .wod-move is a flex row with its own gap token. */
}

.wod-move__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.wod-move__thumb span {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: #fff;
  font-size: 0.7rem;
  background: rgb(19 20 23 / 0.35);
  padding-left: 1px;
}

.wod-move__name {
  flex: 1 1;
  min-width: 0;
}

/* -------------------------------------------------------------- blog */

.postlist {
  list-style: none;
  margin: var(--s-5) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.post-card {
  position: relative;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.post-card__link {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--card-pad);
  text-decoration: none;
  color: var(--ink);
}

.post-card__link:hover .post-card__title {
  color: var(--brand-text);
}

.post-card__meta {
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.post-card__title {
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h4);
  line-height: 1.1;
}

.post-card__excerpt {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-faint);
  line-height: 1.45;
}

/* The archive is long and skimmed by date, so its rows lose the excerpt. */
.postlist--tight .post-card__excerpt {
  display: none;
}

.postlist--tight .post-card__title {
  font-size: var(--t-h6);
  font-family: var(--font-body-stack);
  font-weight: 800;
}

.post-card__edit {
  position: absolute;
  top: var(--s-4);
  right: var(--s-5);
  font-size: var(--t-micro);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
}

/* Post bodies. Rendered as elements by src/components/Markdown.tsx — never
   as HTML — so this styles a known, closed set of tags. */
.prose {
  margin: var(--s-6) 0;
  font-size: var(--t-body-lg);
  line-height: 1.62;
  color: var(--ink-soft);
  /* A measure, so a wide window does not produce 140-character lines. */
  max-width: 38rem;
}

.prose > * + * {
  margin-top: var(--s-6);
}

.prose h2,
.prose h3 {
  font-family: var(--font-display-stack);
  font-weight: 400;
  color: var(--ink);
  line-height: 1.15;
  margin-top: var(--s-11);
}

.prose h2 {
  font-size: var(--t-h3);
}

.prose h3 {
  font-size: var(--t-h5);
}

.prose strong {
  color: var(--ink);
  font-weight: 800;
}

.prose a {
  color: var(--brand-text);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.prose ul,
.prose ol {
  padding-left: 1.25rem;
}

.prose li + li {
  margin-top: var(--s-2);
}

.prose blockquote {
  margin: 0;
  padding-left: var(--s-6);
  border-left: 3px solid var(--brand);
  color: var(--ink);
  font-weight: 600;
}

.prose code {
  font-size: 0.86em;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.05rem 0.3rem;
}

.prose hr {
  border: none;
  border-top: 1px solid var(--line);
}

/* ------------------------------------------- the payroll boundary block */

/* WHAT THE APP DOES NOT DO. Styled as a plain list of limits, not as a
   disclaimer in small print — the whole point is that it gets read. */
.boundary {
  list-style: none;
  margin: var(--s-5) 0 0;
  padding: var(--card-pad);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-soft);
}

.boundary li {
  display: flex;
  gap: var(--s-3);
  line-height: 1.45;
}

.boundary li::before {
  content: "—";
  flex: none;
  color: var(--ink-faint);
}

/* ------------------------------------------------------ wide windows */

@media (min-width: 760px) {
  .merch-grid,
  .video-grid {
    grid-template-columns: repeat(3, 1fr);
  }

  .term-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* At 375px a two-up grid of cards is already tight; one column for merch
   keeps the photo big enough to be worth having. */
@media (max-width: 400px) {
  .merch-grid {
    grid-template-columns: 1fr;
  }
}

/* ------------------------------------------------------------ challenges */

/* A challenge card leads with the FLYER, because the flyer is the artefact —
   the thing Ben made, and the thing a member already recognises off the wall of
   the gym. Everything else on the card is a caption for it. */
.promo {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.promo + .promo {
  margin-top: var(--s-4);
}

.promo__flyer {
  display: block;
  /* A fixed band rather than the poster's own ratio: flyers arrive in every
     shape from a square Instagram tile to a portrait print, and a list where
     each card is a different height is a list nobody can scan. The full poster
     is one tap away, uncropped. */
  aspect-ratio: 16 / 9;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
}

.promo__flyer img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.promo__body {
  padding: var(--card-pad);
}

.promo__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-3);
}

.promo__title {
  font-size: var(--t-h3);
  line-height: 1.2;
  margin: 0 0 var(--s-2);
}

.promo__blurb {
  color: var(--ink-soft);
  margin: 0 0 var(--s-3);
}

.promo__meta {
  font-size: var(--t-meta);
  color: var(--ink-faint);
  margin: 0 0 var(--s-4);
}

.promo__actions {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
}

/* The full poster on the detail page: never cropped, never blown up past its
   own resolution. */
.promo__poster {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  margin: var(--s-5) 0;
}

/* ------------------------------------------------------------- the board */

.board-row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--line);
}

.board-row:last-child {
  border-bottom: 0;
}

/* Your own line, wherever it lands. Finding yourself on a board of sixty is
   the one job this list has. */
.board-row--me {
  background: var(--brand-soft);
  border-radius: var(--radius-small);
  padding-left: var(--s-3);
  padding-right: var(--s-3);
  margin: 0 calc(var(--s-3) * -1);
}

.board-row__place {
  min-width: 1.75rem;
  font-weight: 800;
  font-size: var(--t-meta);
  color: var(--ink-faint);
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
}

.board-row__name {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.board-row__you {
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--brand-text);
}

.board-row__score {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-weight: 800;
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
}

.board-row__done {
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ok);
  background: var(--ok-soft);
  border-radius: 999px;
  padding: 0.16rem 0.5rem;
}

/* ---------------------------------------------------------- the composer */

/* Folded shut by default. The owner reads this page far more often than he
   writes on it, and an open form above the thing he came to look at makes the
   page feel like a form with a list underneath. */
.composer {
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  margin-bottom: var(--s-6);
}

.composer__summary {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-4) var(--s-5);
  font-weight: 800;
  cursor: pointer;
  list-style: none;
  min-height: 44px;
}

.composer__summary::-webkit-details-marker {
  display: none;
}

.composer__glyph {
  width: 18px;
  height: 18px;
  color: var(--brand-text);
}

.composer[open] .composer__summary {
  border-bottom: 1px solid var(--line);
}

.composer .challenge-form {
  border: 0;
  box-shadow: none;
  border-radius: 0;
}

.challenge-form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: var(--s-4);
  gap: var(--s-4);
}

.challenge-form__row input,
.challenge-form__row select {
  min-width: 0;
  width: 100%;
}

.challenge-form__note {
  font-size: var(--t-meta);
  color: var(--ink-faint);
  margin: var(--s-3) 0 0;
}

/* A file input is the one control the app cannot restyle into the house look,
   so it is given room rather than fought with. */
.challenge-form input[type="file"] {
  font-size: var(--t-meta);
  padding: var(--s-3) 0;
}

.section-title__note {
  margin-left: var(--s-2);
  font-weight: 700;
  text-transform: none;
  letter-spacing: 0;
  color: var(--ink-faint);
}

@media (max-width: 400px) {
  .challenge-form__row {
    grid-template-columns: 1fr;
  }
}

/* ---------------------------------------- challenges vs initiatives */

/* The runway badge — "3 days left" / "Ends today" / "Ended". Green while the
   window is open (it is an invitation), line-grey once it is a record. These
   two variants predate this wave in the markup but never had rules, so the
   urgency line rendered in the base badge grey whether it was urgent or not. */
.badge--on {
  background: var(--ok-soft);
  color: var(--ok);
}

.badge--muted {
  background: var(--paper);
  color: var(--ink-faint);
  border: 1px solid var(--line);
}

/* The kind badge says WHICH PROMISE a card is making before the title is
   read: a challenge is something to win, an initiative is something the gym
   is running. Brand tint for the competition, quiet ink for the announcement
   — the same two-tier weighting the rest of the app gives "yours" vs "the
   gym's". */
.badge--kind-challenge {
  background: var(--brand-soft);
  color: var(--brand-text);
}

.badge--kind-initiative {
  background: var(--paper);
  color: var(--ink-soft);
  border: 1px solid var(--line);
}

/* The owner's "somebody is waiting on you" count, on a builder row. Warm,
   because it is the one badge here that asks for an action. */
.badge--waiting {
  background: var(--warn-soft);
  color: var(--warn);
}

/* Where YOU stand on a card, and the one press that changes it. A flex row
   so the state line and its undo share a line where they fit and wrap where
   they do not; the question field takes the full width above the button. */
.optin {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  margin: 0 0 var(--s-4);
}

.optin__question {
  flex: 1 1 100%;
}

.optin__state {
  margin: 0;
  font-size: var(--t-sm);
  font-weight: 700;
}

/* Waiting is patience, not alarm — ink, not warning orange. */
.optin__state--waiting {
  color: var(--ink-soft);
}

.optin__state--declined {
  color: var(--ink-soft);
}

.optin__state--in {
  color: var(--ok);
}

/* The log form's one input — big enough to thumb a number into at the end of
   a workout, tabular so 9:59 and 10:00 line up on re-reads. */
.result-form__value {
  font-size: var(--t-h3);
  font-weight: 800;
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
  max-width: 12rem;
}

/* "3 tries" beside a best — quiet, because the best is the fact. */
.board-row__attempts {
  font-size: var(--t-micro);
  font-weight: 700;
  color: var(--ink-faint);
}

/* ---- the approval queue: one waiting opt-in per row, decisions inline ---- */

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

.queue-row__body {
  flex: 1 1 16rem;
  min-width: 0;
}

.queue-row__who {
  margin: 0 0 var(--s-1);
}

.queue-row__what {
  color: var(--ink-faint);
  font-size: var(--t-meta);
}

/* The member's answer is the thing being judged, so it reads as a quote —
   brand-edged, not boxed. */
.queue-row__answer {
  margin: 0 0 var(--s-1);
  padding-left: var(--s-3);
  border-left: 3px solid var(--brand);
  color: var(--ink-soft);
  font-size: var(--t-sm);
}

.queue-row__prompt {
  display: block;
  font-size: var(--t-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

.queue-row__when {
  margin: 0;
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

.queue-row__actions {
  display: flex;
  gap: var(--s-2);
  flex-wrap: wrap;
}

/* Approve/Decline are row controls but still decisions — full tap height. */
.queue-row__actions .btn {
  min-height: var(--tap);
}

/* ---- the builder list: the desk side of a poster, one compact row each ---- */

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

.engage-row__body {
  flex: 1 1 16rem;
  min-width: 0;
}

.engage-row .promo__tags {
  margin-bottom: var(--s-2);
}

.engage-row__title {
  margin: 0 0 var(--s-1);
  font-weight: 800;
}

.engage-row__title a {
  color: inherit;
  text-decoration: none;
}

.engage-row__title a:hover {
  text-decoration: underline;
}

.engage-row__meta {
  margin: 0;
  font-size: var(--t-meta);
  color: var(--ink-faint);
}

.engage-row__actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
}

.engage-row__actions .btn {
  min-height: var(--tap);
}

/* A checkbox row in the house form look: the box and its label on one line,
   the whole line tappable. */
.field--check {
  flex-direction: row;
  align-items: center;
  gap: var(--s-3);
  min-height: var(--tap);
}

.field--check input[type="checkbox"] {
  width: 1.25rem;
  height: 1.25rem;
  accent-color: var(--brand);
  flex-shrink: 0;
}

@media (max-width: 480px) {
  /* One column on a phone: the facts, then the levers beneath them. */
  .queue-row,
  .engage-row {
    flex-direction: column;
    align-items: stretch;
  }
}

/* The full-width "On now" challenge card that used to sit here in Home's
   flow is GONE (client, this wave) — running challenges live behind the
   trophy spotlight square now (.spotlights / .spot-challenge, end of this
   sheet), where they cost the fold nothing until summoned. */

/* The demo disclosure, as one chip in the header instead of a grey sentence
   under the fold of every page. Yellow because it is the one thing on screen
   that is NOT the product — it should read as a sticker somebody put on top of
   the app, and it comes off in one place when this stops being a demo. */
.demo-chip {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 0.2rem 0.55rem;
  background: var(--warn-soft);
  color: var(--warn);
  border: 1px solid currentColor;
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.08em;
  line-height: 1.2;
  white-space: nowrap;
  cursor: help;
}

/* -- a person, as a circle ---------------------------------------------- */

/* A photo COVERS the initials circle rather than replacing the element, so
   every size the avatar already has — the header's 34px, Profile's 74px, the
   roster's 96px — works on a photo without being restated. `object-fit: cover`
   because these are phone photos of unknown aspect and a squashed face is
   worse than a cropped one. */
.avatar--photo {
  overflow: hidden;
  background: var(--line);
}

.avatar--photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.photo-control {
  display: flex;
  gap: var(--s-2);
  justify-content: center;
  flex-wrap: wrap;
  margin-top: var(--s-3);
}

/* The real input is never shown — the button opens it. Kept in the DOM and
   merely invisible (not `display: none`) so it stays focusable and the file
   picker still fires its change event. */
.photo-control__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  -webkit-clip-path: inset(50%);
          clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* -- the roster ---------------------------------------------------------- */

/* Two up on a phone, more as there is room. auto-fill rather than a fixed
   count so a gym with three coaches and a gym with thirty both look composed. */
.coach-grid {
  list-style: none;
  margin: 0 0 var(--s-6);
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  grid-gap: var(--s-3);
  gap: var(--s-3);
}

.coach {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s-2);
  padding: var(--s-4) var(--s-3);
}

.coach .coach__face {
  width: 5.5rem;
  height: 5.5rem;
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h2);
}

.coach__name {
  font-family: var(--font-display-stack);
  font-size: var(--t-h4);
  font-weight: 400;
  margin: 0;
  line-height: 1.1;
}

.coach__gym {
  margin: 0;
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--brand-strong, var(--brand));
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.coach__teaches {
  margin: 0;
  font-size: var(--t-caption);
  color: var(--ink-faint);
  line-height: 1.35;
}

.coach__chat {
  margin-top: var(--s-3);
}

.coach__chat .btn {
  display: inline-flex;
}

.coach-inbox {
  margin-bottom: var(--s-6);
}

.chat-request {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-3);
}

.chat-request__name {
  margin: 0;
  color: var(--ink);
  font-weight: 850;
}

.chat-request__meta {
  margin: 0.15rem 0 0;
  color: var(--ink-faint);
  font-size: var(--t-caption);
}

.chat-list {
  padding: 0;
  overflow: hidden;
}

.chat-list__row {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  min-height: 4.3rem;
  padding: var(--s-4) var(--s-5);
  color: var(--ink);
  text-decoration: none;
}

.chat-list__row + .chat-list__row {
  border-top: 1px solid var(--line);
}

.chat-list__row > svg:first-child {
  width: 1.35rem;
  height: 1.35rem;
  color: var(--brand-text);
}

.chat-list__body {
  min-width: 0;
  display: grid;
  grid-gap: 0.1rem;
  gap: 0.1rem;
}

.chat-list__body small {
  overflow: hidden;
  color: var(--ink-faint);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-list__arrow {
  width: 1rem;
  height: 1rem;
  margin-left: auto;
}

.chat-unread {
  min-width: 1.35rem;
  height: 1.35rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: auto;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--brand);
  color: var(--brand-ink);
  font-size: 0.7rem;
  font-weight: 900;
}

.chat-page {
  display: flex;
  flex-direction: column;
  min-height: calc(100dvh - 9rem);
}

.chat-back {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  align-self: flex-start;
  margin-bottom: var(--s-4);
  color: var(--brand-text);
  font-weight: 800;
  text-decoration: none;
}

.chat-back svg {
  width: 0.9rem;
  height: 0.9rem;
  transform: rotate(180deg);
}

.chat-head {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--line);
}

.chat-head h1 {
  margin: 0;
  font-family: var(--font-display-stack);
  font-size: var(--t-h3);
  font-weight: 400;
}

.chat-head p {
  margin: 0.1rem 0 0;
  color: var(--ink-faint);
  font-size: var(--t-caption);
}

.chat-thread {
  flex: 1 1;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-5) 0;
}

.chat-bubble {
  max-width: min(82%, 32rem);
  align-self: flex-start;
  padding: var(--s-3) var(--s-4);
  border-radius: 1rem 1rem 1rem 0.25rem;
  background: var(--card);
  border: 1px solid var(--line);
}

.chat-bubble[data-mine="yes"] {
  align-self: flex-end;
  border-color: color-mix(in srgb, var(--brand) 45%, var(--line));
  border-radius: 1rem 1rem 0.25rem 1rem;
  background: color-mix(in srgb, var(--brand) 14%, var(--card));
}

.chat-bubble p {
  margin: 0;
  white-space: pre-wrap;
}

.chat-bubble time {
  display: block;
  margin-top: 0.3rem;
  color: var(--ink-faint);
  font-size: 0.66rem;
}

.chat-thread__empty {
  margin: auto;
  color: var(--ink-faint);
  text-align: center;
}

.chat-compose {
  position: -webkit-sticky;
  position: sticky;
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  display: flex;
  align-items: flex-end;
  gap: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow);
}

.chat-compose textarea {
  min-width: 0;
  flex: 1 1;
  min-height: var(--tap);
  max-height: 8rem;
  padding: 0.65rem;
  border: 0;
  resize: vertical;
  color: var(--ink);
  background: transparent;
  font: inherit;
}

/* -- the Class Club period toggle ---------------------------------------- */

/* Three links styled as one segmented control. Links and not buttons because
   each state is a URL: the board you are looking at is the board you can send
   somebody, and it all works before any JavaScript arrives. It sits ON the
   green header band, so the colours are the band's, not the page's. */
.club-toggle {
  display: inline-flex;
  gap: 1px;
  background: color-mix(in srgb, var(--brand-ink) 22%, transparent);
  border-radius: 999px;
  padding: 2px;
  flex-shrink: 0;
}

.club-toggle__opt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 1.75rem;
  padding: 0 var(--s-3);
  border-radius: 999px;
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--brand-ink);
  opacity: 0.75;
  white-space: nowrap;
  transition: background-color 0.14s ease, opacity 0.14s ease;
}

.club-toggle__opt:hover {
  opacity: 1;
}

.club-toggle__opt.is-on {
  background: var(--paper);
  color: var(--ink);
  opacity: 1;
}

/* WHERE THEY STAND when they are off the bottom of the board. Without this the
   card is a list of other people's names, which is the opposite of the point. */
.board-mine {
  margin: 0;
  padding: var(--s-4) var(--s-6) var(--s-5);
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--ink-muted, var(--ink-faint));
  border-top: 1px solid var(--line);
}

/* The sentence under a section heading, when the heading alone would leave a
   rule unstated. Used where a screen has to say a thing plainly rather than
   let somebody infer it from a date — the membership lock, for one. */
.section-lede {
  margin: calc(var(--s-2) * -1) 0 var(--s-4);
  font-size: var(--t-caption);
  font-weight: 600;
  line-height: 1.45;
  color: var(--ink-faint);
}

/* ─────────────────────────────────────────────────── who has gone quiet */

.nudge-list {
  list-style: none;
  margin: 0 0 var(--s-6);
  padding: 0;
  display: grid;
  grid-gap: var(--s-4);
  gap: var(--s-4);
}

.nudge {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--s-6);
  /* The reason they are on the list, as a colour you read before the words.
     Set per trend below. */
  border-left: 3px solid var(--line);
}

.nudge[data-trend="lapsed"] {
  border-left-color: var(--bad);
}

.nudge[data-trend="slipping"] {
  border-left-color: var(--warn);
}

.nudge[data-trend="steady"] {
  border-left-color: var(--brand);
}

.nudge__who {
  display: flex;
  align-items: center;
  gap: var(--s-4);
}

.nudge__id {
  min-width: 0;
}

.nudge__name {
  margin: 0;
  font-weight: 800;
  color: var(--ink);
}

.nudge__why {
  margin: 0.1rem 0 0;
  font-size: var(--t-caption);
  color: var(--ink-faint);
}

.nudge__flag {
  font-weight: 800;
  color: var(--ink-soft);
}

.nudge[data-trend="lapsed"] .nudge__flag {
  color: var(--bad);
}

.nudge[data-trend="slipping"] .nudge__flag {
  color: var(--warn);
}

/* The three numbers behind the verdict. Ben knows these people; the figures
   are here so he can disagree with the list before he presses send. */
.nudge__facts {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: var(--s-3);
  gap: var(--s-3);
  margin: var(--s-5) 0 0;
  padding: var(--s-4) 0 0;
  border-top: 1px solid var(--line);
}

.nudge__facts dt {
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--ink-faint);
}

.nudge__facts dd {
  margin: 0.1rem 0 0;
  font-weight: 800;
  color: var(--ink);
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
}

.nudge__send {
  margin-top: var(--s-5);
  display: grid;
  grid-gap: var(--s-3);
  gap: var(--s-3);
  justify-items: start;
}

.nudge__send textarea {
  width: 100%;
  resize: vertical;
}

.nudge__cooling {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: var(--s-5) 0 0;
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

.nudge__cool-glyph {
  width: 1rem;
  height: 1rem;
  flex: none;
  color: var(--brand-text);
}

/* ────────────────────────────────────────── the habit: rings and calendar */

.habit {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: start;
  grid-gap: var(--s-3);
  gap: var(--s-3);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--s-6) var(--s-4);
  margin-bottom: var(--s-4);
}

.dial {
  position: relative;
  display: grid;
  justify-items: center;
  grid-gap: var(--s-2);
  gap: var(--s-2);
}

.dial__svg {
  width: 4.75rem;
  height: 4.75rem;
  /* -90deg so the arc starts at twelve o'clock. An arc that begins at three
     reads as "behind" at a glance, whatever the number says. */
  transform: rotate(-90deg);
  overflow: visible;
}

.dial__track,
.dial__fill {
  fill: none;
  stroke-width: 4.5;
  stroke-linecap: round;
}

.dial__track {
  stroke: color-mix(in srgb, var(--ink) 10%, transparent);
}

.dial__fill {
  stroke: var(--brand);
  transition: stroke-dasharray 400ms ease-out;
}

/* A closed ring is the reward, so it changes state rather than just filling:
   the stroke thickens and the numbers go brand-coloured. */
.dial[data-closed="yes"] .dial__fill {
  stroke-width: 6;
}

.dial[data-closed="yes"] .dial__done,
.dial[data-closed="yes"] .dial__goal {
  color: var(--brand-text);
}

.dial__mid {
  position: absolute;
  top: 2.375rem;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: baseline;
  pointer-events: none;
}

.dial__done {
  font-family: var(--font-display-stack);
  font-size: var(--t-h3);
  line-height: 1;
  color: var(--ink);
}

.dial__goal {
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--ink-faint);
}

.dial__label {
  margin: 0;
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--ink-faint);
  text-align: center;
}

.habit__streak {
  display: grid;
  justify-items: center;
  align-content: center;
  grid-gap: var(--s-1);
  gap: var(--s-1);
  min-height: 4.75rem;
}

.habit__streak-num {
  margin: 0;
  font-family: var(--font-display-stack);
  font-size: var(--t-h2);
  line-height: 1;
  color: var(--brand-text);
}

.habit__streak-word {
  margin: 0;
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--ink-faint);
  text-align: center;
}

.cal {
  padding: var(--s-6);
  margin-bottom: var(--s-4);
}

.cal__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
}

.cal__nav {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

.cal__nav-btn {
  width: 2rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--card);
  color: var(--ink);
}

.cal__nav-btn svg {
  width: 0.9rem;
  height: 0.9rem;
}

.cal__nav-btn--back svg {
  transform: rotate(180deg);
}

.cal__nav-btn:disabled {
  opacity: 0.25;
}

.identity__gym {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: var(--s-2);
  padding: 0.28rem 0.65rem 0.28rem 0.35rem;
  border: 1px solid color-mix(in srgb, var(--brand) 45%, var(--line));
  border-radius: 999px;
  background: color-mix(in srgb, var(--brand) 10%, var(--card));
  color: var(--ink-soft);
  font-size: var(--t-caption);
  font-weight: 800;
}

.identity__gym-code {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.75rem;
  height: 1.5rem;
  border-radius: 999px;
  background: var(--brand);
  color: var(--brand-ink);
  font-size: 0.65rem;
  letter-spacing: 0.05em;
}

.cal__title {
  margin: 0;
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h4);
  letter-spacing: 0;
  color: var(--ink);
}

.cal__count {
  margin: 0;
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--ink-faint);
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
}

.cal__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-gap: 0.3rem;
  gap: 0.3rem;
}

.cal__dow {
  font-size: 0.625rem;
  font-weight: 800;
  color: var(--ink-faint);
  text-align: center;
  padding-bottom: 0.15rem;
}

.cal__day {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 0.4rem;
  font-size: 0.6875rem;
  font-weight: 700;
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  color: var(--ink-faint);
}

/* THE FILLED SQUARE — the thing the whole grid exists to accumulate. */
.cal__day[data-filled="yes"] {
  background: var(--brand);
  color: var(--brand-ink);
}

/* The rest of the month: present, and faint. Deliberately not hidden — the
   room left in the grid is the motivating part. */
.cal__day[data-future="yes"] {
  background: transparent;
  color: color-mix(in srgb, var(--ink) 25%, transparent);
}

/* Today is outlined even when empty, so it reads as still open rather than
   already missed. */
.cal__day[data-today="yes"] {
  box-shadow: inset 0 0 0 2px var(--brand);
}

@media (prefers-reduced-motion: reduce) {
  .dial__fill {
    transition: none;
  }
}

/* ──────────────────────────────────────────── today's workout, on Home */

/* THE ONE YELLOW THING IN THE APP, and that is the whole job. This tile sits
   directly above the brand-green booking poster, and the operator asked for the
   two to be unmistakable at a glance. Green is everywhere here; yellow is
   nowhere else, so the eye separates them without reading a word.

   Held as a local token rather than a global one — it is not a brand colour and
   nothing else should start reaching for it. */
/* COMPACT AND RECTANGULAR (client, this wave): one horizontal band — the
   bolt + "WOD" eyebrow and the title stacked on the LEFT, the body-focus
   badge alone in the open space on the RIGHT, both vertically centred. The
   block-kind line is gone entirely, which is most of the height it lost. */
.wod-now {
  --wod-amber: #f2b705;
  --wod-ink: #2a2103;

  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-6);
  margin-bottom: var(--s-4);
  border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--wod-amber) 42%, var(--line));
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--wod-amber) 14%, var(--card)) 0%,
    var(--card) 68%
  );
  box-shadow: var(--shadow);
  color: var(--ink);
  text-decoration: none;
}

/* The tile is the control, so it needs the affordances a link has. */
.wod-now:hover,
.wod-now:focus-visible {
  border-color: var(--wod-amber);
}

.wod-now--empty {
  background: var(--card);
  border-color: var(--line);
}

/* The stacked left column: eyebrow over title (over the benchmark chip on
   the days that carry one). min-width lets a long title truncate against
   the badge instead of shoving it off the card. */
.wod-now__lead {
  min-width: 0;
  display: grid;
  grid-gap: 0.22rem;
  gap: 0.22rem;
  justify-items: start;
}

.wod-now__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-caption);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* The bolt carries the colour; the text around it stays readable ink. A whole
   line of yellow type would fail contrast on this near-white card. */
.wod-now__bolt {
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
  color: var(--wod-amber);
  /* Yellow on white is thin — a dark outline keeps the mark legible without
     darkening the fill into mustard. */
  filter: drop-shadow(0 0 0.5px var(--wod-ink));
}

.wod-now__title {
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h4);
  line-height: 1.05;
  letter-spacing: 0;
  color: var(--ink);
}

/* The pending-widget's one supporting sentence ("Ben usually puts it up the
   night before") — the honest line under "Not posted yet". */
.wod-now__note {
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

/* A benchmark day's ONE quiet chip. Outline only — the loud thing on this
   card stays the title, and the chip is a flag, not a second headline. */
.wod-now__benchmark {
  display: inline-flex;
  align-items: center;
  padding: 0.14rem 0.5rem;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--wod-amber) 60%, var(--line));
  background: transparent;
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--wod-ink);
  white-space: nowrap;
}

/* THE BODY-PART BADGE, in the card's right-hand air. The anatomical mark is
   deliberately larger than a nav icon; the word confirms the region without
   making the picture microscopic. */
.wod-now__focus {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  flex: none;
  padding: 0.3rem 0.65rem 0.3rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--wod-amber) 22%, transparent);
  border: 1px solid color-mix(in srgb, var(--wod-amber) 55%, transparent);
  font-size: var(--t-caption);
  font-weight: 800;
  color: var(--wod-ink);
  white-space: nowrap;
}

.wod-now__focus-glyph {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
}

/* ── TODAY'S MEMBER ANNIVERSARY ──────────────────────────────────────────
   Warm and celebratory without borrowing the WOD's yellow or the booking's
   solid green. The large year numeral is the keepsake; the sentence names the
   person, because recognition without the person is just a metric. */
.anniversary-now {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  margin-bottom: var(--s-4);
  padding: var(--s-5) var(--s-6);
  border: 1px solid color-mix(in srgb, var(--brand) 35%, var(--line));
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--brand) 7%, var(--card));
  box-shadow: var(--shadow);
}

.anniversary-now__mark {
  width: 3.4rem;
  height: 3.4rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  border-radius: 50%;
  background: var(--brand);
  color: var(--brand-ink);
  font-family: var(--font-display-stack);
  font-size: var(--t-h3);
  line-height: 1;
}

.anniversary-now__body {
  min-width: 0;
  display: grid;
  grid-gap: 0.08rem;
  gap: 0.08rem;
}

.anniversary-now__eyebrow {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--brand-text);
  font-size: var(--t-caption);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.anniversary-now__heart {
  width: 0.9rem;
  height: 0.9rem;
}

.anniversary-now__title {
  color: var(--ink);
  font-weight: 850;
}

.anniversary-now__message {
  color: var(--ink-faint);
  font-size: var(--t-caption);
  line-height: 1.4;
}

/* The title and its focus badge share a line, with the badge pushed into the
   open space on the right the operator pointed at. It wraps below on a narrow
   phone rather than squeezing the title, because the title is the screen. */
.wod-hero__line {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
}

.wod-hero__line .wod-hero__title {
  flex: 1 1 12rem;
  min-width: 0;
}

.wod-hero__focus {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
  margin-top: 0.15rem;
  padding: 0.4rem 0.8rem 0.4rem 0.55rem;
  border-radius: 999px;
  background: var(--card);
  border: 1px solid color-mix(in srgb, var(--brand) 45%, var(--line));
  font-size: var(--t-meta);
  font-weight: 800;
  color: var(--ink);
  white-space: nowrap;
}

.wod-hero__focus-glyph {
  width: 1.55rem;
  height: 1.55rem;
  flex: none;
  color: var(--brand-text);
}

/* ── HOME SPOTLIGHTS ─────────────────────────────────────────────────────
   Announcements, challenges and anniversaries as ONE ROW OF THREE SQUARE
   DOORS (client, this wave), replacing the card stacks that pushed the fold
   down. Each button is an equal-width square with a generous tap target;
   the corner dot is the only thing that changes day to day. */
.spotlights {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: var(--s-3);
  gap: var(--s-3);
  margin: 0 0 var(--s-6);
}

.spotlights__btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  aspect-ratio: 1;
  /* Square at a phone's column width; capped so a wide window gets three
     short doors rather than three billboards. */
  max-height: 7.25rem;
  min-height: calc(var(--tap) * 1.5);
  width: 100%;
  padding: var(--s-3) var(--s-2);
  font: inherit;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  cursor: pointer;
}

.spotlights__btn:hover,
.spotlights__btn:focus-visible {
  border-color: color-mix(in srgb, var(--brand) 60%, var(--line));
}

.spotlights__glyph {
  width: 1.6rem;
  height: 1.6rem;
  flex: none;
  color: var(--brand-text);
}

.spotlights__label {
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
}

/* The unread/count dot — same red the drawer's unread pill wears, because
   it means the same thing: something in here is waiting on you. */
.spotlights__dot {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  min-width: 1.3rem;
  height: 1.3rem;
  padding: 0 0.35rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--bad);
  color: #fff;
  font-size: var(--t-micro);
  font-weight: 900;
}

/* ── THE SPOTLIGHT SHEET ─────────────────────────────────────────────────
   A native <dialog> reset into a bottom sheet, same skeleton as the
   checkout's .sheet-overlay — restated under its own name because this one
   BLURS the page behind it (the client's ask) and the checkout must not
   inherit that. Slide-up on open; the drag itself is the shared
   useSwipeAway writing transforms straight to the node. */
.spot-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: none;
  background: none;
  overflow: visible;
  color: var(--ink);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

/* `display: flex` above beats the UA's closed-state display:none, so the
   closed state has to be restated or the sheet flashes before showModal(). */
dialog.spot-overlay:not([open]) {
  display: none;
}

/* THE BLURRED, DIMMED BACKDROP. The page behind stays recognisably the
   page — it has gone soft, not black — which is what tells a thumb "swipe
   this away and you are back where you were". */
.spot-overlay::-webkit-backdrop {
  background: rgb(19 20 23 / 0.38);
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  animation: sheet-scrim 0.18s ease both;
}
.spot-overlay::backdrop {
  background: rgb(19 20 23 / 0.38);
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  animation: sheet-scrim 0.18s ease both;
}

.spot-sheet {
  width: 100%;
  max-width: 30rem;
  max-height: 82dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Vertical panning stays the browser's for the list inside; the dismissal
     drag starts from the grab handle, which opts out via touch-action. */
  touch-action: pan-y;
  background: var(--card);
  border-radius: var(--radius-hero) var(--radius-hero) 0 0;
  padding: 0.35rem var(--s-6) calc(var(--s-8) + env(safe-area-inset-bottom));
  box-shadow: 0 -12px 44px rgb(19 20 23 / 0.28);
}

/* `backwards`, not `both` — a fill of `both` would pin the keyframes' final
   transform over the inline transform the drag writes (see .drawer[open]). */
.spot-overlay[open] .spot-sheet {
  animation: spot-rise 0.26s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

@keyframes spot-rise {
  from {
    transform: translate3d(0, 100%, 0);
  }
}

/* The shared grab handle sizes its negative margins to the checkout's
   1.25rem padding; this sheet's gutter is a token, so restate the reach. */
.spot-sheet .sheet-grab {
  margin: 0 calc(-1 * var(--s-6)) 0.25rem;
}

.spot-sheet__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin: 0 0 var(--s-4);
}

.spot-sheet__title {
  margin: 0;
  font-family: var(--font-display-stack);
  font-weight: 400;
  font-size: var(--t-h3);
  line-height: 1;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

.spot-sheet__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap);
  min-height: var(--tap);
  border: 1px solid var(--line);
  border-radius: 999px;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}

.spot-sheet__close:hover,
.spot-sheet__close:focus-visible {
  color: var(--ink);
  border-color: var(--ink-faint);
}

.spot-sheet__close-glyph {
  width: 1.1rem;
  height: 1.1rem;
}

.spot-sheet__body {
  display: grid;
  grid-gap: var(--s-3);
  gap: var(--s-3);
}

/* NO SLIDE UNDER REDUCED MOTION — the sheet appears and disappears in
   place. The drag's spring-back stays (it answers a finger still on the
   screen, same reasoning as the checkout sheet's). */
@media (prefers-reduced-motion: reduce) {
  .spot-overlay::-webkit-backdrop {
    animation: none;
  }
  .spot-overlay::backdrop,
  .spot-overlay[open] .spot-sheet {
    animation: none;
  }
}

/* A wide window has no bottom edge worth pulling to — centre it. */
@media (min-width: 760px) {
  .spot-overlay {
    align-items: center;
    padding: 2rem 1rem;
  }

  .spot-sheet {
    border-radius: var(--radius-hero);
  }
}

/* ── SHEET CONTENTS ──────────────────────────────────────────────────── */

.spot-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-gap: var(--s-3);
  gap: var(--s-3);
}

/* An announcement, as the bell renders it but at rest on paper. */
.spot-notice {
  display: grid;
  grid-gap: 0.25rem;
  gap: 0.25rem;
  padding: var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--paper);
}

.spot-notice__title {
  margin: 0;
  font-size: var(--t-body);
  font-weight: 800;
}

.spot-notice__meta {
  margin: 0;
  font-size: var(--t-micro);
  font-weight: 700;
  color: var(--ink-faint);
}

.spot-notice__body {
  margin: 0;
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--ink-soft);
}

/* A live challenge/initiative, compact: title + kind up top, runway and
   the reader's own state below. The whole card is the link. */
.spot-challenge {
  display: grid;
  grid-gap: 0.4rem;
  gap: 0.4rem;
  padding: var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--paper);
  color: var(--ink);
  text-decoration: none;
}

.spot-challenge:hover,
.spot-challenge:focus-visible {
  border-color: color-mix(in srgb, var(--brand) 60%, var(--line));
}

.spot-challenge__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
}

.spot-challenge__title {
  min-width: 0;
  font-size: var(--t-body);
  font-weight: 800;
}

.spot-challenge__kind {
  flex: none;
  padding: 0.18rem 0.55rem;
  border-radius: 999px;
  background: var(--brand-soft);
  color: var(--brand-text);
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* An initiative is a hand-raise, not a competition — it wears the warm
   chip so the two kinds sort themselves before the label is read. */
.spot-challenge__kind--initiative {
  background: var(--warn-soft);
  color: var(--warn);
}

.spot-challenge__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

.spot-challenge__state {
  font-weight: 800;
  color: var(--brand-text);
}

/* ── ANNIVERSARY CARD, GROWN UP ──────────────────────────────────────────
   The card learned three states this wave: an UPCOMING preview (quieter —
   dashed border, soft mark), the celebrant's KUDOS WALL, and the sender's
   button/sent pair. Base card unchanged above. */
.anniversary-now--soon {
  background: var(--card);
  border-style: dashed;
  box-shadow: none;
}

.anniversary-now--soon .anniversary-now__mark {
  background: var(--brand-soft);
  color: var(--brand-text);
}

/* "3 kudos — Jordan: 'Three years strong!' · Maya" — the wall itself. */
.anniversary-now__kudos {
  margin-top: 0.15rem;
  color: var(--ink-soft);
  font-size: var(--t-caption);
  font-weight: 600;
  line-height: 1.45;
}

.anniversary-now__send {
  margin-top: var(--s-2);
}

.anniversary-now__send .btn {
  min-height: var(--tap);
}

.anniversary-now__sent {
  margin-top: var(--s-1);
  color: var(--ok);
  font-size: var(--t-caption);
  font-weight: 800;
}

/* ── GUIDED DEMO ─────────────────────────────────────────────────────────
   The walkthrough hub (/app/demo) and the floating tour card the /app
   layout mounts. Demo-phase chrome on the existing tokens: cards, badges
   and buttons the rest of the app already wears, plus ONE loud thing — the
   tour card's brand-green border, so the guide is findable on any page
   without highlighting anything inside the page itself. */

/* -- the persona strip: the dev door, dressed honestly ------------------ */
.demo-personas {
  margin-top: var(--s-6);
}

.demo-personas__row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  grid-gap: var(--s-3);
  gap: var(--s-3);
  margin-top: var(--s-3);
}

.demo-persona {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: var(--s-4) var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--paper);
  text-decoration: none;
  color: var(--ink);
  min-height: var(--tap);
}

.demo-persona strong {
  font-size: var(--t-sm);
}

.demo-persona span {
  font-size: var(--t-caption);
  color: var(--ink-faint);
}

/* The seat you are already in — lit in the brand, like the active tab. */
.demo-persona[aria-current="true"] {
  border-color: var(--brand);
  background: var(--brand-soft);
}

.demo-persona[aria-current="true"] span {
  color: var(--brand-text);
  font-weight: 700;
}

.demo-personas__caption {
  margin: var(--s-3) 0 0;
  font-size: var(--t-caption);
  color: var(--ink-faint);
}

/* -- the flow groups and cards ------------------------------------------ */
.demo-flows {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  margin-top: var(--s-4);
}

/* Another seat's flows sit folded — present, honest, out of the way. */
.demo-group {
  margin-top: var(--s-8);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  padding: 0 var(--card-pad) var(--s-2);
}

.demo-group[open] {
  padding-bottom: var(--card-pad);
}

.demo-group__summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5) 0;
  cursor: pointer;
  font-family: var(--font-display-stack);
  font-size: var(--t-h2);
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

.demo-group__count {
  font-family: var(--font-body-stack);
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
  text-transform: none;
}

.demo-flow {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--card-pad);
}

/* Inside a folded group the group is already the card — go flat. */
.demo-group .demo-flow {
  box-shadow: none;
  background: var(--paper);
}

.demo-flow__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
}

.demo-flow__title {
  margin: 0;
  font-size: var(--t-body);
  font-weight: 800;
}

.demo-flow__count {
  flex: none;
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--brand-text);
}

.demo-flow__blurb {
  margin: var(--s-1) 0 0;
  font-size: var(--t-sm);
  color: var(--ink-soft);
}

.demo-flow__steps {
  margin: var(--s-4) 0 0;
  padding-left: 1.2rem;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  font-size: var(--t-sm);
}

.demo-flow__steps li::marker {
  color: var(--brand-text);
  font-weight: 800;
}

.demo-flow__instruction {
  display: block;
}

.demo-flow__note {
  display: block;
  margin-top: 0.15rem;
  font-size: var(--t-caption);
  color: var(--ink-faint);
}

/* The route, in passing — the person driving the demo reads it, Ben never
   has to. */
.demo-flow__path {
  display: block;
  margin-top: 0.15rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem;
  color: var(--ink-faint);
}

.demo-flow__foot {
  margin-top: var(--s-5);
}

.demo-flow__switch {
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
}

a.demo-flow__switch {
  color: var(--brand-text);
  text-decoration: none;
}

/* -- the floating tour card ---------------------------------------------
   Fixed above the tab bar (the bar is 20; toasts are 40 — the tour sits
   between, so a toast can still speak over it). ≤90vw and small type: it
   is a guide riding along, not a sheet over the page. */
.demo-tour {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(4.2rem + env(safe-area-inset-bottom));
  z-index: 30;
  width: min(90vw, 24rem);
  background: var(--card);
  border: 2px solid var(--brand);
  border-radius: var(--radius);
  box-shadow: 0 10px 34px rgb(19 20 23 / 0.22);
  padding: var(--s-5) var(--s-6);
  animation: demo-tour-rise 0.22s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

@keyframes demo-tour-rise {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(0.6rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  .demo-tour {
    animation: none;
  }
}

/* On a wide window the bar is a top strip, so the card hugs the bottom. */
@media (min-width: 760px) {
  .demo-tour {
    bottom: var(--s-8);
  }
}

.demo-tour__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
}

.demo-tour__flow {
  font-size: var(--t-caption);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--brand-text);
}

.demo-tour__count {
  flex: none;
  font-size: var(--t-caption);
  font-weight: 700;
  color: var(--ink-faint);
}

.demo-tour__instruction {
  margin: var(--s-2) 0 0;
  font-size: var(--t-sm);
  line-height: 1.45;
}

.demo-tour__note {
  margin: var(--s-1) 0 0;
  font-size: var(--t-caption);
  color: var(--ink-faint);
}

.demo-tour__actions {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-4);
}

/* "End tour" is an exit, not an action — words, not a button. */
.demo-tour__end {
  margin-left: auto;
  border: 0;
  background: none;
  padding: var(--s-2);
  font: inherit;
  font-size: var(--t-caption);
  font-weight: 600;
  color: var(--ink-faint);
  cursor: pointer;
  min-height: var(--tap);
}

