/* ─────────────────────────────────────────────────────────
   ServeRight Coach App — iOS-native minimal system
   System fonts, white grouped cards, native chrome
   ───────────────────────────────────────────────────────── */

:root {
  /* iOS surfaces */
  --bg:        #F2F2F0;            /* systemGroupedBackground, faintly warm */
  --bg-2:      #FFFFFF;            /* secondarySystemGroupedBackground */
  --bg-3:      #F7F6F2;            /* THE sunken/lifted warm tint — background canonisation
                                      ruling 2026-07-26: was #F8F8F4; absorbs the whole warm
                                      off-white band (#F6F4EF / #F6F5F2 / old --lift-bg) */
  /* Neutral fill ladder — ONE base (iOS systemFill grey), three steps + --well.
     fill-1+fill-2 MERGED at .10 (ruling 2026-07-26); --fill-2 kept as an alias so
     existing call sites resolve unchanged. */
  --fill-1:    rgba(120,120,128,.10);
  --fill-2:    var(--fill-1);
  --fill-3:    rgba(120,120,128,.18);  /* hover / pressed wells */
  --fill-4:    rgba(120,120,128,.28);  /* strong control fill: chip base, switch-off, stepper active */
  --well:      rgba(60,60,67,.16);     /* engraved wells + grab handles — the one darker ink base,
                                          pairs with --engrave; NOT interchangeable with --fill-3 */

  /* Semantic UI accents */
  --badge:     var(--destructive); /* notification / unread dot — merged into --destructive (REQ-082, same #FF3B30) */
  --lift-bg:   var(--bg-3);        /* lifted-row tint — swipe / drag surface (alias, was #F7F6F2) */

  /* iOS text */
  --label:     #000000;            /* primary label */
  --label-2:   rgba(60,60,67,.60); /* secondary */
  --label-3:   rgba(60,60,67,.30); /* tertiary */
  --label-4:   var(--label-3);     /* quaternary — merged into --label-3 .30 (REQ-008/009; was .18, now darker) */

  /* Sheet header type ramp (Bottom Sheet spec).
     TIER IS ASSIGNED BY NESTING, NOT BY FEEL (binding, user ruling 2026-07-09):
     • --sheet-title (24) = PRIMARY — a top-level sheet opened directly (from a
       screen / nav / FAB / list-row tap / the notification center): the root of
       its own sheet stack.
     • --sheet-title-sm (18) = COMPACT — a sub-sheet opened from WITHIN another
       sheet (nested), a peek/preview panel, or a Dialog/utility confirmation.
     Assign via the SheetHeader `compact` prop / these tokens — never a raw px.
     Enforced by scripts/design-audit rule C-TITLETIER. */
  --sheet-title:    24px;          /* primary sheet title */
  --sheet-title-sm: 18px;          /* compact / nested sheet title */
  --sheet-subtitle: 13px;          /* sheet subtitle */
  --sheet-coin-top: 14px;          /* close/back/action coin — vertical inset from sheet top */
  --sheet-coin-inset: 14px;        /* close/back/action coin — horizontal inset from sheet edge */

  /* iOS separators */
  --sep:       rgba(60,60,67,.18); /* opaque separator-ish */
  --sep-soft:  rgba(60,60,67,.10); /* RETAINED as the .10 value so raw rgba(60,60,67,.10) dependents still
                                      resolve to a token (audit T-RAWCOLOR, no flip to T-NEWCOLOR). The
                                      REQ-010/011 soft→hard merge is applied at the 19 CSS usage sites, which
                                      point at var(--sep) directly — identical visible result, no bucket rise. */

  /* Elevated dashboard card — layered drop shadow + hairline ring + top light-catch
     (option B: same border/shading as the booking recap card, tuned for light bg) */
  --elev-card: 0 8px 22px rgba(20,16,12,.14), 0 2px 6px rgba(20,16,12,.08), 0 0 0 .5px rgba(20,16,12,.10), inset 0 1px 0 rgba(255,255,255,.7);

  /* ── 3D depth system — box-shadow only, lit from top-center ──────────────
     One source of truth for the raised + engraved treatments. Order (outer→inner):
     ambient drop (cast away from the surface) · contact ring · white top/side bevel ·
     soft inner glow under the top lip · faint dark inner seam at the very top.
     Bevel width scales with corner radius, so a 14px-radius card uses a 2px bevel and
     the big 38px-radius bottom sheet keeps its own 3px-bevel stack inline.  */
  --raise-card:
    0 10px 24px -8px rgba(0,0,0,.16),
    0 3px 8px -3px rgba(0,0,0,.11),
    0 0 0 1px rgba(20,16,12,.1),
    inset 0 2px 0 #fff,
    inset 2px 0 0 rgba(255,255,255,.75),
    inset -2px 0 0 rgba(255,255,255,.75),
    inset 0 6px 12px -5px rgba(255,255,255,.85),
    inset 0 5px 2px -4px rgba(20,16,12,.10);
  /* Flat segmented-thumb lift — the FLAT counterpart to the domed --raise-card. Segmented
     pill thumbs (the shared Segmented primitive + the Add-Client Adult/Junior + Money
     chart-filter) sit in an engraved well and must read FLAT, not 3D: a single soft
     drop, no gloss/bevel. One value so every pill thumb is identical. */
  --thumb-flat: 0 1px 2px rgba(20,16,12,.18);
  /* Engraved control — top-center light inverted so it reads pressed into the surface:
     stronger inset-dark on the inside-top, fainter inset-dark on the inside-bottom,
     and a single white lip highlight just below the element. */
  --engrave:
    inset 0 1.5px 2px rgba(20,16,12,.35),
    inset 0 -1px 1px rgba(20,16,12,.16),
    0 1px 0 rgba(255,255,255,.85);
  --engrave-lg:
    inset 0 2px 3px rgba(20,16,12,.35),
    inset 0 -1.5px 1.5px rgba(20,16,12,.16),
    0 1px 0 rgba(255,255,255,.85);
  /* Engraved WELL — the soft, large-surface variant for light input fields & content
     wells (the --engrave/-lg grooves above are tuned for small dark controls and read
     muddy on a big light field). Same top-lit inset language, dialled way down: a gentle
     inner-top shadow + a white lower lip. This is the "engraving" axis for .field-val,
     inline calendars, and any read/write well. */
  --engrave-well:
    inset 0 2px 4px rgba(20,16,12,.10),
    inset 0 -1px 0 rgba(255,255,255,.55),
    0 1px 0 rgba(255,255,255,.5);
  /* Raised bottom sheet — same system as a card, but the ambient casts UPWARD (a bottom
     sheet floats up off the bottom edge) and the bevel is 3px for the big 38px radius.
     Used by .sheet and by every hand-rolled inline bottom sheet so they read identically. */
  --raise-sheet:
    inset 0 2px 0 #fff,
    inset 3px 0 0 rgba(255,255,255,.75),
    inset -3px 0 0 rgba(255,255,255,.75),
    inset 0 8px 14px -6px rgba(255,255,255,.85),
    inset 0 5px 2px -4px rgba(20,16,12,.10),
    0 0 0 1px rgba(20,16,12,.1),
    0 -14px 36px rgba(0,0,0,.20),
    0 -6px 15px rgba(0,0,0,.12);

  /* Sheet height scale — collapses the 78 / 82 / 86 / 90 / 92% spread to a 3-step
     scale plus content-sized `auto` (capped by --sheet-h-max). */
  --sheet-h-md:   82%;   /* compact — absorbs the old 78% */
  --sheet-h-lg:   90%;   /* tall — absorbs the old 86% / 88% */
  --sheet-h-full: 92%;   /* full — the tallest fixed sheet */
  --sheet-h-max:  92%;   /* hard cap for auto/content-sized sheets */

  /* Sheet upper-corner curve — one value for every bottom sheet (kills the scattered
     `38px 38px 0 0` literals and the dead `22px` in .ns-sheet). */
  --sheet-radius: 38px;

  /* ── Brand + accent (P1·T1) — burgundy is the brand; gold retired ──
     One brand colour drives the wordmark, selection, filter chips, taps and the
     booking-success mark. --accent + --ochre alias --brand so existing refs re-tint
     with no per-file edits. The old gold #B48A2C is gone. */
  --brand:       #B04A3D;
  --brand-2:     #8A382E;            /* darker — gradients / pressed */
  --brand-tint:  rgba(176,74,61,.12);
  --accent:      var(--brand);       /* was #B48A2C gold — retired */
  --accent-2:    var(--brand-2);     /* was #8E6C1F */
  --accent-tint: var(--brand-tint);

  /* ── Semantic — ONE green (P1·T1) ── */
  --positive:      #34C759;          /* THE green: confirm / paid / available */
  --positive-2:    #248A3D;          /* darker — hover / pressed (was a 2nd green #2BAE4A) */
  --positive-tint: rgba(52,199,89,.14);
  --warning:   #FF9F0A;
  --warning-tint: rgba(255,159,10,.14);
  --destructive: #FF3B30;
  --destructive-tint: rgba(255,59,48,.12);
  --info:      #007AFF;
  --group:     #2a6fdb;   /* group-lesson blue — matches the lesson-plus group accent bar */

  /* Legacy aliases — keep so screens that reference green/red/etc still render
     (palette tweaks in app.jsx remap these, but iOS look stays cohesive) */
  --paper:   var(--bg);
  --paper-2: var(--bg-2);
  --paper-3: var(--fill-1);
  --paper-4: var(--bg-3);
  --ink:     var(--label);
  --ink-2:   var(--label-2);
  --ink-3:   var(--label-3);
  --line:    var(--sep);
  --line-2:  var(--sep);
  --green:   var(--positive);
  --green-2: var(--positive-2);     /* was #2BAE4A — folded into the one green */
  --green-tint: var(--positive-tint);
  --purple:  #AF52DE;
  --ochre:   var(--accent);         /* legacy gold alias → brand burgundy */

  /* Category sheet tints — the paper a detail sheet is printed on, keyed to what
     it is ABOUT (coach / lesson / plain court). Held as bare "r,g,b" triplets so a
     caller can rgba() them for the translucent variant; --cat-bg-* is the opaque
     form. Declared here because screen-courts-map.jsx and screen-courts-cards.jsx
     both need them and were drifting apart as separate literals. */
  --cat-rgb-coach:  238,245,240;    /* was the raw #eef5f0 in screen-courts-cards */
  --cat-rgb-lesson: 238,242,248;
  --cat-rgb-court:  242,242,240;
  --cat-bg-coach:  rgb(var(--cat-rgb-coach));
  --cat-bg-lesson: rgb(var(--cat-rgb-lesson));
  --cat-bg-court:  rgb(var(--cat-rgb-court));
  /* Dominant land tone of the CARTO light_all tiles (sampled) — the page canvas
     around/behind the Courts map adopts it so the browser-owned strips (status-bar
     surround, visual-viewport leftover) blend into the map instead of reading as
     --bg-grey bands against the whiter tiles. */
  --map-paper: #F6F6F5;
  --red:     var(--destructive);
  --red-tint: var(--destructive-tint);

  /* ── Background canonisation (2026-07-26) — status accents, ONE token per
     booking kind. Single source for display-rules.jsx's C palette (which reads
     these via getComputedStyle at boot — edit HERE, never there). The deep
     --notif/--cancel hues are deliberate: tuned for large filled surfaces with
     white text; badges/swipe actions keep --warning/--destructive. */
  --court:   var(--brand);           /* court booking bar / cells */
  --lesson:  #2F6F4E;                /* private lesson green */
  --open:    var(--positive);        /* open / bookable */
  --notif:   #E0892C;                /* notify AND scheduled (shared hue, icon differs) */
  --cancel:  #E5392F;                /* cancel / unpaid / failed filled surfaces */
  --unavail: rgba(60,60,67,.22);     /* unavailable / blocked bar */
  --done:    #48484A;                /* COMPLETED pill + played-out rows */
  --open-dim: #5AA06F;               /* schedule-reveal strip's muted open (behind-cells);
                                        NOT --open — the strip is deliberately dimmer */
  --lesson-2: #265D43;               /* darker lesson green — pressed / gradients (mirrors --brand-2) */
  --group-2:  #2257AD;               /* darker group blue — pressed / gradients */
  --money-pending: #C8821E;          /* text-grade amber for pending-money notes (darker than
                                        --notif for contrast on tinted note backgrounds) */
  /* rgb triplets — for rgba(var(--x-rgb), α) alpha ramps (charts, pins), same
     pattern as --cat-rgb-* above */
  --bg-rgb:     242,242,240;      /* --bg triplet — translucent fade stops rgba(var(--bg-rgb),α) */
  --brand-rgb:  176,74,61;
  --lesson-rgb: 47,111,78;
  --group-rgb:  42,111,219;
  /* semantic tint gaps */
  --lesson-tint: rgba(47,111,78,.12);
  --group-tint:  rgba(42,111,219,.14);
  --purple-tint: rgba(175,82,222,.12);
  /* materials + toast */
  --material-bar:  rgba(252,251,249,.72);  /* frosted tab bar */
  --material-card: rgba(255,255,255,.96);  /* floating glass controls (map) */
  --toast:         rgba(28,24,22,.82);     /* the one dark toast pill */

  /* ═══════ P1 · Token foundation (additive; component adoption lands in P2–P3) ═══════ */

  /* T2 · Elevation ladder — completes the raise/engrave set defined above.
     raise-card / raise-sheet / engrave / engrave-well = already defined. */
  --raise-sheet-high:                /* Peek / map card — floats higher than a normal sheet */
    inset 0 2px 0 #fff,
    inset 3px 0 0 rgba(255,255,255,.75),
    inset -3px 0 0 rgba(255,255,255,.75),
    inset 0 8px 14px -6px rgba(255,255,255,.85),
    inset 0 5px 2px -4px rgba(20,16,12,.10),
    0 0 0 1px rgba(20,16,12,.1),
    0 -22px 50px rgba(0,0,0,.28),
    0 -10px 22px rgba(0,0,0,.16);
  --recessed:                        /* inset content well — grouped lists, grids, tracks */
    inset 0 5px 11px -3px rgba(0,0,0,.22),
    inset 0 -4px 9px -3px rgba(0,0,0,.13),
    inset 8px 0 11px -8px rgba(0,0,0,.16),
    inset -8px 0 11px -8px rgba(0,0,0,.16);
  --dialog:                          /* centred alert/confirm — even ambient, no directional cast */
    0 0 0 1px rgba(20,16,12,.08),
    0 24px 60px -12px rgba(0,0,0,.34),
    inset 0 1px 0 rgba(255,255,255,.7);
  --coin:                            /* the ONE coin dome — engraved raised close/back/action
                                        button (Coin primitive + .sr-coin). The single lighter
                                        .22 stack; the heavier .35 inline clones are retired.
                                        Resting state only — the .sr-coin:active press-in is a
                                        distinct interaction state (REQ-132). */
    inset 0 1.5px 2px rgba(20,16,12,.22),
    inset 0 -1px 1px rgba(255,255,255,.55),
    0 1px 0 rgba(255,255,255,.5);
  --scrim: rgba(0,0,0,.32);          /* one backdrop dim for every sheet + dialog */

  /* T3 · Density scale (hit targets / row heights) + motion */
  --row-xs: 36px;    /* dense inset control */
  --row-sm: 44px;    /* minimum touch target */
  --row-md: 52px;    /* default list row */
  --row-lg: 64px;    /* stat / hero row */
  --ease-standard:   cubic-bezier(.2,.8,.2,1);
  --ease-emphasized: cubic-bezier(.32,.72,0,1);
  --ease-spring:     cubic-bezier(.34,1.56,.64,1);
  --dur-fast:   .18s;   /* taps, toggles */
  --dur-base:   .30s;   /* most transitions */
  --dur-reveal: .34s;   /* swipe reveal / row settle */
  --dur-sheet:  .42s;   /* sheet slide + scrim */

  /* T5 · Money palette + amount rule.
     Amount rule: income = leading "+" in --money-in; expense = leading "−" in --money-out;
     not-yet-cleared = --money-pending; a zero amount stays neutral --label. */
  --money-in:           #1E8F47;
  --money-out:          var(--label);
  --money-pending:      #C98A1E;

  /* T6 · Icon system — 24-grid, 1.7 outline stroke, 5-step size scale (maps to the Ic s= prop).
     Outline glyphs stroke 1.7; check & plus keep 2.0 as the sole exception (ruled 2026-07-08).
     Scale re-valued 12/14/16/20/24 → 12/16/20/28/34 (P4f iconography wave). */
  --icon-grid:   24;
  --icon-stroke: 1.7;
  --icon-xs: 12px;
  --icon-sm: 16px;
  --icon-md: 20px;
  --icon-lg: 28px;
  --icon-xl: 34px;

  /* ── P4 Foundation scales (Phase 0, design-p4b) ─────────────────────────────
     Four load-bearing primitive scales that were never tokenised. Minted from
     the consistency audit's measured usage (mass snapped to a coherent step
     count). ZERO-VISUAL on mint — NO consumers yet; adoption is later waves,
     driven down by the ratchet on L-SPACESCALE/L-TYPESCALE/L-RADIUS/L-ZINDEX.
     Do NOT adopt these at call sites in this wave. */

  /* T7 · Spacing scale — 2px base step. Anchored on the de-facto rhythm
     (10/6/16/8/4/14/12 dominate). Governs L-SPACESCALE; off-scale gaps
     (11/13/15/18/22/26 + fractionals) flag once sites adopt. */
  --space-0:   0;
  --space-1:   2px;
  --space-2:   4px;
  --space-3:   6px;
  --space-4:   8px;
  --space-5:   10px;
  --space-6:   12px;
  --space-7:   14px;
  --space-8:   16px;
  --space-9:   20px;
  --space-10:  24px;
  --space-11:  32px;
  --space-12:  40px;
  --space-13:  48px;

  /* T8 · Body type ramp {11,12,13,15,17}. Kills the half-px family
     (10.5/11.5/12.5/13.5/14.5/15.5/9.5). The three --sheet-title* tokens above
     stay the title ramp. Governs L-TYPESCALE. */
  --fs-xs:   11px;
  --fs-sm:   12px;
  --fs-md:   13px;
  --fs-base: 15px;
  --fs-lg:   17px;

  /* T8b · Badge type — caps status chips sit OUTSIDE the body ramp above: they
     are label furniture, not text, and 11px caps read heavy next to 15px body.
     ONE size for every chip in the app (was drifting across 9.5/10/10.5/11).
     Never use --fs-xs for a badge — it is body text and mapping 9.5→--fs-xs is
     what drifted the Clients chips to 11px. Consumed by .sr-badge. */
  --fs-badge: 10px;

  /* T9 · General radius scale {4,8,12,14,20,999}. 14 is the de-facto card
     corner. --sheet-radius(38) above stays the bottom-sheet corner (raw 38
     should adopt it). Governs L-RADIUS. */
  --r-sm:   4px;
  --r-md:   8px;
  --r-lg:   12px;
  --r-card: 14px;
  --r-xl:   20px;
  --r-pill: 999px;

  /* Button system (.sr-btn) — height tiers map to iOS .controlSize, shape is
     one token so the whole app can flip capsule → continuous rounded-rect. */
  --sr-btn-h-lg: 50px;              /* .controlSize(.large)  → footer CTAs   */
  --sr-btn-h-md: 34px;              /* .controlSize(.regular)→ inline        */
  --sr-btn-h-sm: 28px;              /* .controlSize(.small)                  */
  --sr-btn-shape: var(--r-pill);   /* capsule; one edit → var(--r-lg) rounded-rect */

  /* T10 · Z-index tiers (7). Replaces 37 ad-hoc values; the coin-over-header
     N/N+1 idiom becomes calc(var(--z-sheet)+1). Leaflet map (2147483000)
     is exempt. Governs L-ZINDEX. */
  --z-base:    1;
  --z-raised:  10;
  --z-sticky:  20;
  --z-sheet:   100;
  --z-coin:    110;
  --z-dialog:  200;
  --z-toast:   300;

  --ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
        "Helvetica Neue", system-ui, sans-serif;
  --sans:  var(--ui);
  --serif: var(--ui);
  --mono:  ui-monospace, "SF Mono", Menlo, monospace;
}

* { box-sizing: border-box; margin: 0; padding: 0; -webkit-font-smoothing: antialiased; }
html, body { height: 100%; }
body {
  background: #1c1c1e;
  font-family: var(--ui);
  color: var(--label);
  overflow: hidden;
}

/* ───── App canvas — clean neutral stage ───── */
.app-stage {
  position: fixed; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: #1c1c1e;
  overflow: hidden;
}
.app-stage::before {
  content: ''; position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 70% 50% at 50% 45%, #2c2c2e 0%, #1c1c1e 60%, #0a0a0c 100%);
  pointer-events: none;
}

.stage-mark {
  position: absolute; top: 24px; left: 24px; z-index: 2;
  font-family: var(--ui); font-weight: 600; color: rgba(255,255,255,.92);
  font-size: 17px; letter-spacing: -.02em;
}
.stage-mark span { color: var(--accent); font-weight: 600; }
.stage-mark-sub {
  font-family: var(--ui); font-size: 11px; letter-spacing: .04em;
  color: rgba(255,255,255,.45); margin-top: 2px;
  text-transform: none; font-weight: 400;
}

/* Boot loader — visible feedback while the data files load and React mounts.
   Replaced by the app the moment ReactDOM.render runs. */
.boot-loader {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 16px; z-index: 2;
  font-family: var(--ui); color: rgba(255,255,255,.6);
}
.boot-spinner {
  width: 30px; height: 30px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,.18);
  border-top-color: var(--accent);
  animation: boot-spin .8s linear infinite;
}
.boot-text { font-size: 12px; letter-spacing: .06em; text-transform: uppercase; }
@keyframes boot-spin { to { transform: rotate(360deg); } }
body.bezel-free .boot-loader { color: rgba(0,0,0,.4); }
body.bezel-free .boot-spinner { border-color: rgba(0,0,0,.12); border-top-color: var(--accent); }

/* ───── App body (inside iOS frame) ───── */
.app {
  width: 100%; height: 100%;
  background: var(--bg);
  position: relative;
  font-family: var(--ui);
  color: var(--label);
  display: flex; flex-direction: column;
  overflow: hidden;
  padding-top: 58px;
}
.app-scroll {
  flex: 1; overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 120px; /* room for tab bar */
}
.app-scroll::-webkit-scrollbar { display: none; }

/* ───── Bezel-free frame (no device chrome) ─────
   Locked to the 402px design width and uniformly scaled to fit the
   viewport by JS (see scaler in the bezel-free HTML), so the app renders
   pixel-identical to the design on any phone instead of reflowing. */
.bezel-free-frame {
  position: absolute;
  top: 0;
  left: 50%;
  width: 402px;            /* design width — JS sets height + transform */
  transform-origin: top center;
  transform: translateX(-50%);
  background: var(--bg);
  overflow: hidden;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.06);
}
.bezel-free-frame .app { padding-top: calc(env(safe-area-inset-top, 0px) + 4px); }
/* When bezel-free, drop the stage backdrop + watermark for a clean full-bleed screen.
   Use the app's own background (not black) so the safe-area strip behind the
   Dynamic Island / status bar reads as the app, edge-to-edge. */
body.bezel-free .app-stage { background: var(--bg); }
body.bezel-free .app-stage::before { display: none; }
body.bezel-free .stage-mark { display: none; }
/* Hide the floating tab bar while a Courts venue sheet is open (the sheet covers
   the whole screen; on iOS the tab bar can otherwise paint in front of it). */
body.venue-open .tabbar { display: none; }
/* Paint the page canvas (status-bar strip / overscroll) with the app colour
   instead of black, so the app reads edge-to-edge into the phone's top area. */
html.bezel-free, html.bezel-free body.bezel-free { background: var(--bg); }
/* While the Courts map is up, the page canvas takes the map's paper tone instead:
   Safari (and a stale-meta PWA) cannot render the page behind the status bar /
   Dynamic Island, so that strip shows the CANVAS colour — on --bg it reads as a
   grey band over the whiter map. Matching the tile tone makes the map read as
   spilling behind the island / below the frame even where it physically can't. */
html.bezel-free:has(.app[data-screen-label="03 Courts"]),
html.bezel-free body.bezel-free:has(.app[data-screen-label="03 Courts"]),
body.bezel-free:has(.app[data-screen-label="03 Courts"]) .app-stage,
body.bezel-free:has(.app[data-screen-label="03 Courts"]) .bezel-free-frame {
  background: var(--map-paper);
}

/* ───── Large title block ───── */
.title-block { padding: 4px 20px 14px; }
.tb-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px;
}
.eyebrow {
  font-family: var(--ui); font-size: 13px; letter-spacing: -.02em;
  text-transform: none; color: var(--label-2); font-weight: 400;
  display: flex; align-items: center; gap: 0; margin-bottom: 4px;
  white-space: nowrap;
}
.eyebrow::before { display: none; content: none; }
.eyebrow-2 { color: var(--label-2); }
.h-display {
  font-family: var(--ui); font-weight: 700;
  font-size: 34px; line-height: 1.07; letter-spacing: -.022em;
  color: var(--label);
}
.h-display em { display: none; }    /* drop the editorial colored "." */
.h-sub {
  font-family: var(--ui); font-style: normal; font-weight: 400;
  font-size: 15px; line-height: 1.4;
  color: var(--label-2); margin-top: 2px;
  letter-spacing: -.01em;
}

/* ───── Generic cards — iOS grouped style ───── */
/* All raised cards share the top-lit depth stack. A card that needs to read as
   recessed/flat opts out with its own inline box-shadow (inline wins over this rule)
   — e.g. recessed wells set an inset shadow, the calendar card sets box-shadow:none. */
.card {
  background: var(--bg-2);
  border: none;
  border-radius: 14px;
  padding: 14px 16px;
  position: relative;
  box-shadow: var(--raise-card);
}
.card-flat {
  background: var(--bg-2);
  border: none;
  border-radius: 14px;
  box-shadow: var(--raise-card);
}
.card-green {
  background: var(--positive-tint);
  border: none; border-radius: 14px;
  box-shadow: var(--raise-card);
}
.card-red {
  background: var(--destructive-tint);
  border: none; border-radius: 14px;
  box-shadow: var(--raise-card);
}
/* Reusable depth utilities — same tokens as .card / .sheet, for inline surfaces
   and controls that don't use those classes. .sr-raised on any card/sheet;
   .sr-engraved on any handle, groove, or icon button. */
.sr-raised { box-shadow: var(--raise-card); }
.sr-engraved {
  background: var(--well);
  box-shadow: var(--engrave);
}
.sr-engraved-lg {
  background: var(--well);
  box-shadow: var(--engrave-lg);
}

/* Numeric/label primitives — iOS scale */
.k-label {
  font-family: var(--ui); font-size: 12px; letter-spacing: -.01em;
  text-transform: none; color: var(--label-2); font-weight: 500;
}
.k-label-g { color: var(--positive); }
.k-label-r { color: var(--destructive); }
.k-num {
  font-family: var(--ui); font-weight: 600; line-height: 1.1;
  color: var(--label); letter-spacing: -.022em;
}
.k-num-g { color: var(--positive); }
.k-num-r { color: var(--destructive); }

/* ───── Bottom tab bar — Instagram-style floating pill ───── */
.tabbar {
  position: absolute; left: 0; right: 0;
  /* The pill sits on the bottom of the frame, and the frame is now clamped to the
     viewport WebKit lays out — so this IS the bottom of everything the app can draw.
     On iOS 26/27 standalone there is an unreachable band below it (--sr-vv-gap, the
     layout viewport's shortfall; 0 everywhere healthy). That band is dead space
     between the pill and the physical screen edge, i.e. it already clears the home
     indicator — so subtract it here, or the inset gets cleared twice and the pill
     floats (the "menu not at the bottom" report on #205). */
  bottom: 0;
  z-index: 30;
  display: flex; justify-content: center;
  padding: 0 0 max(6px, calc(env(safe-area-inset-bottom, 0px) - 10px - var(--sr-vv-gap, 0px)));
  /* The strip behind the pill is a FADE, never an opaque band: solid --bg only at
     the very bottom edge (home-indicator seat), dissolving to transparent so the
     screen's content passes visibly beneath the floating pill — the pill's own
     frosted material carries legibility, not this strip. */
  background: linear-gradient(to top, var(--bg) 8%, color-mix(in srgb, var(--bg) 55%, transparent) 46%, transparent);
  border: none;
  pointer-events: none;             /* only the pill is interactive */
}
/* Phone build — floating-pill clearance.
   With viewport-fit=cover gone, env(safe-area-inset-bottom) resolves to 0 (the
   system owns the home-indicator inset now, the page never covers it), so the
   max() above collapses to its 6px floor and the pill ends up sitting on the very
   edge of the app's box. A floating pill wants real air under it — the standard
   for this pattern (Instagram, Apple Maps) is a visible gap above the indicator,
   not a pill flush to the bottom. 24px restores exactly the clearance inset
   devices used to get from `inset-bottom - 10px` (34 - 10). Scoped to the phone
   build so the desktop bezel reference keeps its own 6px seat. */
.bezel-free-frame .tabbar {
  padding-bottom: max(24px, calc(env(safe-area-inset-bottom, 0px) - 10px - var(--sr-vv-gap, 0px)));
}
/* Courts map: no strip at all — the map spills full-bleed to the screen's bottom
   edge under the floating pill (iOS-Maps-style), instead of dissolving into --bg. */
.app[data-screen-label="03 Courts"] .tabbar { background: none; }
.tabbar-inner {
  pointer-events: auto;
  position: relative;
  width: min(330px, 84%);
  display: flex; align-items: center; justify-content: space-between;
  gap: 4px;
  padding: 7px 18px;
  border-radius: 999px;
  /* translucent frosted material */
  background: var(--material-bar);
  backdrop-filter: blur(34px) saturate(190%);
  -webkit-backdrop-filter: blur(34px) saturate(190%);
  border: .5px solid rgba(255,255,255,.55);
  box-shadow: 0 4px 16px rgba(20,16,12,.10), 0 1px 4px rgba(20,16,12,.07);
  transform-origin: bottom center;
  transition: transform .30s cubic-bezier(.32,.72,0,1), opacity .30s, box-shadow .30s;
}
/* scroll-down → shrink; scroll-up → grow */
.tabbar.shrunk .tabbar-inner {
  transform: scale(.80) translateY(8px);
  opacity: .94;
  box-shadow: 0 3px 12px rgba(20,16,12,.08), 0 1px 4px rgba(20,16,12,.06);
}
.tab {
  flex: 0 0 auto; border: none; background: none; cursor: pointer;
  position: relative;
  padding: 4px; margin: 0; -webkit-tap-highlight-color: transparent;
  display: flex; align-items: center; justify-content: center;
  position: relative;
  color: var(--label-2);
  transition: color .15s, transform .1s;
}
.tab:active { transform: scale(.88); }
.tab.active { color: var(--brand); }
.tab svg { display: block; position: relative; z-index: 1; }
.tab-msg-dot {
  position: absolute; top: 8px; right: calc(50% - 13px);
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--badge); z-index: 2;
}
.tab-indicator {
  position: absolute; top: 50%; left: 0;
  width: 66px; height: 50px; border-radius: 999px;
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  z-index: 0; pointer-events: none;
  transition: transform .42s cubic-bezier(.34,1.32,.5,1), opacity .2s;
}
/* center create button — brand-tinted plus.circle.fill */
.tab-create {
  flex: 0 0 auto; border: none; background: none; cursor: pointer;
  padding: 2px; margin: 0; -webkit-tap-highlight-color: transparent;
  display: flex; align-items: center; justify-content: center;
  color: var(--brand);
  transition: transform .1s;
}
.tab-create:active { transform: scale(.9); }
/* Drop-shadow on the filled + icon: the shadow shows through the plus cutout so the
   "+" reads as recessed/dropping a shadow, rather than a ring on the button. */
.tab-create svg { display: block; filter: drop-shadow(0 2.5px 2px rgba(0, 0, 0, 0.75)); }

/* ───── Status pills — iOS capsule style ───── */
.pill {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--ui); font-size: 12px; letter-spacing: -.01em;
  text-transform: none; padding: 3px 9px; font-weight: 500;
  border: none; color: var(--label-2);
  background: var(--fill-1); border-radius: 999px;
}
.pill-g { color: var(--positive); background: var(--positive-tint); }
.pill-r { color: var(--destructive); background: var(--destructive-tint); }
.pill-o { color: var(--purple); background: var(--purple-tint); }
.pill-y { color: var(--warning); background: var(--warning-tint); }
.pill .dot-pill { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* ───── Badge — the caps status chip ─────
   THE canonical small-caps status/label chip (SCHEDULED / PAID / UNPAID /
   NOT BOOKED / CANCELLED / NOTIFY / BOOK …). Distinct primitive from .pill
   above, which is a 12px capsule FILTER with text-transform:none — not a
   caps chip. Skin per variant with the three custom props; never re-declare
   the geometry inline (it drifted to 9.5/10/10.5/11px across ~30 hand-rolled
   copies before this class existed).
     --sr-badge-fg / -bg / -bd   fall back to a neutral outlined chip. */
.sr-badge {
  display: inline-flex; align-items: center; flex-shrink: 0;
  /* padding-bottom:1px is OPTICAL, not spacing. The chip is caps-only, so no glyph
     ever descends — but the font still reserves a descender (SF: ascent 10 + descent 2
     for a 10px em), and align-items:center centres the LINE BOX including that unused
     reserve, dropping the visible caps ~0.5px. The 1px bottom pad lifts them back:
     measured ink offset 0.95px → 0.05px. height stays 15 (border-box absorbs it).
     If --fs-badge or the UI font ever changes, re-measure — this is metric-specific. */
  box-sizing: border-box; height: 15px; padding: 0 6px 1px;
  border-radius: var(--r-sm);
  font-family: var(--ui); font-size: var(--fs-badge); font-weight: 700;
  letter-spacing: .04em; line-height: 1; text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid var(--sr-badge-bd, var(--sep));
  background: var(--sr-badge-bg, transparent);
  color: var(--sr-badge-fg, var(--label-2));
}
/* Court chip (C4, C7) — already-caps glyphs, so uppercase is a no-op and the
   caps tracking would only push the digits apart; tabular-nums keeps a column
   of them edge-aligned. */
.sr-badge.sr-badge-court {
  letter-spacing: -.01em; font-variant-numeric: tabular-nums;
}

/* ───── Buttons — iOS filled / tinted ───── */
.btn {
  height: 50px; border-radius: 12px;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 0 18px; cursor: pointer; user-select: none;
  font-family: var(--ui); font-size: 17px; letter-spacing: -.022em;
  text-transform: none; font-weight: 600;
  border: none; background: var(--label); color: var(--bg-2);
  transition: transform .08s, opacity .15s;
}
.btn:active { transform: scale(.985); opacity: .85; }
/* Primary CTA rides the brand burgundy (REQ-091; was --positive green). */
.btn-primary { background: var(--brand); color: #fff; }
.btn-primary:hover { background: var(--brand-2); }

/* ───────────────── Canonical button system (.sr-btn) ─────────────────
   THE shared button SHELL — geometry + mechanics ONLY (size · radius · press ·
   disabled). It carries NO colour; a --skin picks the look. Skins are named for
   their SwiftUI equivalents so the eventual iOS port maps 1:1 onto stock styles:
     --prominent    filled accent  → .borderedProminent   (the primary CTA)
     --gray         grey fill      → .bordered + role:.cancel
     --destructive  red            → role:.destructive
     --plain        text only      → .borderless
   Width layers on via a geometry modifier — never re-declare identity inline:
     (default)   fixed 170px, centred          — confirm / peek sheets
     --auto      hugs content + side padding    — footer CTAs (may carry an arrow)
     --grow      flex:1, fills a row by a Cancel — edit sheets
     --float     floats bottom-centre, blurred  — add / save overlays
     --block     full-width (100%)              — connect / consent sheets
   Theme --prominent per-instance with --sr-pill-accent (defaults --brand); the
   shadow follows. Divs that can't take :disabled use .is-disabled.
   .sr-action-pill is the LEGACY ALIAS for .sr-btn--prominent — co-listed below so
   every existing CTA keeps rendering byte-for-byte the same. New code uses .sr-btn. */
.sr-btn, .sr-action-pill {
  box-sizing: border-box; width: 170px; height: var(--sr-btn-h-lg); min-height: var(--sr-btn-h-lg);
  align-self: center; text-align: center;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: none; border-radius: var(--sr-btn-shape);
  font-family: inherit; font-size: 16px; font-weight: 600; letter-spacing: -.01em;
  cursor: pointer; transition: background .25s, color .25s, opacity .2s;
}
.sr-btn:active:not(:disabled):not(.is-disabled),
.sr-action-pill:active:not(:disabled):not(.is-disabled) { opacity: .85; }
.sr-btn:disabled, .sr-btn.is-disabled,
.sr-action-pill:disabled, .sr-action-pill.is-disabled {
  color: var(--label-3); background: var(--sep-soft);
  box-shadow: none; cursor: default;
}
/* ── skins (colour only) ── */
.sr-btn--prominent, .sr-action-pill {
  color: var(--bg-2); background: var(--sr-pill-accent, var(--brand));
  box-shadow: 0 6px 18px color-mix(in srgb, var(--sr-pill-accent, var(--brand)) 32%, transparent);
}
.sr-btn--gray  { background: var(--fill-1); color: var(--ink); box-shadow: none; }
.sr-btn--plain { background: transparent; color: var(--brand); box-shadow: none; }
/* role: destructive — red text (on --gray/--plain), or red fill on --prominent.
   Mirrors SwiftUI role:.destructive, which recolours whatever button style it rides. */
.sr-btn--destructive { color: var(--destructive); }
.sr-btn--destructive.sr-btn--prominent {
  color: var(--bg-2); background: var(--destructive); box-shadow: none;
}
/* ── geometry modifiers — after the skins so they win on equal specificity ── */
.sr-btn--auto,  .sr-action-pill--auto  { width: auto; padding: 0 44px; white-space: nowrap; }
.sr-btn--grow,  .sr-action-pill--grow  { width: auto; flex: 1; height: var(--row-md); box-shadow: none; }
.sr-btn--block, .sr-action-pill--block { width: 100%; }
.sr-btn--sm { width: auto; height: 38px; min-height: 38px; padding: 0 16px; font-size: 13.5px; box-shadow: none; }
.sr-btn--float, .sr-action-pill--float {
  position: absolute; left: 50%; bottom: 28px; transform: translateX(-50%);
  width: auto; height: var(--row-md);
  background: color-mix(in srgb, var(--brand) 90%, transparent); box-shadow: 0 8px 24px color-mix(in srgb, var(--brand) 32%, transparent);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
}

/* ───── Small icon / header buttons — canonical (icon toggles · add · pills) ─────
   THE little control that lives in grid & section headers: a 30px inset-well icon
   toggle. Grey inactive skin in the base; .is-active flips to the accent fill. Theme
   per-instance with --sr-btn-accent (default --brand). The :active scale is the press
   bounce — retiring the hand-rolled sr-bounce-btn / sr-hdr-btn / cb-sink duplicates.
     --label  hug-content pill for a short label (length / mode)
     --add    brand-filled circular add (+ / person-add), engraved dome */
.sr-icon-btn {
  box-sizing: border-box; width: 30px; height: 30px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid rgba(60,60,67,0.2); border-radius: var(--r-pill);
  background: var(--fill-2); color: var(--ink-2);
  font-family: inherit; font-size: 12px; font-weight: 600; letter-spacing: -.03em;
  cursor: pointer; box-shadow: inset 0 1px 2.5px rgba(0,0,0,0.16);
  transition: background .15s, color .15s, border-color .15s, box-shadow .15s, transform .24s cubic-bezier(.34,1.56,.64,1);
}
.sr-icon-btn.is-active {
  background: var(--sr-btn-accent, var(--brand));
  border-color: var(--sr-btn-accent, var(--brand));
  color: var(--bg-2); box-shadow: inset 0 2px 4px rgba(0,0,0,0.35);
}
.sr-icon-btn:active { transform: scale(.85); }
.sr-icon-btn:disabled { opacity: .4; cursor: default; }
.sr-icon-btn--label { width: auto; min-width: 30px; padding: 0 8px; }
.sr-icon-btn--add {
  border: none; border-radius: 50%; color: var(--bg-2);
  background: var(--sr-btn-accent, var(--brand)); box-shadow: var(--engrave);
}

/* Neutral secondary chip — the quiet grey action beside .sr-action-pill. */
.sr-chip {
  box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
  height: 36px; padding: 0 22px; border: none; border-radius: var(--r-pill);
  background: var(--fill-4); color: var(--ink);
  font-family: inherit; font-size: 14px; font-weight: 600; letter-spacing: -.01em;
  cursor: pointer; transition: background .15s, transform .24s cubic-bezier(.34,1.56,.64,1);
}
.sr-chip:active { transform: scale(.96); }

/* ───── Round icon button (.sr-round) — send · clear-✕ · map controls · menu-close ─────
   A circular tappable icon. Size via --sr-round-size (default 30); accent fill via
   .sr-round--accent + --sr-btn-accent. iOS: Button { Image(systemName:) }. */
.sr-round {
  box-sizing: border-box; width: var(--sr-round-size, 30px); height: var(--sr-round-size, 30px);
  flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%; padding: 0; cursor: pointer;
  background: var(--fill-1); color: var(--ink-2);
  transition: background .15s, color .15s, transform .2s cubic-bezier(.34,1.56,.64,1);
}
.sr-round:active { transform: scale(.88); }
.sr-round--accent { background: var(--sr-btn-accent, var(--brand)); color: var(--bg-2); }

/* Calendar-fold affordance (.sr-cal-fold) — the centred, chrome-less chevron tap
   target that cycles a calendar's height. Transparent by design (no button skin);
   this just unifies the tap geometry. Port: a plain chevron Button / DisclosureGroup. */
.sr-cal-fold {
  position: absolute; left: 50%; top: 0; transform: translateX(-50%);
  width: 46px; height: 17px; border: none; background: transparent; cursor: pointer;
  display: flex; align-items: center; justify-content: center; padding: 0;
}

/* ───── Switch (.sr-switch) — the one iOS toggle. Port: Toggle().toggleStyle(.switch) ─────
   Native UISwitch metrics: 51×31, thumb 27, on = --positive (systemGreen). Add .is-on. */
.sr-switch {
  box-sizing: border-box; width: 51px; height: 31px; flex-shrink: 0; padding: 0;
  border: none; border-radius: 999px; cursor: pointer; position: relative;
  background: var(--fill-4); transition: background .22s; box-shadow: var(--engrave);
}
.sr-switch.is-on { background: var(--positive); }
.sr-switch::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 27px; height: 27px;
  border-radius: 50%; background: var(--bg-2); box-shadow: 0 1px 3px rgba(0,0,0,.3);
  transition: left .2s cubic-bezier(.4,0,.2,1);
}
.sr-switch.is-on::after { left: 22px; }

/* ───── Stepper (.sr-stepper) — joined −/+ pill. Port: Stepper (UIStepper) ───── */
.sr-stepper { display: inline-flex; align-items: stretch; flex-shrink: 0; }
.sr-stepper button {
  width: 44px; min-height: 32px; border: none; background: var(--fill-1); color: var(--ink);
  cursor: pointer; font-family: inherit; font-size: 20px; line-height: 1; padding: 0;
  display: inline-flex; align-items: center; justify-content: center; transition: background .15s;
}
.sr-stepper button:first-child { border-radius: 999px 0 0 999px; }
.sr-stepper button:last-child  { border-radius: 0 999px 999px 0; }
.sr-stepper button:active { background: var(--fill-4); }
.sr-stepper .sr-stepper-sep { width: 1px; background: var(--sep); flex-shrink: 0; }

/* ───── List row-as-button (.sr-row) — inset-grouped List row.
   Port: List { NavigationLink } .listStyle(.insetGrouped). Separator via sibling ::before,
   inset to content; opt a leading-icon inset with .sr-row--lead. ───── */
.sr-row {
  box-sizing: border-box; position: relative; width: 100%;
  display: flex; align-items: center; gap: 12px; min-height: var(--row-sm); padding: 11px 16px;
  border: none; background: transparent; cursor: pointer; font-family: inherit;
  text-align: left; color: var(--ink); letter-spacing: -.01em;
}
.sr-row:active { background: var(--fill-2); }
/* single separator mechanism — a hairline span the row renders when not first (inset to content).
   Inset BOTH sides: 16px each, the same ruling .list-item::after already follows above and the
   payment-requests list states outright ("dividers inset 16px BOTH sides"). It used to bleed to
   the trailing edge, which made it the one hairline in the app that did not. Leading stays 16px
   here; rows with a leading glyph tile push it out to the label themselves. */
.sr-row-sep { position: absolute; top: 0; left: 16px; right: 16px; height: 1px; background: var(--sep); }

/* ───── Leading glyph tile for action / navigation rows (ruling 2026-08-11).
   Settings-style rows used to lead with a bare 20px glyph — the one context where iOS
   DOES use a tinted rounded square. 36 / r12 is not a new size: --row-xs is 36px and the
   notification-center payment button (notification-center.jsx:641) is already exactly this
   geometry in SOLID --brand. This is its tinted counterpart, registered once so the two
   components can't drift. Tint follows --tile-accent, so a row re-tints by setting one prop.
   Port: Label icon in RoundedRectangle(cornerRadius: 12).fill(accent.opacity(0.14)). ───── */
.sr-row-tile {
  --tile-accent: var(--brand);
  box-sizing: border-box; flex-shrink: 0;
  width: var(--row-xs); height: var(--row-xs);
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--tile-accent) 14%, transparent);
  color: var(--tile-accent);
}
/* Call sites hand the icons in at s={20}; inside the tile the glyph steps to 19 (mockup B).
   Descendant, not child — NcRow wraps the glyph in a span so `spin` rotates the GLYPH and
   leaves the square still. */
.sr-row-tile svg { width: 19px; height: 19px; }
.sr-row-tile--danger { --tile-accent: var(--destructive); }

/* ───── Lists — iOS inset-grouped ───── */
.list {
  background: var(--bg-2);
  margin: 0 16px 8px;
  border-radius: 14px;
  display: flex; flex-direction: column;
  overflow: hidden;
}
/* Sliding rows stay clipped by the list frame (overflow:hidden above), so a slid
   row goes BEHIND the rounded frame edge instead of poking over the 16px margin. */
.list.list-sliding { overflow: hidden; }
/* Transactions ledger list — FLAT OUTLINED: transparent surface (no raised
   card, no white group background), a hairline border frames the group, and
   rows carry no hairline dividers between them. */
/* padding-top 8 (not 16): the 32px first row centres its 13px text, adding ~8px
   of its own headroom — 8+8 reads level with the 16px side insets. */
.list.list-ledger { background: transparent; box-shadow: none; border: 1px solid var(--line); padding-top: 8px; }
.list.list-ledger .list-item::after { display: none; }
/* Recessed schedule list — a rim shadow overlay makes the list read as a well
   set slightly deeper than the surrounding plane. Sits above resting rows
   (z 1) but below a slid-out row (z 50) so the lift still reads. */
.list.list-recessed { position: relative; }
/* Clients tray: keep the recessed rim above a slid-open row so the well's inset shadow
   still drops onto the slid contact (rim z 70 > sliding wrapper z 60). */
.list.cli-list.list-recessed::before { z-index: 70; }
/* Two adjacent client BOOKINGS slid open at once: drop the doubled shadow at their shared
   edge and draw a padded hairline divider instead. Upper keeps side lift (loses bottom drop)
   and gains a left/right-inset divider; lower keeps side+bottom lift (loses top highlight). */
.bk-wrap.sliding-wrap:has(+ .bk-wrap.sliding-wrap) .bk-row {
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7),
              -8px 0 14px -6px rgba(0,0,0,.22), 8px 0 14px -6px rgba(0,0,0,.22) !important;
}
.bk-wrap.sliding-wrap:has(+ .bk-wrap.sliding-wrap) .bk-row::after {
  content: ""; position: absolute; left: 16px; right: 16px; bottom: 0; height: 1px;
  background: var(--line); z-index: 2;
}
.bk-wrap.sliding-wrap + .bk-wrap.sliding-wrap .bk-row {
  box-shadow: -8px 0 14px -6px rgba(0,0,0,.22), 8px 0 14px -6px rgba(0,0,0,.22),
              0 12px 24px -8px rgba(0,0,0,.32) !important;
}
/* A row directly below a swiped row drops its own top divider — the slid row's depth
   separates them. */
.bk-wrap.sliding-wrap + .bk-wrap .bk-row { background-image: none !important; }
.list.list-recessed::before {
  content: ""; position: absolute; inset: 0; border-radius: 14px;
  box-shadow: var(--recessed);
  pointer-events: none; z-index: 4;
}
/* No hairline divider directly above or below a recessed row (not-available /
   requested) — they read via their own inset shadow instead. */
.list-recessed .list-item.recessed-row::after { display: none; }
.list-recessed > div:has(+ div:not(.sr-collapsed) .recessed-row) .list-item::after { display: none; }
/* Recessed (not-available) es-slots form a single inset "well": keep the inset
   shadow on the group's OUTER top/bottom edges, but between two consecutive
   not-available rows drop the shadow and use a plain inset hairline instead. */
.list-recessed .list-item.es-slot.recessed-row {
  box-shadow: inset 0 8px 11px -8px rgba(0,0,0,0.32), inset 0 -8px 11px -8px rgba(0,0,0,0.16);
}
/* row below is also recessed → interior bottom edge: keep only the top shadow */
.list-recessed > div:has(+ div .recessed-row) .list-item.es-slot.recessed-row {
  box-shadow: inset 0 8px 11px -8px rgba(0,0,0,0.32);
}
/* row above is also recessed → interior top edge: keep only the bottom shadow */
.list-recessed > div:has(.recessed-row) + div .list-item.es-slot.recessed-row {
  box-shadow: inset 0 -8px 11px -8px rgba(0,0,0,0.16);
}
/* recessed both above AND below (middle of a run) → no shadow, dividers only */
.list-recessed > div:has(.recessed-row) + div:has(+ div .recessed-row) .list-item.es-slot.recessed-row {
  box-shadow: none;
}
/* the interior boundary between two consecutive recessed rows: inset hairline */
.list-recessed > div:has(+ div .recessed-row) .list-item.es-slot.recessed-row::after { display: block; }
/* Requested-session Accept button: force the check glyph white (Ic.check
   hardcodes a black stroke, ignoring the button's white color). */
.req-accept-btn svg path { stroke: #fff !important; }
/* Unpaid-lesson "Received" swipe: force the check glyph white too. */
.nc-received-check svg path { stroke: #fff !important; }
.list-item {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px;
  position: relative;
  background: transparent;
  min-height: var(--row-lg);   /* was a raw 60px — off the 36/44/52/64 row scale entirely */
}
/* Inset hairline divider (left/right 16px, var(--line)) — matches the session rows everywhere */
.list-item::after {
  content: ""; position: absolute; left: 16px; right: 16px; bottom: 0;
  height: 1px; background: var(--line);
}
.list-item:last-child::after { display: none; }
/* Session rows are each wrapped in their own swipe container, so the .sess
   item is always :last-child of that wrapper and wrongly loses its divider.
   Re-assert the hairline for session rows. */
.list-item.sess::after { display: block; }
/* In filtered view, the last session row is visually last — drop its hairline. */
.list-item.sess.no-div::after { display: none; }
/* Pending/accepted request rows are likewise wrapped in a swipe container,
   so re-assert their hairline divider too. */
.list-item.swipe-row::after { display: block; }
/* EmptySlot rows are wrapped in a Collapsible (Filter animation), making each
   the :last-child of its wrapper and wrongly losing its hairline. Re-assert it. */
.list-item.es-slot::after { display: block; }
/* …but the very last row in the list shouldn't have a trailing hairline. */
.list > div:last-child .list-item.es-slot::after { display: none; }
.list > div:last-child .list-item.sess::after { display: none; }
/* A session row being slid drops its own bottom hairline. */
.list-recessed .list-item.sess.sliding::after { display: none; }
/* …and the row directly above a sliding session drops its divider too. */
.list-recessed > div:has(+ div .sess.sliding) .list-item::after { display: none; }
.list-item:active { background: var(--fill-2); }
/* Client rows draw their own hairline in JS, as a SIBLING of the row inside .cli-wrap, so it
   stays put while the row slides. Canon 16/16 like every other divider; it cannot be the
   .list-item::after below because that one would slide with the row. */
.list-item.cli-row::after { display: none; }
/* Two adjacent client rows slid open at once: replace the doubled shadow at their shared
   edge with a hairline divider. Upper row keeps top+side lift (loses its bottom drop) and
   gains a bottom inset hairline; lower row keeps bottom+side lift (loses its top drop). */
.cli-wrap.sliding-wrap:has(+ .cli-wrap.sliding-wrap) .cli-row {
  box-shadow: inset 0 -1px 0 var(--line), inset 0 1px 0 rgba(255,255,255,.7),
              0 -6px 14px -6px rgba(0,0,0,.22),
              -8px 0 14px -6px rgba(0,0,0,.22), 8px 0 14px -6px rgba(0,0,0,.22) !important;
}
.cli-wrap.sliding-wrap + .cli-wrap.sliding-wrap .cli-row {
  box-shadow: 0 12px 24px -8px rgba(0,0,0,.32),
              -8px 0 14px -6px rgba(0,0,0,.22), 8px 0 14px -6px rgba(0,0,0,.22) !important;
}
/* Fixed --row-lg height for every client record (the 64px it already was, now named). */
.list-item.cli-row { height: var(--row-lg); padding-top: 0; padding-bottom: 0; box-sizing: border-box; }
/* Compact ⇄ regular density morph: height transitions on the reused row node,
   while the content cross-fades. The class flips cmpA⇄cmpB on toggle, which
   re-triggers the (otherwise identical) keyframes so the fade replays each time. */
.row-morph { transition: height .3s cubic-bezier(.4,0,.2,1), min-height .3s cubic-bezier(.4,0,.2,1), padding .3s cubic-bezier(.4,0,.2,1), transform .22s ease; }
.row-morph.dragging { transition: none; }
.list-item.cmpA > *, .list-item.cmpB > * { animation-duration: .3s; animation-timing-function: ease; animation-fill-mode: both; }
.list-item.cmpA > * { animation-name: rowFadeA; }
.list-item.cmpB > * { animation-name: rowFadeB; }
@keyframes rowFadeA { from { opacity: 0.3; } to { opacity: 1; } }
@keyframes rowFadeB { from { opacity: 0.3; } to { opacity: 1; } }

/* Avatars */
.avatar {
  width: 36px; height: 36px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--ui); font-weight: 600; font-size: 13px;
  letter-spacing: -.01em; flex-shrink: 0;
  border: none;
}
.av-g { background: var(--positive-tint); color: var(--positive); }
.av-o { background: var(--purple-tint); color: var(--purple); }
.av-y { background: var(--warning-tint); color: var(--warning); }
.av-r { background: var(--destructive-tint); color: var(--destructive); }
.av-b { background: color-mix(in srgb, var(--group) 14%, transparent); color: var(--group); } /* group clients — blue tint */

/* ───── Filter chip rows — iOS segmented look ───── */
.chip-row {
  display: flex; gap: 8px; padding: 0 16px 12px;
  overflow-x: auto;
}
.chip-row::-webkit-scrollbar { display: none; }
.chip-f {
  flex-shrink: 0;
  font-family: var(--ui); font-size: 14px; letter-spacing: -.01em;
  text-transform: none; padding: 7px 14px; font-weight: 500;
  border: none; color: var(--label);
  background: var(--fill-1);
  border-radius: 999px;
  cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; gap: 6px;
}
.chip-f.active { background: var(--accent); color: #fff; }
.chip-f .ct { opacity: .55; font-weight: 400; }
.chip-f.active .ct { opacity: .8; }

/* ───── Section divider — iOS plain header ───── */
.section-rule {
  display: flex; align-items: center; gap: 10px;
  padding: 20px 20px 8px;
}
.section-rule::after { display: none; }
.section-rule-lbl {
  font-family: var(--ui); font-size: 13px; letter-spacing: -.01em;
  text-transform: none; color: var(--label-2); font-weight: 400;
}
/* Group / section header — the ONE token for grouped-content headers (Today date
   groups, new-lesson-plus + venue section titles). Adopt this class instead of a
   bespoke label style. (New class, so the 6 existing .section-rule-lbl call sites
   are untouched.) */
.sr-group-header {
  font-family: var(--ui); font-weight: 600; white-space: nowrap;
  font-size: var(--fs-md); color: var(--label-2); letter-spacing: -0.1px;
}

/* ───── Sheet (create flow) — iOS bottom sheet ───── */
.sheet-back {
  position: absolute; inset: 0; background: var(--scrim);
  backdrop-filter: blur(0px); -webkit-backdrop-filter: blur(0px);
  z-index: 100; opacity: 0; pointer-events: none;
  /* Fade + blur ramp in on the same curve/length as the sheet's slide so the
     dim and the rise feel like one motion. */
  transition: opacity .42s cubic-bezier(.2,.8,.2,1),
              backdrop-filter .42s cubic-bezier(.2,.8,.2,1),
              -webkit-backdrop-filter .42s cubic-bezier(.2,.8,.2,1);
}
.sheet-back.open { opacity: 1; pointer-events: auto; backdrop-filter: none; -webkit-backdrop-filter: none; }
.sheet {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: var(--bg);
  border-top: none;
  border-radius: 38px 38px 0 0;
  z-index: 101; transform: translateY(100%);
  /* Closed sheets sit just below the viewport — hide them fully so their upward
     box-shadows can't bleed into the bottom of the screen (visibility flips
     instantly on open, and only after the slide-down finishes on close). */
  visibility: hidden;
  transition: transform .35s cubic-bezier(.2,.8,.2,1), visibility 0s .35s;
  max-height: 92%;
  display: flex; flex-direction: column;
  box-shadow: var(--raise-sheet);
}
.sheet.open { transform: translateY(0); visibility: visible; transition: transform .35s cubic-bezier(.2,.8,.2,1); }
.sheet-grab {
  width: 80px; height: 7px; border-radius: 4px;
  background: var(--well); margin: 6px auto 0;
  box-shadow: var(--engrave);
}
.sheet-head {
  padding: 12px 20px 4px;
  display: flex; align-items: center; justify-content: space-between;
}
.sheet-head-l {
  font-family: var(--ui); font-size: 13px; letter-spacing: -.01em;
  text-transform: none; color: var(--label-2); font-weight: 400;
}
/* .sheet-head-x retired (P3.1) — every sheet close uses the 40px Coin primitive. */
.sheet-body { flex: 1; overflow-y: auto; padding: 4px 20px 24px; }
.sheet-body::-webkit-scrollbar { display: none; }
/* Coin button — domed raised icon button (close/back) with a press-in click, box-shadow only.
   !important so it overrides per-button inline background/shadow; size-agnostic (28-52px). */
.sr-coin {
  background: var(--coin-bg, transparent) !important;
  color: var(--label-2) !important;
  border: 1px solid rgba(20,16,12,.14) !important;
  box-shadow: var(--coin) !important;
  transition: transform .12s ease, box-shadow .12s ease;
}
.sr-coin:active {
  transform: scale(.96);
  box-shadow:
    inset 0 2px 3px rgba(20,16,12,.34),
    inset 0 -1px 1px rgba(255,255,255,.4) !important;
}
.sheet-foot {
  padding: 12px 16px 26px; border-top: .5px solid var(--sep);
  background: var(--bg);
}

/* ───── Form inputs ───── */
.field {
  display: flex; flex-direction: column; gap: 6px;
  margin-bottom: 14px;
}
.field-lbl {
  font-family: var(--ui); font-size: 13px; letter-spacing: -.01em;
  text-transform: none; color: var(--label-2);
  font-weight: 400; padding: 0 4px;
}
/* Section header inside a sheet ("Client" / "Booking Summary" / "Payment" / "Booking
   Rules"). NOT a .field-lbl — that is the 13px/400 grey label that sits ON a field;
   this titles a whole block and was hand-rolled 12 times across create-sheet.jsx,
   create-sheet-plus.jsx and notification-center.jsx, in three near-identical
   spellings (-0.4px vs -.022em tracking), each fully overriding .field-lbl.
   ⚠️ 16px is NOT on the T8 ramp {11,12,13,15,17}. Kept at the inherited 16px so
   this stays a dedup, not a resize — the ramp reconciliation is a separate call. */
.sheet-section-lbl {
  font-family: var(--ui); font-size: 16px; font-weight: 600;
  letter-spacing: -.022em; color: var(--label);
  text-transform: none; padding: 0 4px;
}

/* Engraving axis: a form input is a WELL you write a value into — recessed, not a raised
   card. Subtle inset fill + --engrave-well, replacing the old flat white surface. */
.field-val {
  background: var(--fill-1); border: none;
  border-radius: 12px;
  box-shadow: var(--engrave-well);
  padding: 12px 14px; font-size: 16px; color: var(--label);
  font-family: var(--ui); font-weight: 400;
  display: flex; align-items: center; justify-content: space-between;
  cursor: pointer; min-height: 50px;
  letter-spacing: -.02em;
}
.field-val.placeholder { color: var(--label-3); font-style: normal; }
.field-val:hover { background: var(--fill-3); }
.field-val .chev { color: var(--label-3); }

/* ───── Outcome receipt — soft iOS card ───── */
.receipt {
  background: var(--bg-2); border: none;
  border-radius: 14px;
  padding: 16px;
}
@keyframes court-pan {
  0%, 14%   { transform: translateX(0); }
  86%, 100% { transform: translateX(var(--court-pan, 0)); }
}
.receipt-head {
  display: flex; align-items: baseline; justify-content: space-between;
  padding-bottom: 12px; margin-bottom: 4px;
  border-bottom: .5px solid var(--sep);
}
.receipt-head-t {
  font-family: var(--ui); font-weight: 600; font-size: 17px;
  letter-spacing: -.02em;
}
.receipt-head-s {
  font-family: var(--ui); font-size: 13px; letter-spacing: -.01em;
  text-transform: none; color: var(--label-2); font-weight: 400;
}
.receipt-row {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 0; border-bottom: .5px solid var(--sep);
}
.receipt-row:last-child { border-bottom: none; }
.receipt-check {
  width: 22px; height: 22px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--positive); color: #fff; flex-shrink: 0; margin-top: 1px;
}
.receipt-row-body { flex: 1; }
.receipt-row-t { font-size: 15px; color: var(--label); margin-bottom: 2px; font-weight: 400; }
.receipt-row-s {
  font-family: var(--ui); font-size: 13px; letter-spacing: -.01em;
  color: var(--label-2); text-transform: none; font-weight: 400;
}
.receipt-row-amt {
  font-family: var(--ui); font-weight: 600; font-size: 16px; color: var(--label);
  letter-spacing: -.02em;
}
.receipt-row-amt.gain { color: var(--positive); }
.receipt-row-amt.cost { color: var(--destructive); }

/* ANIM */
@keyframes fadeUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
@keyframes flash-in { 0% { opacity: 0; transform: scale(.94); } 100% { opacity: 1; transform: scale(1); } }
.flash { animation: flash-in .25s ease both; }
@keyframes scr-in { from { transform: translateY(6px); } to { transform: translateY(0); } }
@keyframes sheet-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes recal-scan { from { transform: translateX(-40px); } to { transform: translateX(calc(100% + 40px)); } }
.screen { animation: scr-in .25s ease both; }

/* Calendar month-swipe transitions */
@keyframes cal-slide-l { from { opacity: .3; transform: translateX(14px); } to { opacity: 1; transform: translateX(0); } }
@keyframes cal-slide-r { from { opacity: .3; transform: translateX(-14px); } to { opacity: 1; transform: translateX(0); } }

/* Picker row inset dividers — start at the option text, equal inset on the right */
.picker-row { position: relative; }
.picker-row::after {
  content: ""; position: absolute; left: 40px; right: 16px; bottom: 0; height: 1px;
  background: var(--sep);
}
.picker-row:last-child::after { display: none; }
/* Selected picker row: label + sub-line go burgundy + bold (overrides inline weight) */
.picker-row.is-selected > div:first-of-type div { font-weight: 600 !important; }
.picker-row.is-selected div, .picker-row.is-selected > span:last-child { font-weight: 600 !important; }
.picker-row.is-selected > span:last-child { font-weight: 600 !important; }
/* Selected venue row: court chips go black */
.picker-row.is-selected > div:first-of-type > div:nth-child(2) span { color: rgba(60,60,67,.6) !important; border-color: rgba(60,60,67,.35) !important; }

/* Compact dropdown-menu rows — same text-aligned, symmetric inset treatment */
.menu-row { position: relative; }
.menu-row::after {
  content: ""; position: absolute; left: 32px; right: 12px; bottom: 0; height: 1px;
  background: var(--line);
}
.menu-row:last-child::after { display: none; }

/* Availability-check spinning clock (Schedule slot ticks) */
@keyframes clk-spin { to { transform: rotate(360deg); } }
.clk-hand { transform-box: view-box; transform-origin: 12px 12px; }
.clk-min { animation: clk-spin .7s linear infinite; }
.clk-hr  { animation: clk-spin 2.1s linear infinite; }
@keyframes slot-pop { 0% { transform: scale(.6); opacity: 0; } 60% { transform: scale(1.12); } 100% { transform: scale(1); opacity: 1; } }
.slot-pop { animation: slot-pop .26s cubic-bezier(.2,.8,.3,1.2) both; }

/* Notification bell — periodic ring swing (pivots from the top). */
@keyframes sr-bell-ring {
  0%, 70%, 100% { transform: rotate(0); }
  74% { transform: rotate(15deg); }
  78% { transform: rotate(-12deg); }
  82% { transform: rotate(9deg); }
  86% { transform: rotate(-6deg); }
  90% { transform: rotate(3deg); }
  94% { transform: rotate(-1.5deg); }
}
.sr-bell-ring { transform-origin: 50% 15%; animation: sr-bell-ring 3.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .sr-bell-ring { animation: none; } }
@keyframes srToastIn { 0% { opacity: 0; transform: translate(-50%, 8px); } 100% { opacity: 1; transform: translate(-50%, 0); } }

/* SRToast — the canonical toast (ui.jsx window.SRToast; USER RULING 2026-08-03).
   The host centers and stacks; each pill is the one dark --toast look the Clients
   saved-toast established. Sits above dialogs/sheets on the --z-toast tier; never
   intercepts input. srToastPop = srToastIn minus the left-50% translate (the host
   flex-centers, so the pill needs no horizontal transform of its own). */
.sr-toast-host { position: absolute; left: 0; right: 0; bottom: 92px; z-index: var(--z-toast); display: flex; flex-direction: column; align-items: center; gap: 8px; pointer-events: none; }
.sr-toast { max-width: min(86%, 340px); padding: 10px 16px; border-radius: var(--r-pill); background: var(--toast); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); color: rgba(255,255,255,0.92); font-size: var(--fs-md); font-weight: 600; letter-spacing: -.01em; line-height: 1.4; text-align: center; box-shadow: 0 6px 20px rgba(20,16,12,0.16); animation: srToastPop .22s ease; }
@keyframes srToastPop { 0% { opacity: 0; transform: translateY(8px); } 100% { opacity: 1; transform: translateY(0); } }

/* Schedule dot on a booking-summary row pulses when it marks a CONFLICT
   (the coach already has a session at the same time as this booking). */
@keyframes sr-dot-conflict {
  0%, 100% { transform: translateY(-50%) scale(1); box-shadow: 0 0 0 0 rgba(255,59,48,0.55); }
  50%      { transform: translateY(-50%) scale(1.28); box-shadow: 0 0 0 4px rgba(255,59,48,0); }
}
.sr-dot-conflict { animation: sr-dot-conflict 1.15s cubic-bezier(.4,0,.2,1) infinite; }
@media (prefers-reduced-motion: reduce) { .sr-dot-conflict { animation: none; } }

/* The lesson rail uses the canonical filled person for a scheduling conflict. Keep
   the established conflict pulse while avoiding the dot's translateY transform. */
@keyframes sr-conflict-person {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%      { transform: scale(1.14); opacity: .78; }
}
.sr-conflict-person { animation: sr-conflict-person 1.15s cubic-bezier(.4,0,.2,1) infinite; }
@media (prefers-reduced-motion: reduce) { .sr-conflict-person { animation: none; } }

/* Court-block sibling flash (blocks P5) — when a tap on ONE cell of a multi-slot
   block marks/unmarks the WHOLE block, the sibling cells pulse briefly in the
   chip's own colour (--sr-flash-c, set per-cell by the grid) as feedback that they
   changed state because of that tap. ~0.5s per pulse, run twice, then done. */
@keyframes sr-block-flash {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  50%      { box-shadow: 0 0 0 3.5px var(--sr-flash-c, color-mix(in srgb, var(--brand) 55%, transparent)); }
}
.sr-block-flash { animation: sr-block-flash .5s ease-in-out 2; }
@media (prefers-reduced-motion: reduce) { .sr-block-flash { animation: none; } }

/* ═══════════════════════════════════════════════════════════════════════════
   Booking sneak-peek card (window.BookingSneakPeek — booking-sneak-peek.jsx)
   A peek that opens when a coach taps a private lesson; "Open lesson" expands it
   into the full create-lesson-plus editor. Standard sheet chrome: the .sheet /
   .sheet-back shell + SheetHeader (tokenised grab + × close coin), the canonical
   .list.list-recessed well, and .btn buttons — no card-local palette.
   ═══════════════════════════════════════════════════════════════════════════ */
.spk-content { padding: 2px 0 calc(16px + env(safe-area-inset-bottom)); -webkit-user-select: none; user-select: none; }
.spk-head { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--label-3); margin: 6px 18px 6px; }
/* client peek — contact well (no swipe row here, so plain rows) */
.spk-contact { padding: 12px 14px; }
.spk-contact a { color: var(--brand); text-decoration: none; }
.spk-contact .l { font-size: 15px; color: var(--label); letter-spacing: -.01em; display: flex; align-items: center; gap: 9px; }
.spk-contact .s { font-size: 13px; color: var(--label-2); letter-spacing: -.01em; display: flex; align-items: center; gap: 9px; margin-top: 8px; }
.spk-contact svg { color: var(--label-2); flex-shrink: 0; }
/* court-booking peek — gate-PIN well */
.spk-pin { display: flex; align-items: center; gap: 12px; padding: 12px 14px; }
.spk-pin .lock { width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--brand) 12%, transparent); color: var(--brand); }
.spk-pin .digits { font-size: 28px; font-weight: 700; color: var(--label); letter-spacing: .14em; font-variant-numeric: tabular-nums; line-height: 1.15; }
/* swipe-to-reveal row inside the canonical .list.list-recessed well */
.spk-swipe { position: relative; }
.spk-row {
  position: relative; z-index: 2; background: var(--bg-2);
  padding: 12px 14px; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  touch-action: pan-y; cursor: grab;
}
.spk-row .l { font-size: 15px; color: var(--label); letter-spacing: -.01em; display: flex; align-items: center; gap: 6px; }
.spk-row .l svg { color: var(--label-2); flex-shrink: 0; }
.spk-row .s { font-size: 12px; color: var(--label-2); margin-top: 3px; display: flex; align-items: center; gap: 5px; }
.spk-row .s svg { color: var(--label-3); flex-shrink: 0; }
.spk-row .amt { font-size: 14px; color: var(--label-2); flex-shrink: 0; font-variant-numeric: tabular-nums; font-weight: 600; letter-spacing: -.01em; }
/* private peek total — matches the schedule row's SchedAmount (weight 500, 17px,
   --label) with the pay method beneath it (11px, --label-2). */
.spk-amt { flex-shrink: 0; text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.spk-amt .v { font-weight: 500; font-size: 17px; color: var(--label); letter-spacing: -.022em; font-variant-numeric: tabular-nums; }
.spk-amt .m { font-size: 11px; color: var(--label-2); letter-spacing: -.01em; }
.spk-act { position: absolute; top: 0; bottom: 0; width: 72px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; z-index: 1; font-size: 11px; font-weight: 700; letter-spacing: .01em; color: #fff; cursor: pointer; }
.spk-act svg { display: block; }
.spk-act.remind { left: 0; background: var(--warning); }
/* group-lesson peek — PAYE chip (informational, not a status pill). Was a private
   9.5px re-declaration of the badge geometry; it is now plain .sr-badge, whose
   neutral fallback skin (--sep border / --label-2 ink) is what it already wanted. */
.spk-act.paid { right: 0; background: var(--positive-2); }
.spk-btnrow { display: flex; gap: 10px; margin: 14px 16px 0; }
/* canonical .sr-action-pill CTAs, split full-width (override its fixed 170px).
   --spk-accent (set per minicard) themes them to the booking-type colour; falls
   back to --brand. */
.spk-btnrow .sr-action-pill { flex: 1; width: auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px; background: var(--spk-accent, var(--brand)); box-shadow: 0 6px 18px color-mix(in srgb, var(--spk-accent, var(--brand)) 30%, transparent); }
.spk-btnrow .sr-action-pill svg { display: block; }
/* venue peek — About row + coach/lesson list rows + "book on website" badge */
.spk-info { padding: 12px 14px; }
.spk-info .l { font-size: 15px; color: var(--label); letter-spacing: -.01em; }
.spk-info .s { font-size: 12px; color: var(--label-2); letter-spacing: -.01em; margin-top: 3px; }
.spk-lrow { display: flex; align-items: center; gap: 11px; padding: 11px 14px; }
.spk-lrow .av { width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--positive-2) 14%, transparent); color: var(--positive-2); }
.spk-lrow.blue .av { background: color-mix(in srgb, var(--group) 14%, transparent); color: var(--group); }
.spk-lrow .lm { min-width: 0; }
.spk-lrow .ln { font-size: 14.5px; font-weight: 600; color: var(--label); letter-spacing: -.01em; }
.spk-lrow .ls { font-size: 12px; color: var(--label-2); margin-top: 1px; letter-spacing: -.01em; }
.spk-badge { display: inline-flex; align-items: center; gap: 5px; margin: 6px auto 2px; padding: 4px 9px; border-radius: 6px; background: var(--fill-1); color: var(--label-2); font-size: 10px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
