/* ============================================================
   DinkedIn — Cebu
   Clean, light-first: Paper ground, hairline borders, generous radii.
   One Dink Yellow (lime) FILL with Ink text is the primary action;
   lime-family text and strokes use --hi-strong. Court Pink survives
   only as --danger. Information hierarchy comes from weight and
   spacing, not hue. Binding rules: docs/design/components.md.

   SECTION MAP. Search for the § anchor to jump. Each section has
   one owner during a build wave; see docs/design/components.md.
     §0  Components + page templates (docs/design/ui-framework.md)
     §1  Tokens (colour × 4 theme blocks, type, space, tap, z)
     §2  Base (reset, body, focus, inputs)
     §3  Components (shared): buttons, pills, avatars, cards,
         court card, queue row, leaderboard row, delta chip,
         bottom sheet, stepper, skeleton, offline bar, empty
         state, footer credit
     §4  Masthead + nav           (shell)
     §5  Courts finder            (courts: stat strip → map view)
     §6  Bookings + pickers       (courts: bookings → set-up court)
     §7  Scorer                   (game)
     §8  iPad board               (board: courtside board → open play board)
     §9  Open Play on a phone     (game)
     §10 Groups regions           (one START/END region per owner)
     §11 P2 components            (CSS only, unused until phase 2)
   ============================================================ */

/* ══ §0 Components ══════════════════════════════════════════
   The inventory (v2: borderless, lighter type, one framework). One class
   family per job; variants are modifiers. Feature CSS further down may set
   LAYOUT only (grid, gap, order, placement, width). Colour, radius, type,
   padding and shadow come from here. Spec: docs/design/ui-framework.md.
     .btn (--primary --ink --soft --on-dark --quiet --danger --state, --sm --lg --block)
     .icon-btn (--sm --bare) · .seg > .seg-item (--fill) · .chip (--on --token)
     .tag (--neutral --lime --tint --loss --dot) · .field (--float --chat)
     .card (--dark --quiet) > .card-head · .tile · .list-row · .stat (--tile)
     .stat-strip · .bar · .bar-chart · .avatar-stack · .page-head
     .crew-line · .door (--invite) · .overlay (--s --m --l --pop)
     .shout · .label · .say (--shout) · .sticker · .tilt-l .tilt-r · .pill-nav
   Crew v2 kit (docs/design/rebrand/README.md): no borders, hairlines,
   gradients or shadows; a colour block separates things. One Ink primary
   per view. Text on colour is always Ink. Radii: --r-lg 40, --r-card 30,
   --r-tile 22, --r-field 14, --r-pill. Type: Bricolage 800 for score,
   shout, titles and headings; Figtree 800 for buttons and chips, 500-700
   for text; JetBrains Mono 600 caps for labels. Press scales to .95.
   Tilt: ±1-3° playful cards and rows, 4-8° stickers, rewards and bubbles,
   0° on forms, inputs, payments and settings.
   Page templates ([data-tpl] on every view) close this block. */

/* ── button ── 56 (sm 44, the minimum), pill, Figtree 800, no border. A
   bare .btn is the primary: Ink with white text, the one main action in a
   view (--primary and --ink are the same). --soft is the secondary: White
   on a colour or on Cream, Cream on a White card or sheet. --lime is a
   lime fill for a highlight action on Ink. */
.btn {
  min-height: var(--tap-lg); padding: 0 24px; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font-family: var(--font-body); font-size: var(--t-button); font-weight: 800; line-height: 1.2;
  white-space: nowrap; text-align: center; text-decoration: none; cursor: pointer;
  border: 0; border-radius: var(--r-pill);
  background: var(--ink); color: var(--white);
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.btn:hover { background: color-mix(in srgb, var(--ink) 88%, var(--white)); }
.btn:active { transform: scale(.95); }
.btn svg { width: 18px; height: 18px; flex: none; }
.btn--primary, .btn--ink { background: var(--ink); color: var(--white); }
.btn--lime { background: var(--lime); color: var(--ink); }
.btn--lime:hover { background: var(--accent-2); }
.btn--soft { background: var(--btn-2, var(--white)); color: var(--ink); }
.btn--soft:hover { background: color-mix(in srgb, var(--ink) 6%, var(--btn-2, var(--white))); }
:is(.card:not(.card--dark):not(.card--quiet), .overlay, .tile) { --btn-2: var(--cream); }
/* On an ink card (--dark): white on ink, the soft button turned over. */
.btn--on-dark { background: var(--white); color: var(--ink); }
.btn--on-dark:hover { background: var(--cream); }
.btn--quiet { background: none; color: var(--ink-2); padding-inline: 12px; }
.btn--quiet:hover { background: var(--surface-2); color: var(--ink); }
.btn.btn--danger { color: var(--loss); }
.btn.btn--danger:hover { color: var(--loss); background: var(--loss-tint); }
/* A pressed choice (a team, an option in a pair). */
.btn--soft[aria-pressed="true"] { background: var(--lime-tint); color: var(--on-lime-tint); }
/* The RSVP morph: one control whose look follows the user's state. Bare
   (.is-in) it is primary; Going and Out settle into calmer fills. */
.btn--state { transition-property: background-color, color, box-shadow, width, transform; }
.btn--state.is-going { background: var(--lime-tint); color: var(--on-lime-tint); }
.btn--state.is-out { background: var(--surface-2); color: var(--ink-2); }
.btn--sm { min-height: var(--tap); padding-inline: 18px; font-size: 14px; }
.btn--lg { min-height: var(--tap-lg); padding-inline: 28px; font-size: 17px; }
/* --xl: the homepage's calls to action (§13), 56 (--tap-lg), 17/800. */
.btn--xl { min-height: var(--tap-lg); padding-inline: 28px; font-size: 17px; }
.btn--block { display: flex; width: 100%; }
@media (max-width: 767.98px) { .btn--lg { width: 100%; } }
/* --touch: a pair of answers; 56 like every button (kept for its callers). */
@media (max-width: 767.98px) { .btn--touch { min-height: var(--tap-lg); } }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .45; pointer-events: none; cursor: default; }

/* ── icon button ── a 44 circle in --surface-2 (Sand). Always
   carries an aria-label. --sm is the 30px close (44 to tap); --bare drops the fill. */
.icon-btn {
  width: var(--tap); height: var(--tap); flex: none; padding: 0;
  display: inline-grid; place-items: center; cursor: pointer;
  font: inherit; font-size: 18px; line-height: 1; text-decoration: none;
  background: var(--surface-2); color: var(--ink);
  border: 0; border-radius: 50%;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out),
    transform var(--dur-1) var(--ease-out);
}
.icon-btn:hover { background: color-mix(in srgb, var(--ink) 9%, var(--surface-2)); }
.icon-btn:active { transform: scale(.95); }
.icon-btn svg { width: 18px; height: 18px; }
.icon-btn:disabled { opacity: .45; pointer-events: none; }
.icon-btn--sm { position: relative; width: 30px; height: 30px; color: var(--ink-2); }
.icon-btn--sm::after { content: ''; position: absolute; inset: -7px; border-radius: 50%; }   /* 44px tap area */
.icon-btn--sm svg { width: 12px; height: 12px; }
/* --bare: a glyph at the nav icons' size (22px), no fill; the tap area
   stays and hover is the tabs' soft rounded wash. Help, theme, sign out. */
.icon-btn--bare { background: none; color: var(--ink-2); border-radius: var(--r-field); }
.icon-btn--bare:hover, .icon-btn--bare[aria-current="page"] { background: var(--surface-2); color: var(--ink); }
.icon-btn--bare svg { width: 22px; height: 22px; }

/* ── segmented control ── the pill nav's look: a Sand track, 4px inset,
   pill. Items 40 high, Figtree 800 14, Ink; the selected one is Ink with
   white text. The track hugs its labels; --fill stretches the items (phone,
   sheets). Items are buttons (aria-pressed / aria-selected), links
   (aria-current) or labels after a hidden radio (input:checked + .seg-item). */
.seg {
  display: inline-flex; gap: 2px; padding: 4px; min-width: 0; max-width: 100%;
  align-self: flex-start; background: var(--surface-2); border-radius: var(--r-pill);
}
.seg--fill { display: flex; align-self: stretch; }
.seg input { position: absolute; opacity: 0; width: 0; height: 0; }
.seg-item {
  flex: 0 0 auto; min-width: 0; min-height: 40px; padding: 0 18px;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  font-family: var(--font-body); font-size: 14px; font-weight: 800; line-height: 1.2;
  white-space: nowrap; text-align: center; text-decoration: none; cursor: pointer;
  color: var(--ink); background: none; border: 0; border-radius: var(--r-pill);
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
  transition: color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.seg--fill .seg-item { flex: 1 1 0; padding-inline: 8px; }
.seg-item:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
.seg-item:active { transform: scale(.95); }
.seg-item svg { width: 16px; height: 16px; flex: none; }
.seg-item[aria-pressed="true"], .seg-item[aria-selected="true"], .seg-item[aria-current]:not([aria-current="false"]),
.seg-item.is-on, .seg input:checked + .seg-item {
  background: var(--ink); color: var(--white);
}
.seg input:focus-visible + .seg-item { outline: 2px solid var(--ink); outline-offset: 2px; }
/* Tabs hug their labels on a wide view and fill the width on a phone. */
@media (max-width: 767.98px) {
  .seg { display: flex; align-self: stretch; width: 100%; }
  .seg-item { flex: 1 1 0; padding-inline: 8px; }
}
/* Short labels for a phone (ui.seg({ short })): Cork, Club, Board. */
.seg-short { display: none; }
@media (max-width: 767.98px) {
  .seg-item:has(.seg-short) .seg-long { display: none; }
  .seg-item .seg-short { display: inline; }
}

/* ── chip ── tappable only (filter, pick). 40 high, --r-field (14),
   Figtree 800 14 on the screen's tint (--stage-tint, Sand on Cream), no
   border. --on (or a pressed state) flips to Ink with the stage colour as
   its text (--stage-on, Lime on Cream).
   --token is a filter Volley set: lime tint, 600, optional bolt; the same
   element in the chat, the Courts list and the phone sheet. A label that
   can't be tapped is a .tag. */
.chip {
  position: relative; min-height: 40px; padding: 0 16px; min-width: 0; max-width: 100%;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  font-family: var(--font-body); font-size: 14px; font-weight: 800; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-decoration: none; cursor: pointer;
  color: var(--ink); background: var(--stage-tint);
  border: 0; border-radius: var(--r-field);
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
  transition: background-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out),
    transform var(--dur-1) var(--ease-out);
}
.chip::after { content: ""; position: absolute; inset: -5px 0; }
.chip:hover { background: color-mix(in srgb, var(--ink) 7%, var(--stage-tint)); }
.chip:active { transform: scale(.95); }
.chip svg { width: 14px; height: 14px; flex: none; }
.chip--on, .chip[aria-pressed="true"], .chip[aria-checked="true"], .chip[aria-selected="true"], .chip[aria-expanded="true"], .chip.is-on,
input:checked + .chip {
  background: var(--ink); color: var(--stage-on);
}
.chip--token, .chip.chip--token.is-on { background: var(--lime-tint); color: var(--ink); padding-inline: 12px; min-height: 34px; font-size: 13px; }
.chip--token:hover { background: color-mix(in srgb, var(--lime) 40%, var(--lime-tint)); }
.chip--token svg { width: 11px; height: 11px; }
.chip--token svg path { fill: currentColor; }
.chip:disabled { opacity: .45; pointer-events: none; }
/* A chip can be the label of a hidden checkbox before it (forms). */
input:focus-visible + .chip { outline: 2px solid var(--ink); outline-offset: 2px; }
/* --float: over the map, White (no shadow: the map is the colour block). */
.chip--float:not(.is-on):not([aria-expanded="true"]):not(.chip--token) { background: var(--surface); }

/* ── tag ── a status label, never tappable (no click handler, ever).
   22 high, pill, 12/600. Bare is neutral (surface-2): You, Owner, Guest. */
.tag {
  display: inline-flex; align-items: center; gap: 5px; vertical-align: middle; flex: none;
  height: 22px; padding: 0 9px;
  min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12px; font-weight: 700; font-style: normal; line-height: 1; letter-spacing: 0; text-transform: none;
  border: 0; border-radius: var(--r-pill);
  color: var(--ink); background: var(--surface-2);
}
.tag--neutral { color: var(--ink); background: var(--surface-2); }
.tag--lime { color: var(--on-lime); background: var(--lime); }
.tag--tint { color: var(--on-lime-tint); background: var(--lime-tint); height: 24px; padding-inline: 10px; font-size: 12.5px; }
.tag--loss { color: var(--loss); background: var(--loss-tint); }
/* Live: lime with a 6px ink dot that pulses. */
.tag--live { color: var(--on-lime); background: var(--lime); }
.tag--live::before {
  content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%; background: currentColor;
  animation: tag-pulse 1.6s ease-out infinite;
}
@keyframes tag-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--on-lime) 45%, transparent); }
  100% { box-shadow: 0 0 0 6px transparent; }
}
/* A one-letter result dot (W / L / D in a form row). */
.tag--dot { width: 22px; padding: 0; justify-content: center; }

/* ── field ── search, caption and chat inputs. 44 high, --r-field,
   --surface-2 fill, no border, 15px; a leading 17px icon. --float is white
   with the float shadow (the map search on a phone); --chat is a pill with
   a 34px send button inside. Wraps an <input> (.field > svg + input) or is
   the <input> itself. */
.field {
  min-height: 44px; min-width: 0; width: 100%;
  display: flex; align-items: center; gap: 10px; padding: 0 14px;
  font: inherit; font-size: var(--t-body); color: var(--ink);
  background: var(--surface-2); border: 0; border-radius: var(--r-field);
}
.field > svg { width: 17px; height: 17px; flex: none; color: var(--ink-3); }
.field > input, .field > textarea {
  flex: 1 1 auto; min-width: 0; min-height: 42px; padding: 0; margin: 0; border-radius: 0;
  font: inherit; color: inherit; background: none; border: 0; outline: 0; box-shadow: none;
}
.field::placeholder, .field > input::placeholder, .field > textarea::placeholder { color: var(--ink-3); }
.field:focus-within { background: var(--surface); box-shadow: var(--ring-field); }
.field > :is(input, textarea, select), .field > :is(input, textarea, select):is(:hover, :focus, :focus-visible) { background-color: transparent; box-shadow: none; outline: 0; }
.field--float { min-height: 48px; border-radius: var(--r-pill); background: var(--surface); padding: 0 6px 0 16px; }
.field--chat { min-height: 44px; border-radius: var(--r-tile); padding: 5px 5px 5px 16px; }
.field-send {
  flex: none; width: 34px; height: 34px; padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  display: grid; place-items: center; background: var(--ink); color: var(--lime);
  transition: background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.field-send:active { transform: scale(.96); }
.field-send:disabled { background: var(--send-off); color: var(--ink-4); cursor: default; }
.field-send svg { width: 15px; height: 15px; }

/* ── card ── the one container: --surface (White), --r-card (30), no
   border, no shadow: White on Cream is the separation. Children are spaced
   by the gap. --dark is Ink (next game, game of the week); --quiet is a
   --surface-3 (Cream) block (Dink's nudge, hint cards, "you're up next");
   --stage is a crew colour block (set --stage on it), Ink text. */
.card {
  min-width: 0; padding: 22px;
  display: flex; flex-direction: column; gap: 16px;
  background: var(--surface); border: 0; border-radius: var(--r-card);
}
@media (max-width: 767.98px) { .card { padding: 20px; gap: 14px; } }
.card--dark { background: var(--ink); color: var(--cream); }
.card--dark :is(.card-meta, .card-head-meta, .card-kicker) { color: var(--inv-ink-2); }
.card--dark .card-sub { color: var(--inv-ink-2); }
.card--quiet { background: var(--surface-3); }
.card--stage { background: var(--stage); color: var(--ink); --btn-2: var(--white); }
.card--stage :is(.card-meta, .card-head-meta, .card-kicker, .card-sub) { color: var(--ink); }
.card--lg { border-radius: var(--r-lg); }
/* The card's gap is the only space between its parts: nothing inside a
   card adds block margins (important, so a feature rule can't win). */
.card > * { margin-block: 0 !important; }
/* A card's head: title left, meta or a link right, on one baseline. */
.card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 4px 12px; min-width: 0; flex-wrap: wrap; }
.card-head > :is(h2, h3, .card-title) { margin: 0; min-width: 0; }
.card-title { font-family: var(--font-display); font-size: var(--t-card); font-weight: 800; letter-spacing: -.02em; line-height: 1.15; color: inherit; text-wrap: balance; }
a.stat-strip { color: inherit; text-decoration: none; }
.card-head-meta, .card-head > a { margin: 0 0 0 auto; font-size: 13px; font-weight: 600; color: var(--ink-3); text-decoration: none; }
.card-head > a { color: var(--ink-2); }
.card-head > a:hover { color: var(--ink); }
.card-kicker { margin: 0; font-family: var(--font-mono); font-size: var(--t-label-mono); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3); }
.card-sub { margin: 0; font-size: var(--t-body); color: var(--ink-2); text-wrap: pretty; }
/* The one pairing: a card title's own subline sits 4px under it. */
.card > :is(.card-title, .sec-head, .card-head) + .card-sub { margin-top: calc(4px - var(--sp-4)) !important; }
/* The feature title (a game, a court): a heading, Bricolage 800. */
.feature-title { margin: 0; font-family: var(--font-display); font-size: var(--t-feature); font-weight: 800; letter-spacing: var(--track-heading); line-height: 1; }
/* A score: Bricolage 800, tabular. The big result score is .score--hero. */
.score { font-family: var(--font-display); font-size: 34px; font-weight: 800; letter-spacing: -.04em; line-height: 1; font-variant-numeric: tabular-nums; }
.score--hero { font-size: var(--t-score); letter-spacing: var(--track-score); line-height: .8; }
.score-dash { color: var(--ink-4); padding: 0 10px; }
@media (max-width: 767.98px) { .score { font-size: 28px; } .score-dash { padding: 0 6px; } }

/* ── tile ── a quiet cell holding one item (gear, trophy, quest art):
   --surface-3 (Cream), --r-tile (22), no border, no shadow. --lime-tint when on. A tile
   never contains a card. Interactive tiles (button/a) press. */
.tile {
  min-width: 0; padding: var(--sp-3);
  background: var(--surface-3); border: 0; border-radius: var(--r-tile);
  color: var(--ink); font: inherit; text-decoration: none;
}
:is(button, a).tile { cursor: pointer; transition: transform var(--dur-1) var(--ease-out), box-shadow var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out); }
:is(button, a).tile:active { transform: scale(.95); }
.tile--on, .tile.is-on, .tile[aria-pressed="true"], .tile[aria-checked="true"] { background: var(--lime-tint); }
.tile.is-locked { background: var(--sand); color: var(--ink-3); }

/* ── list row ── the one row for every table or list: leaderboard, This
   week, high scores, partners, the courts list, members. A grid of
   [rank 14] [avatar 28–30 | thumb 52–56] [name + meta] [value], 46 high,
   gap 12, no dividers. Slots are optional. --selected is a White block
   (the selected court); --me lifts the name to 700. */
.list { display: flex; flex-direction: column; min-width: 0; }
.list-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 12px;
  min-height: 46px; min-width: 0; padding: 0; color: inherit; text-decoration: none;
  font-size: 14.5px; font-weight: 500; border: 0; background: none;
}
.list-row:has(.list-meta) { min-height: 56px; padding-block: 4px; }
.list-row:has(> .list-rank) { grid-template-columns: 14px auto minmax(0, 1fr) auto; }
.list-row:not(:has(> .list-lead)) { grid-template-columns: minmax(0, 1fr) auto; }
.list-row:has(> .list-rank):not(:has(> .list-lead)) { grid-template-columns: 14px minmax(0, 1fr) auto; }
.list-rank { font-size: 13px; font-weight: 500; color: var(--ink-3); font-variant-numeric: tabular-nums; text-align: center; }
.list-lead { display: inline-flex; line-height: 0; flex: none; }
.list-lead .avatar { --av: 30px; }
.list-thumb { display: block; width: 56px; height: 56px; border-radius: var(--r-field); object-fit: cover; overflow: hidden;
  background: var(--sand); }
.list-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.list-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: flex; align-items: center; gap: 6px; }
.list-title > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.list-meta { font-size: 13px; font-weight: 500; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.list-end { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; text-align: right; }
.list-value { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.list-value small { font-weight: 500; color: var(--ink-3); }
.list-value-meta { font-size: 12.5px; font-weight: 500; color: var(--lime-ink); white-space: nowrap; }
.list-value-meta.is-warn { color: var(--warn); }
.list-value-meta.is-quiet { color: var(--ink-3); }
.list-row .bar { margin-top: 2px; }
.list-row--thumb { min-height: 76px; gap: 14px; padding: 10px; margin-inline: -10px; border-radius: var(--r-tile); }
.list-row--me .list-title, .list-row--thumb .list-title { font-weight: 700; }
.list-row--thumb .list-title { font-size: var(--t-body); }
.list-row--selected { background: var(--surface); }
/* --wrap: title and meta wrap instead of clipping (rows an organizer must read whole). */
.list-row--wrap { padding-block: 8px; }
.list-row--wrap :is(.list-title, .list-title > span, .list-meta) { white-space: normal; overflow: visible; overflow-wrap: anywhere; }
/* --stack: under 480px the value slot drops under the name, left-aligned
   (a row whose two buttons won't fit beside it). */
@media (max-width: 479.98px) {
  .list-row.list-row--stack { grid-template-columns: minmax(0, 1fr); gap: 8px; padding-block: 8px; }
  .list-row--stack .list-end { align-items: flex-start; }
}
:is(a, button).list-row { cursor: pointer; font: inherit; font-size: 14.5px; text-align: left; transition: background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out); }
:is(a, button).list-row:active { transform: scale(.98); }
:is(a, button).list-row--thumb:hover:not(.list-row--selected) { background: var(--surface-3); }
@media (max-width: 767.98px) { .list-thumb { width: 52px; height: 52px; } .list-row--thumb { min-height: 72px; gap: 12px; } }

/* ── stat ── label (meta), value (28/700 tabular), optional delta (12.5/500,
   --lime-ink up, --ink-3 neutral). --tile sits on a --surface-3 panel
   (Insights). .stat-strip is the white bar of four on the Me stage. */
.stat { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.stat-label { font-size: 12.5px; font-weight: 600; color: var(--ink-3); }
.stat-value { font-family: var(--font-display); font-size: 28px; font-weight: 800; letter-spacing: -.035em; line-height: 1.1; font-variant-numeric: tabular-nums; color: var(--ink); }
.stat-delta { font-size: 12.5px; font-weight: 500; color: var(--ink-3); }
.stat-delta.is-up { color: var(--lime-ink); }
.stat--tile { padding: 16px 18px; background: var(--surface-3); border-radius: var(--r-tile); }
.stat-strip {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px;
  padding: 12px 18px; background: var(--surface); border-radius: var(--r-tile);
}
.stat-strip .stat { flex-direction: column-reverse; gap: 0; }
.stat-strip .stat-value { font-size: 20px; letter-spacing: -.02em; }
.stat-strip .stat-label { font-size: 12px; }
/* --bare: on a phone the strip sits straight on the stage (one number, no bar). */
@media (max-width: 767.98px) { .stat-strip--bare { padding: 0; background: none; } }

/* ── bar ── an 8px progress track in Sand with an Ink fill, Lime when
   complete. .bar-chart is the column chart: ink bars, radius 8, the
   current period lime, micro --ink-3 labels. */
.bar { display: block; height: 8px; border-radius: var(--r-pill); background: var(--surface-2); overflow: hidden; }
.bar > i { display: block; height: 100%; width: var(--p, 0%); border-radius: inherit; background: var(--ink); transition: width var(--dur-3) var(--ease-out); }
.bar.is-done > i { background: var(--lime); }
.bar-chart { display: flex; align-items: flex-end; gap: 10px; height: 180px; padding-top: 8px; min-width: 0; }
.bar-col { flex: 1 1 0; min-width: 0; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 6px; }
.bar-col > i { display: block; width: 100%; height: var(--h, 0%); min-height: 4px; border-radius: var(--r-field); background: var(--ink); }
.bar-col.is-now > i { background: var(--lime); }
.bar-col > span, .bar-axis { font-size: 11.5px; font-weight: 500; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.bar-axis { display: flex; justify-content: space-between; color: var(--ink-4); }

/* ── avatar stack ── 32–34 circles, -8 overlap, a 2.5px surface ring (no
   border), then trailing meta: "+4 going". */
.avatar-stack { display: inline-flex; align-items: center; }
.avatar-stack .avatar { --av: 34px; border: 0; box-shadow: 0 0 0 2.5px var(--surface); }
.avatar-stack .avatar + .avatar { margin-left: -8px; }
.avatar-stack .avatar-more { font-size: 11px; font-weight: 600; color: var(--ink-2); background: var(--surface-2); }
.avatar-stack-meta { margin-left: 8px; font-size: 13px; font-weight: 500; color: var(--ink-3); }
@media (max-width: 767.98px) { .avatar-stack .avatar { --av: 32px; } }

/* ── page head ── back link, then [mark] title + one meta line, then at
   most one .btn and one .icon-btn on the right. Ownership is meta text,
   never a tag. ui.pageHead() builds every one. */
.page-head { display: flex; flex-direction: column; gap: 14px; min-width: 0; margin: 0 0 18px; }
.page-back {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 4px; min-height: 32px;
  margin: -6px 0 -8px; font-size: 13.5px; font-weight: 500; color: var(--ink-3); text-decoration: none;
}
.page-back svg { width: 14px; height: 14px; }
.page-back:hover { color: var(--ink); }
.page-head-row { display: flex; align-items: center; gap: 14px; min-width: 0; }
.page-mark { flex: none; display: inline-flex; line-height: 0; }
.page-mark .gtile { --gt: 52px; border-radius: var(--r-field); font-size: 17px; }
.page-head-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.page-title { margin: 0; font-family: var(--font-display); font-size: var(--t-page); font-weight: 800; letter-spacing: var(--track-title); line-height: .95; overflow-wrap: anywhere; text-wrap: balance; }
.page-title-row { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
.page-meta { margin: 0; font-size: 13.5px; font-weight: 600; color: var(--ink-3); overflow-wrap: anywhere; }
.page-head-actions { flex: none; display: flex; align-items: center; gap: 6px; margin-left: auto; }
.page-head > .seg { margin: 0; }
@media (max-width: 767.98px) {
  .page-head-row { flex-wrap: wrap; }
  .page-head-actions:has(> .seg) { flex: 1 0 100%; margin-left: 0; }
  .page-mark .gtile { --gt: 44px; border-radius: 14px; font-size: 15px; }
  .page-meta { font-size: 13px; }
}

/* ── crew line ── the crew inside content: the one crew size (40px,
   PAC_UI.CREW_PX) and its words in a soft bubble, no outline. Attached to an
   action it leads that card instead (.crew-line--lead: same 40px, the words
   as the card's title, no bubble). Volley's chat keeps its own size. */
.crew-line { display: flex; align-items: center; gap: 10px; min-width: 0; font-size: 14px; font-weight: 500; line-height: 1.4; color: var(--ink); }
.crew-line > :first-child { flex: none; line-height: 0; }
.crew-line b, .crew-line strong { font-weight: 600; }
.crew-line > p { position: relative; margin: 0; min-width: 0; max-width: var(--w-bubble); padding: 9px 14px; border-radius: var(--r-tile); background: var(--surface-2); }
.crew-line > p::before { content: ''; position: absolute; left: -4px; top: 50%; width: 10px; height: 10px; border-radius: 2px; background: inherit; transform: translateY(-50%) rotate(45deg); }
.crew-line--lead { gap: 14px; font-size: var(--t-body); color: var(--ink); }
.crew-line--lead .crew-line-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.crew-line--lead .crew-line-text { font-size: var(--t-card); font-weight: 600; }
.crew-line--lead .crew-line-meta { font-size: 13px; color: var(--ink-3); }
.crew-line--lead > .btn { flex: none; }
@media (max-width: 767.98px) {
  .crew-line { font-size: 13px; }
  .crew-line--lead { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px 14px; }
  .crew-line--lead > .btn { grid-column: 1 / -1; width: 100%; min-height: 48px; }
}

/* ── locker door ── one member's door (Clubhouse and the Me locker). White
   face, --r-card, vents and a handle (no frame line). 80 avatar (62 on a phone), name + tag,
   record meta, a row of 30px stickers. --invite is the + door. */
.door {
  position: relative; min-width: 0; height: 244px;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 18px 14px 16px; text-align: center; color: inherit; text-decoration: none;
  background: var(--surface); border: 0; border-radius: var(--r-card);
  font: inherit;
}
:is(a, button).door { cursor: pointer; transition: transform var(--dur-1) var(--ease-out); }
:is(a, button).door:active { transform: scale(.95); }
.door-vents { display: grid; grid-template-columns: repeat(2, 12px); gap: 3px 4px; }
.door-vents i { display: block; height: 3px; border-radius: var(--r-pill); background: var(--door-metal); }
.door-handle { position: absolute; right: 14px; top: 50%; width: 6px; height: 20px; margin-top: -10px; border-radius: var(--r-pill); background: var(--door-metal); }
.door-face { margin-top: 6px; display: grid; place-items: center; line-height: 0; }
.door-face .avatar { --av: 80px; border: 0; }
.door-name { display: flex; align-items: center; justify-content: center; gap: 6px; padding-top: 4px; min-width: 0; max-width: 100%; font-size: var(--t-body); font-weight: 600; }
.door-name > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.door-meta { font-size: 12.5px; font-weight: 500; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.door-stickers { margin-top: auto; display: flex; gap: 2px; justify-content: center; min-height: 30px; line-height: 0; }
.door-stickers > * { display: inline-block; width: 30px; height: 30px; transform: rotate(var(--tilt, 0deg)); }
.door-stickers svg { width: 100%; height: 100%; }
.door--invite { background: var(--surface-2); justify-content: center; gap: 10px; color: var(--ink-2); }
.door--invite .door-plus { width: 48px; height: 48px; border-radius: 50%; background: var(--surface); color: var(--ink); display: grid; place-items: center; }
.door--invite .door-plus svg { width: 18px; height: 18px; }
.door--invite .door-name { padding: 0; font-size: var(--t-label); color: var(--ink); }
@media (max-width: 767.98px) {
  .door { height: 196px; gap: 4px; padding: 14px 12px 12px; }
  .door-vents { grid-template-columns: repeat(2, 10px); gap: 3px; }
  .door-handle { right: 11px; width: 5px; height: 18px; margin-top: -9px; }
  .door-face { margin-top: 4px; }
  .door-face .avatar { --av: 62px; }
  .door-name { font-size: 14.5px; }
  .door-meta { font-size: 12px; }
  .door-stickers { min-height: 26px; }
  .door-stickers > * { width: 26px; height: 26px; }
}

/* ── map pin ── a price on the map: a White pill, Figtree 800; --on (the
   picked court) is Ink. */
.pin {
  display: inline-flex; align-items: center; height: 32px; padding: 0 13px; white-space: nowrap;
  font-size: 13px; font-weight: 800; color: var(--ink); background: var(--surface);
  border: 0; border-radius: var(--r-pill);
}
.pin--on { background: var(--ink); color: var(--white); }
@media (max-width: 767.98px) { .pin { height: 30px; padding: 0 12px; font-size: 12.5px; } }

/* ── section head ── kept for bare sections outside a card: a heading,
   meta 13 on the right, optional action. Inside a card it is the card head. */
.sec-head { display: flex; align-items: baseline; gap: var(--sp-1) var(--sp-4); flex-wrap: wrap; min-width: 0; margin: 0; }
.sec-head > :is(h2, h3, .sec-title) { margin: 0; min-width: 0; font-family: var(--font-display); font-size: var(--t-section); font-weight: 800; letter-spacing: var(--track-heading); line-height: 1; color: var(--ink); text-wrap: balance; }
.card .sec-head > :is(h2, h3, .sec-title) { font-size: var(--t-card); letter-spacing: -.02em; }
.sec-meta { margin: 0 0 0 auto; font-size: 13px; font-weight: 600; color: var(--ink-3); }
.sec-head > :is(a, button):last-child { margin-left: auto; }
.sec-meta + :is(a, button) { margin-left: 0; }

/* ── overlay ── one <dialog> for every sheet and dialog (showModal()).
   <768 a bottom sheet with a grip; ≥768 a centred dialog. Sizes:
   --s 360, --m 460, --l 560. Shared parts: .overlay-grip, .overlay-head
   (.overlay-title, .overlay-sub, .icon-btn.overlay-close), .overlay-body,
   .overlay-foot. --pop is a [popover] anchored to its trigger at ≥1280
   only; below that it is the same sheet / dialog. No border, no shadow
   (the scrim is the separation); a 38×5 grip; a 30px soft close. */
.overlay {
  --ov-w: var(--w-sheet);
  border: 0; padding: 8px var(--sp-4) calc(var(--sp-5) + var(--safe-b));
  display: flex; flex-direction: column; gap: 14px;
  color: var(--ink); background: var(--surface);
  width: 100%; max-width: 100%; max-height: 92svh;
  margin: auto 0 0; border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  overflow: auto; overscroll-behavior: contain;
}
dialog.overlay:not([open]), .overlay[popover]:not(:popover-open) { display: none; }
.overlay--s { --ov-w: var(--w-dialog); }
.overlay--m { --ov-w: var(--w-sheet); }
.overlay--l { --ov-w: var(--w-card); }
.overlay[open] { animation: sheet-up var(--dur-3) var(--ease-out); }
.overlay::backdrop { background: var(--scrim); }
.overlay > * { margin-block: 0 !important; }
.overlay-grip { flex: none; width: 38px; height: 5px; border-radius: var(--r-pill); background: var(--grip); margin: 0 auto -2px; }
.overlay-head { display: flex; align-items: flex-start; gap: var(--sp-3); min-width: 0; }
.overlay-head > :first-child { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.overlay-title, .overlay h2 { margin: 0; font-family: var(--font-display); font-size: 24px; font-weight: 800; letter-spacing: var(--track-heading); line-height: 1; text-wrap: balance; }
.overlay-sub { margin: 0; font-size: 13.5px; font-weight: 600; color: var(--ink-3); text-wrap: pretty; }
.overlay-close { margin: 0 0 0 auto; width: 30px; height: 30px; color: var(--ink-2); }
.overlay-close svg { width: 12px; height: 12px; }
.overlay-body { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.overlay-body > * { margin-block: 0 !important; }
.overlay-foot { display: flex; gap: 6px; }
.overlay-foot > * { flex: 1; }
.overlay h2[tabindex="-1"]:focus { outline: none; }
@keyframes sheet-up { from { transform: translateY(40px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (min-width: 768px) {
  .overlay {
    width: calc(100% - 2 * var(--gutter)); max-width: var(--ov-w); margin: auto;
    padding: 24px; border-radius: var(--r-lg);
  }
  .overlay-grip { display: none; }
}
/* The popover form. Without the Popover API it is a plain box under its
   trigger (the field row is the positioned parent). */
.overlay--pop:not([popover]) {
  position: absolute; z-index: 25; top: calc(100% + 6px); left: 0; margin: 0;
  width: min(var(--w-pop), 78vw); padding: 20px; animation: none;
  border: 0; border-radius: var(--r-card);
}
.overlay--pop.tp-inline { position: static; width: auto; margin: 8px 0 4px; box-shadow: none; }
/* In the top layer below 1280, it is a sheet or dialog. */
.overlay--pop[popover] { position: fixed; inset: auto 0 0 0; }
.overlay--pop[popover]::backdrop { background: var(--scrim); }
@media (min-width: 768px) {
  .overlay--pop[popover] { inset: 0; }
}
@media (min-width: 1280px) {
  .overlay--pop[popover] {
    inset: auto; margin: 6px 0 0; width: auto; min-width: var(--w-pop); max-width: var(--w-pop-lg);
    padding: 20px; border: 0; border-radius: var(--r-card);
    position-area: bottom span-right; position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
    animation: none;
  }
  .overlay--pop[popover]::backdrop { background: none; }
}
@media (prefers-reduced-motion: reduce) { .overlay[open] { animation: none; } .tag--live::before { animation: none; } }

/* ── shout ── the one big line a screen gets (a takeover's headline):
   Bricolage 800 caps, 52-124. ── label ── JetBrains Mono 600 caps,
   10.5-12, tracked +8%: kickers and stamps ("LIVE · SMASH YARD"). */
.shout {
  margin: 0; font-family: var(--font-display); font-size: var(--t-shout); font-weight: 800;
  line-height: .84; letter-spacing: var(--track-shout); text-transform: uppercase; text-wrap: balance; color: var(--ink);
}
.label {
  font-family: var(--font-mono); font-size: var(--t-label-mono); font-weight: 600; line-height: 1.3;
  letter-spacing: var(--track-label); text-transform: uppercase; color: inherit;
}
.title { margin: 0; font-family: var(--font-display); font-size: var(--t-title); font-weight: 800; line-height: .95; letter-spacing: var(--track-title); text-wrap: balance; }
.lead { margin: 0; font-size: var(--t-lead); font-weight: 700; line-height: 1.45; text-wrap: pretty; }

/* ── say ── a crew speech bubble: the speaker's tint (--say, set from
   crew-data), Ink text, Figtree 700, a tail toward the speaker, tilted
   4-8°. --shout is the big moment: Ink, white Bricolage 800 ("BOOM. Next.").
   The name label (.label) sits above it. --tail-r puts the tail right. */
.say {
  position: relative; display: inline-block; max-width: var(--w-bubble);
  padding: 10px 16px; border-radius: var(--r-tile); background: var(--say, var(--sand)); color: var(--ink);
  font-size: 15px; font-weight: 700; line-height: 1.35; text-wrap: pretty;
  transform: rotate(var(--tilt, -4deg));
}
.say::after {
  content: ""; position: absolute; left: 24px; bottom: -6px; width: 14px; height: 14px;
  border-radius: 3px; background: inherit; transform: rotate(45deg);
}
.say--tail-r::after { left: auto; right: 24px; }
.say--shout { background: var(--ink); color: var(--white); font-family: var(--font-display); font-size: 22px; font-weight: 800; letter-spacing: -.02em; }

/* ── sticker ── a reward slapped on crooked: Cream die-cut card at 4-8°,
   a solid offset in a darker crew tone (--tone), never a blur. */
.sticker {
  display: inline-flex; align-items: center; gap: 10px; padding: 10px 16px 10px 10px;
  border-radius: var(--r-tile); background: var(--cream); color: var(--ink);
  font-family: var(--font-display); font-size: 20px; font-weight: 800; letter-spacing: -.02em;
  transform: rotate(var(--tilt, -7deg)); box-shadow: 0 8px 0 -3px var(--tone, var(--erne));
}

/* ── tilt ── playful cards and rows lean ±1-3°, alternating. Never on
   forms, inputs, payments or settings (the quiet zones). */
.tilt-l { transform: rotate(-1.5deg); }
.tilt-r { transform: rotate(1.5deg); }
.tilt-alt > :nth-child(odd) { transform: rotate(-1.5deg); }
.tilt-alt > :nth-child(even) { transform: rotate(1.5deg); }

/* ── pill nav ── the desktop nav (≥1024): a soft Ink-tinted track, Figtree
   800 14 items 40 high; the current one is Ink with white text. */
.pill-nav { display: inline-flex; gap: 4px; padding: 5px; border-radius: var(--r-pill); background: color-mix(in srgb, var(--ink) 7%, transparent); }
.pill-nav > a, .pill-nav > button {
  min-height: 40px; padding: 0 18px; display: inline-flex; align-items: center; gap: 6px;
  font: 800 14px/1.2 var(--font-body); color: var(--ink); text-decoration: none;
  background: none; border: 0; border-radius: var(--r-pill); cursor: pointer;
  transition: background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.pill-nav > :is(a, button):active { transform: scale(.95); }
.pill-nav > [aria-current]:not([aria-current="false"]) { background: var(--ink); color: var(--white); }

/* ── states and contexts the components own ── */
.btn--quiet[aria-pressed="true"] { color: var(--ink); background: var(--lime-tint); }
.icon-btn[aria-current="page"] { background: var(--ink); color: var(--white); }
.seg--grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; border-radius: var(--r-field); }
.seg--grid .seg-item { border-radius: var(--r-field); }
/* Inverse surfaces (a called court, the dark help mock, the story viewer):
   quiet buttons and icon buttons read on Ink. */
:is(.court-card.is-called, .kb-inv, .card--dark) .btn--quiet { color: var(--inv-ink-2); }
:is(.court-card.is-called, .kb-inv) .btn--quiet.btn--danger { color: var(--inv-late); }
:is(.court-card.is-called, .kb-inv, .card--dark) .btn--quiet:hover { color: var(--ground); background: color-mix(in srgb, var(--ground) 10%, transparent); }
.story-head .icon-btn { color: var(--white); background: color-mix(in srgb, var(--white) 16%, transparent); }
/* The title leaves room for the close button ui.js adds to every overlay. */
.overlay:has(> .overlay-close) :is(.overlay-title, h2) { padding-right: 34px; }
/* The close button ui.js puts in every overlay: top right, clear of the notch. */
.overlay > .overlay-close { position: absolute; z-index: 2; top: 16px; right: calc(16px + var(--safe-r)); margin: 0; }
.overlay:has(> .overlay-grip) > .overlay-close { top: 18px; }
/* Inside the phone stage sheet the panels are the sheet: no card chrome. */
@media (max-width: 767.98px) { .stage-sheet .card { padding: 0; border-radius: 0; background: none; box-shadow: none; } }

/* ── page templates ──
   Every view declares one with data-tpl. main.wrap fills the space right
   of the nav; the template centres itself in it, so no page ever sits
   left-aligned with an empty right side.
     read       one column, --w-read, centred
     feed       600 | 340, centred; <1280 one column
     stage      340 | stage | 340 (300 at 1280–1439); 768–1279 stage on
                top, panels paired below; <768 stacked
     workspace  420 | rest, the rest full-bleed to the viewport edge
     docs       220 sidebar | one --w-read column (How it works) */
[data-tpl] { width: 100%; margin-inline: auto; min-width: 0; }
[data-tpl="read"] { max-width: var(--w-read); }
[data-tpl="docs"] { max-width: calc(var(--w-read) + 220px + var(--sp-7)); }
[data-tpl="feed"] { max-width: calc(var(--w-feed) + var(--sp-6) + var(--w-aside)); }
[data-tpl="stage"], [data-tpl="workspace"] { max-width: none; }
[data-tpl="feed"] .split > * { grid-area: auto; }
@media (min-width: 1280px) {
  [data-tpl="feed"] .split {
    grid-template-columns: minmax(0, var(--w-feed)) var(--w-aside); gap: var(--sp-6);
    grid-template-areas: none; justify-content: center; align-items: start;
  }
  [data-tpl="feed"] .split-lead, [data-tpl="feed"] .split-more { grid-column: 1; }
  [data-tpl="feed"] .split-side { grid-column: 2; grid-row: 1 / span 2; position: sticky; top: var(--sticky-top); }
}
@media (max-width: 1279.98px) {
  [data-tpl="feed"] { max-width: var(--w-feed); }
  [data-tpl="feed"] .split { grid-template-columns: minmax(0, 1fr); grid-template-areas: none; gap: var(--stack); }
  /* The rail's first card leads the feed; the rest follow it. */
  [data-tpl="feed"] .split-side { display: contents; }
  [data-tpl="feed"] .split-side > * { order: 2; }
  [data-tpl="feed"] .split-side > :first-child { order: 0; }
  [data-tpl="feed"] .split-lead { order: 1; }
  [data-tpl="feed"] .split-more { order: 3; }
}
.stage-grid {
  display: grid; gap: var(--sp-5); align-items: start; min-width: 0;
  grid-template-columns: var(--w-aside) minmax(0, 1fr) var(--w-aside);
  grid-template-areas: "l c r";
}
.stage-grid > * { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-4); }
.stage-l { grid-area: l; }
.stage-c { grid-area: c; }
.stage-r { grid-area: r; }
@media (min-width: 1280px) and (max-width: 1439.98px) {
  .stage-grid { grid-template-columns: 300px minmax(0, 1fr) 300px; }
}
@media (max-width: 1279.98px) {
  .stage-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-areas: "c c" "l r"; }
}
@media (max-width: 767.98px) {
  .stage-grid { grid-template-columns: minmax(0, 1fr); grid-template-areas: "c" "l" "r"; gap: var(--sp-4); }
}
.ws-grid { display: flex; flex-direction: column; min-width: 0; }
@media (min-width: 1280px) {
  .ws-grid {
    display: grid; grid-template-columns: var(--w-ws) minmax(0, 1fr); gap: var(--sp-6); align-items: start;
    margin-right: calc(-1 * var(--gutter));
  }
  .ws-main { position: sticky; top: 0; height: 100svh; min-width: 0; }
}

/* ══ §1 Tokens ══════════════════════════════════════════════
   DinkedIn Crew v2 (docs/design/rebrand/README.md). One light palette:
   Cream ground, White cards, Sand tracks, Ink text. Every page but the
   iPad board pins data-theme="light"; the board alone keeps a dark theme
   (its own switch), so the dark blocks below are the board's.
   The older token names stay as aliases onto the new palette so every
   rule further down lands on it; new code uses the brand names. */
:root {
  /* Brand: the crew (fills, never text), their tints, Sun (stage only). */
  --dink: #d4ec4f;   --dink-tint: #e6f39a;
  --volley: #6ec3ff; --volley-tint: #93d3ff;
  --smash: #ff7a45;  --smash-tint: #ff9c70;
  --lob: #f7a8c4;    --lob-tint: #fbc4d8;
  --erne: #b39cf5;   --erne-tint: #c8b8fa;
  --pop: #5fe0b0;    --pop-tint: #88ecc8;
  --sun: #ffd23f;    --sun-tint: #ffe27a;
  /* A character never stands on their own colour: their takeover stage. */
  --stage-dink: var(--lob); --stage-volley: var(--dink); --stage-smash: var(--sun);
  --stage-lob: var(--pop); --stage-erne: var(--volley); --stage-pop: var(--erne);
  /* Neutrals. Text on any colour is Ink. */
  --ink-0: #1d2233; --cream: #f6f3ea; --sand: #ebe6d8; --white: #ffffff; --error: #d13a52;
  /* Stage: the screen's colour and its tint (unselected chips). A takeover
     sets these on its view; Cream is the default. */
  --stage: var(--cream); --stage-tint: var(--sand); --stage-on: var(--lime);

  --canvas: var(--cream);
  --panel: var(--cream);
  --ground: var(--panel);
  --surface: var(--white);
  --surface-2: var(--sand);   /* tracks, soft fills, inputs, empty and locked */
  --surface-3: var(--cream);  /* tiles and quiet cards on a white card */
  --ink: var(--ink-0);
  --ink-2: #4a4f60;       /* secondary text: 7.3:1 on Cream, 6.5 on Sand */
  --ink-3: #5a5e6d;       /* meta: 5.8:1 on Cream, 5.2 on Sand */
  --ink-4: #8a8d99;       /* inactive icons, axis ticks: never body text */
  /* No lines in the kit: separation is a colour block. These survive as
     fills for the few rules still drawing one (removed section by section). */
  --line: var(--sand);
  --line-2: #d9d3c3;
  --lime: var(--dink); --on-lime: var(--ink-0);
  --lime-tint: var(--dink-tint); --on-lime-tint: var(--ink-0); --lime-ink: #5f6c00;
  --warn: #c2410c; --loss: #b42e46; --loss-tint: #fbe3e7;   /* --loss is Error darkened to AA as text */
  /* No UI shadows (the crew's ground shadow lives in the art). */
  --sh-card: none;
  --sh-float: none;
  --sh-pin: none;
  --sh-seg: none;
  --sh-nav: none;
  --door-frame: var(--sand); --door-metal: var(--sand);
  --grip: #d9d3c3; --send-off: var(--sand);
  --tabbar-bg: var(--white); --tabbar-edge: transparent; --tabbar-pill: var(--sand); --tabbar-pill-lift: #e0dac9;
  --tabbar-sh: none;
  --ring-field: 0 0 0 3px rgba(29, 34, 51, .16);

  /* Primary: Ink fill, white text, one per screen. Lime stays a fill for
     highlights (Ink on it); --accent-edge is gone with the hairlines. */
  --accent: var(--dink);
  --accent-2: #c6de3f;
  --accent-ink: var(--ink-0);
  --accent-wash: var(--dink-tint);
  --accent-edge: transparent;
  --danger: var(--error);
  --danger-wash: #fbe3e7;

  --hi: var(--dink);
  --hi-ink: var(--ink-0);
  --hi-strong: #5f6c00;
  --hi-wash: var(--dink-tint);
  --good: var(--hi-strong);
  --good-wash: var(--hi-wash);
  /* Inverted card (Ink ground). */
  --inv-ink-2: #b9bccb; --inv-line: #343a50; --inv-cue: var(--dink); --inv-late: #ff8095;

  --shadow-sm: none;
  --shadow-md: none;
  --shadow-lg: none;

  /* Type: Bricolage for display, Figtree for text, JetBrains Mono for labels. */
  --font-display: "Bricolage Grotesque", "Figtree", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-body: "Figtree", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --scrim: rgba(29, 34, 51, .5);
  --skel: var(--sand);
  --bubble: var(--sand);
  --skel-hi: var(--cream);

  /* Radii: 999 pills, 40 large card, 30 card, 22 tile, 14 small chip. */
  --r-lg: 40px;
  --r-card: 30px;
  --r-tile: 22px;
  --r-field: 14px;
  --r-inner: var(--r-field);
  --r-input: var(--r-field);
  --r-sm: var(--r-field);
  --r-sheet: 40px;
  --r-shell: 30px;
  --r-pill: 999px;
  --track-tight: -.025em;   /* titles 22px and up */

  /* Brand type roles (README): score, shout, title, heading in Bricolage
     800; lead and body Figtree; label is mono caps. */
  --t-score: clamp(150px, 80px + 13vw, 280px);
  --t-shout: clamp(52px, 34px + 6vw, 124px);
  --t-title: clamp(25px, 21px + 1.1vw, 38px);
  --t-heading: clamp(19px, 16px + .9vw, 30px);
  --t-lead: clamp(17px, 16.5px + .2vw, 19px);
  --t-button: clamp(14.5px, 14px + .25vw, 17px);
  --t-label-mono: 11px;
  --track-shout: -.05em; --track-title: -.035em; --track-heading: -.025em; --track-score: -.065em; --track-label: .08em;

  /* Fluid type: one scale from 375px to 1280px+, no breakpoint jumps. */
  --fs-xs: 12px;
  --fs-sm: 13px;
  --fs-base: clamp(14px, 13.2px + .2vw, 15px);
  --fs-md: clamp(15px, 14px + .3vw, 17px);
  --fs-lg: clamp(18px, 15.5px + .6vw, 22px);
  --fs-xl: clamp(22px, 18px + 1.1vw, 30px);
  --fs-2xl: clamp(28px, 21px + 2vw, 44px);
  --fs-score: clamp(40px, 28px + 4vw, 72px);

  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-7: 32px; --sp-8: 48px;

  /* Tap targets: 44px minimum everywhere; 56px for the one primary
     action on a screen; 72px+ for courtside buttons pressed mid-game. */
  --tap: 44px;
  /* A segment inside a 44px segmented track (3px track padding each side). */
  --seg-h: 38px;
  --tap-lg: 56px;
  --tap-xl: 72px;

  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  /* Layout: phone tab bar, desktop sidebar. */
  --tabbar-h: 64px;
  --sidebar-w: 232px;
  /* Where sticky asides stop: under the masthead below 1280, near the top beside the sidebar. */
  --sticky-top: 84px;
  /* Four templates, three widths (docs/audit/widths.md). main.wrap is the
     only positioner; a view only caps itself at one of these.
     read = sign-in, join, game form; page = every Split and Board view;
     shell = Courts. */
  --w-read: 680px;
  --w-page: 1040px;
  --w-shell: 1280px;
  /* §0 page templates: feed column, side column, workspace list, icon rail. */
  --w-feed: 600px;
  --w-aside: 340px;
  --w-ws: 420px;
  --rail-w: 76px;
  /* Type (Crew v2): page = title, feature and section = heading (Bricolage
     800); card 17/800 Bricolage; label 14/800 Figtree (buttons, chips);
     body 15.5/500; meta 13/600; micro 12/700. */
  --t-page: var(--t-title);
  --t-feature: var(--t-heading);
  --t-section: var(--t-heading);
  --t-card: 17px;
  --t-label: 14px;
  --t-body: 15.5px;
  --t-meta: 13px;
  --t-micro: 12px;
  /* One gutter: 16 on a phone, 24 from 768 (set below). */
  --gutter: 16px;
  /* One rhythm: --stack between blocks in any column; --stack-sec before a
     bare section with its own L2 head. */
  --stack: var(--sp-4);
  --stack-sec: var(--sp-7);
  /* Component widths. */
  --w-dialog: 360px;
  --w-sheet: 460px;
  --w-toast: 420px;
  --w-card: 560px;
  --w-card-sm: 320px;
  --w-pop-sm: 268px;
  --w-pop: 300px;
  --w-pop-lg: 400px;
  --measure: 60ch;
  --measure-narrow: 34ch;
  --w-bubble: 440px;
  --w-pin: 140px;

  --z-sticky: 10; --z-fab: 29; --z-tabbar: 30; --z-sheet: 50; --z-toast: 60;

  /* Motion. --ease-out (spring-like decelerate) for anything entering or
     answering a touch; --ease-in for exits. Three durations only:
     --dur-1 press, --dur-2 state change, --dur-3 page / sheet. */
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  --ease-spring: cubic-bezier(.32, 1.25, .5, 1);   /* a little overshoot: the phone tab bar */
  --ease-in: cubic-bezier(.4, 0, 1, 1);
  --dur-1: .12s;
  --dur-2: .24s;
  --dur-3: .38s;
  --ease: var(--ease-out);
  --dur: var(--dur-2);
}

@media (min-width: 768px) { :root { --gutter: 24px; } }
@media (min-width: 768px) { :root { --sticky-top: var(--sp-6); } }

/* The iPad board only (every other page pins data-theme="light"): its
   auto mode follows the OS, its own switch stamps data-theme="dark". */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --canvas: #0e0e0e;
    --panel: #161616;
    --ground: var(--panel);
    --surface: #1f1f1f;
    --surface-2: #2a2a2a;
    --surface-3: #232323;
    --ink: #f0f0ee;
    --ink-2: #b3b3b3;
    --ink-3: #8c8c8c;
    --ink-4: #6c6c68;
    --line: #2e2e2e;
    --line-2: #404040;
    --lime: #d4ec4f; --on-lime: #1a1a1a;
    --lime-tint: #d4ec4f24; --on-lime-tint: #d4ec4f; --lime-ink: #d4ec4f;
    --volley: #6ec3ff;
    --warn: #fb923c; --loss: #ff5a76; --loss-tint: #ff5a7624;
    --sh-card: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px -12px rgba(0,0,0,.6);
    --sh-float: 0 2px 6px rgba(0,0,0,.4), 0 24px 48px -12px rgba(0,0,0,.7);
    --sh-pin: 0 8px 20px -6px rgba(0,0,0,.6);
    --sh-seg: 0 1px 3px rgba(0,0,0,.4);
    --sh-nav: 0 1px 2px rgba(0,0,0,.5);
    --door-frame: #2e2e2e; --door-metal: #3a3a3a;
    --grip: #444; --send-off: #333;
    --tabbar-bg: rgba(34,34,34,.88); --tabbar-edge: rgba(255,255,255,.07); --tabbar-pill: rgba(255,255,255,.10); --tabbar-pill-lift: rgba(255,255,255,.15);
  --tabbar-sh: 0 1px 2px rgba(0,0,0,.5), 0 12px 32px -8px rgba(0,0,0,.7);
    --ring-field: 0 0 0 3px rgba(240,240,238,.16);

    --accent: #d4ec4f;
    --accent-2: #e2f47a;
    --accent-ink: #1a1a1a;
    --accent-wash: #d4ec4f24;
    --accent-edge: transparent;
    --danger: #ff5a76;
    --danger-wash: #ff5a7624;

    --hi: #d4ec4f; --hi-ink: #1a1a1a; --hi-strong: #d4ec4f; --hi-wash: #d4ec4f24;
    --good: var(--hi-strong);
    --good-wash: var(--hi-wash);
    --inv-ink-2: #555555; --inv-line: #d4d4d0; --inv-cue: #5f6c00; --inv-late: #b02a40;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-md: 0 6px 16px rgba(0, 0, 0, 0.5);
    --shadow-lg: 0 8px 28px rgba(0, 0, 0, 0.6);

    --scrim: rgba(0, 0, 0, .62);
    --skel: #242424;
    --bubble: #f5f5f1;
    --skel-hi: #2e2e2e;
  }
}
:root[data-theme="dark"] {
  --canvas: #0e0e0e; --panel: #161616; --ground: var(--panel);
  --surface: #1f1f1f; --surface-2: #2a2a2a; --surface-3: #232323;
  --ink: #f0f0ee; --ink-2: #b3b3b3; --ink-3: #8c8c8c; --ink-4: #6c6c68;
  --line: #2e2e2e; --line-2: #404040;
  --lime: #d4ec4f; --on-lime: #1a1a1a; --lime-tint: #d4ec4f24; --on-lime-tint: #d4ec4f; --lime-ink: #d4ec4f;
  --volley: #6ec3ff; --warn: #fb923c; --loss: #ff5a76; --loss-tint: #ff5a7624;
  --sh-card: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px -12px rgba(0,0,0,.6);
  --sh-float: 0 2px 6px rgba(0,0,0,.4), 0 24px 48px -12px rgba(0,0,0,.7);
  --sh-pin: 0 8px 20px -6px rgba(0,0,0,.6); --sh-seg: 0 1px 3px rgba(0,0,0,.4); --sh-nav: 0 1px 2px rgba(0,0,0,.5);
  --door-frame: #2e2e2e; --door-metal: #3a3a3a; --grip: #444; --send-off: #333;
  --tabbar-bg: rgba(34,34,34,.88); --tabbar-edge: rgba(255,255,255,.07); --tabbar-pill: rgba(255,255,255,.10); --tabbar-pill-lift: rgba(255,255,255,.15);
  --tabbar-sh: 0 1px 2px rgba(0,0,0,.5), 0 12px 32px -8px rgba(0,0,0,.7);
  --ring-field: 0 0 0 3px rgba(240,240,238,.16);
  --accent: #d4ec4f; --accent-2: #e2f47a; --accent-ink: #1a1a1a; --accent-wash: #d4ec4f24;
  --accent-edge: transparent; --danger: #ff5a76; --danger-wash: #ff5a7624;
  --hi: #d4ec4f; --hi-ink: #1a1a1a; --hi-strong: #d4ec4f; --hi-wash: #d4ec4f24;
  --good: var(--hi-strong); --good-wash: var(--hi-wash);
  --inv-ink-2: #555555; --inv-line: #d4d4d0; --inv-cue: #5f6c00; --inv-late: #b02a40;
  --shadow-sm: 0 1px 2px rgba(0,0,0,.4);
  --shadow-md: 0 6px 16px rgba(0,0,0,.5);
  --shadow-lg: 0 8px 28px rgba(0,0,0,.6);
  --scrim: rgba(0,0,0,.62); --skel: #242424; --skel-hi: #2e2e2e;
  --bubble: #f5f5f1;
}

/* ══ §2 Base ════════════════════════════════════════════════ */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; }
a, button, input, select, label, [role="button"] { touch-action: manipulation; }
/* No iOS long-press callout or text selection on rapid-tap controls. */
button, .tab, .chip, .op-pick, .rally-btn, .team-pick, .seg label, .seg-item {
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
}

/* The hidden attribute must always win. Without this, any element that
   also has an explicit display (e.g. .grid { display: grid }) ignores
   hidden entirely — the UA's [hidden] rule loses to any authored display. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: 500;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

.wrap { width: 100%; max-width: calc(var(--w-shell) + 2 * var(--gutter)); margin-inline: auto; padding-inline: var(--gutter); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: -9999px; top: 8px; z-index: 20;
  background: var(--ink); color: var(--ground); padding: 8px 14px; border-radius: var(--r-pill);
}
.skip:focus { left: 24px; }

:where(a, button):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
/* The ring follows the element's own shape (a pill stays a pill). Only a
   bare link or button with no radius of its own gets soft 4px corners:
   :where() keeps this at zero specificity so no component loses its radius. */
:where(a, button):where(:focus-visible) { border-radius: 4px; }

button { font-family: inherit; }
/* Headings are Bricolage 800 unless a component says otherwise (:where
   keeps this at zero specificity). */
:where(h1, h2, h3) { font-family: var(--font-display); font-weight: 800; letter-spacing: var(--track-heading); line-height: 1.05; }
img, svg, video, canvas { max-width: 100%; }

/* ══ §3 Components (shared) ═════════════════════════════════
   One small set, used on every screen. Markup shapes are in
   docs/design/components.md. Coral (--accent) is only for the primary
   action and "you're up" moments; green (--good) only for winners,
   checked in and live. Everything else is ink, line and surface. */

/* A link chip, e.g. "Reclub link ↗". Same box as .chip. */
.chip-link {
  display: inline-flex; align-items: center; gap: 6px; min-height: var(--tap);
  font-size: 13px; font-weight: 600; color: var(--ink); text-decoration: none;
  padding: 0 4px; position: relative; max-width: 100%;
}
.chip-link > span {
  display: inline-flex; align-items: center; gap: 6px;
  min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-height: var(--tap); padding: 0 14px; border-radius: var(--r-pill); border: 0; background: var(--surface-2); }
.chip-link:hover > span { background: color-mix(in srgb, var(--ink) 7%, var(--surface-2)); }
.chip-link svg { width: 12px; height: 12px; flex: none; }

/* ── avatars ──
   Initials until P2 brings photos. data-tone (0–5) varies the wash so a
   stack of people is not one grey blob; .is-me rings you in lime-strong. */
.avatar {
  --av: 36px;
  width: var(--av); height: var(--av); flex: none; border-radius: 50%;
  display: inline-grid; place-items: center; overflow: hidden;
  font-size: calc(var(--av) * .38); font-weight: 700; letter-spacing: -.01em;
  color: var(--ink); background: var(--surface-2); border: 0;
  user-select: none;
}
.avatar { position: relative; }
.avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.avatar-sm { --av: 28px; }
.avatar-lg { --av: 56px; }
.avatar-xl { --av: 64px; }
.avatar-btn { display: inline-flex; flex: none; padding: 0; border: 0; background: none; border-radius: 50%; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: transform var(--dur-1) var(--ease-out); }
.avatar-btn:active { transform: scale(.96); }
.avatar-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.avatar[data-tone="1"] { background: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
.avatar[data-tone="2"] { background: color-mix(in srgb, var(--pop) 16%, var(--surface)); }
.avatar[data-tone="3"] { background: color-mix(in srgb, var(--volley) 16%, var(--surface)); }
.avatar[data-tone="4"] { background: color-mix(in srgb, var(--sun) 18%, var(--surface)); }
.avatar[data-tone="5"] { background: color-mix(in srgb, var(--erne) 16%, var(--surface)); }
.avatar.is-me { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--hi-strong); }
/* Character avatars (assets/avatars.js): a face crop on the character's own
   colour fills the circle. A photo still sits on top. */
.avatar[data-char] { background: var(--av-c); border-color: transparent; }
.avatar .av-char { position: absolute; inset: 0; display: grid; place-items: center; }
.avatar .av-char svg { width: 100%; height: 100%; }

/* ── avatar picker ("Pick your player") ──
   Radio tiles: 2 columns on phones, 3 from 520px. Selected = 3px ink ring. */
.av-picker { max-width: var(--w-card); display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); margin: var(--sp-2) 0 var(--sp-4); }
@media (min-width: 768px) { .av-picker { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.av-tile {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--sp-3) var(--sp-2) var(--sp-4); border-radius: var(--r-inner);
  background: color-mix(in srgb, var(--av-c) 15%, var(--surface));
  cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent;
  transition: transform var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}
.av-tile:hover { transform: scale(1.03); }
.av-tile[aria-checked="true"] { transform: scale(1.04); box-shadow: 0 0 0 3px var(--av-ring, var(--ink)); }
.av-tile:focus { outline: none; }
.av-tile:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.av-art { display: block; width: 120px; height: 120px; line-height: 0; }
.av-art svg { width: 100%; height: 100%; }
.av-name { margin-top: var(--sp-1); font-size: 15px; font-weight: 700; color: var(--ink); }
.av-name { display: inline-flex; align-items: center; gap: 4px; }
.av-name .ai-mark { flex: none; }
.av-trait { font-size: 12px; color: var(--ink-2); }
/* The power row: a hairline in the tile's own colour, then name + where. */
.av-power { margin-top: var(--sp-2); padding-top: var(--sp-2); font-size: 12px; line-height: 1.3; color: var(--ink-2); text-align: center; }
.av-power b { color: var(--ink); font-weight: 700; }
@media (max-width: 360px) { .av-art { width: 96px; height: 96px; } }
/* #141414 is the dark theme's page colour, so the ring flips to ink there. */
:root[data-theme="dark"] { --av-ring: var(--ink); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --av-ring: var(--ink); } }
@media (prefers-reduced-motion: reduce) { .av-tile, .av-tile:hover, .av-tile[aria-checked="true"] { transition: none; } }

/* ── group identity: a rounded-square tile on one of 8 colours ──
   Same palette as GROUP_COLORS in groups.js: the crew colours, Sun and
   Volley's tint, Ink text on every one (text on colour is always Ink). */
.gtile {
  --gt: 36px;
  width: var(--gt); height: var(--gt); flex: none; border-radius: var(--r-field);
  display: inline-grid; place-items: center; overflow: hidden; position: relative;
  font-size: calc(var(--gt) * .38); font-weight: 700; letter-spacing: -.01em;
  background: var(--gbg); color: var(--gfg); user-select: none;
}
.gtile img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.gtile-sm { --gt: 24px; }
.gtile-lg { --gt: 48px; }
.gtile-xl { --gt: 64px; }
[data-c="0"] { --gbg: var(--dink); --gfg: var(--ink); }
[data-c="1"] { --gbg: var(--volley); --gfg: var(--ink); }
[data-c="2"] { --gbg: var(--smash); --gfg: var(--ink); }
[data-c="3"] { --gbg: var(--pop); --gfg: var(--ink); }
[data-c="4"] { --gbg: var(--erne); --gfg: var(--ink); }
[data-c="5"] { --gbg: var(--lob); --gfg: var(--ink); }
[data-c="6"] { --gbg: var(--volley-tint); --gfg: var(--ink); }
[data-c="7"] { --gbg: var(--sun); --gfg: var(--ink); }
.gkicker { display: flex; align-items: center; gap: 8px; }

/* ── group list ── Home's "Your groups": rows in one card, like the leaderboard. */
.group-list { padding-block: var(--sp-1); }
.group-row {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 60px;
}
.group-row:first-child { border-top: 0; }
.group-row-link {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--sp-3);
  padding-block: var(--sp-3); color: inherit; text-decoration: none;
  transition: transform var(--dur-1) var(--ease-out);
}
.group-row-link:active { transform: scale(.985); }
.group-row-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.group-row-name { font-size: var(--fs-base); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-row-meta { font-size: var(--fs-sm); color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-row-plan { flex: none; margin-right: calc(-1 * var(--sp-3)); }
.gkicker > span:last-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cover-in .gtile { box-shadow: 0 0 0 3px var(--surface); }

/* Group settings → Look */
.look-row { display: flex; align-items: center; gap: var(--sp-4); margin: var(--sp-3) 0; }
.look-acts { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; min-width: 0; }
.look-photo { display: flex; flex-wrap: wrap; gap: 4px; }
.swatches { display: flex; gap: 6px; margin: var(--sp-2) 0; }
.swatch {
  flex: 1 1 0; min-width: 0; max-width: 44px; aspect-ratio: 1; padding: 0; cursor: pointer;
  border-radius: var(--r-field); border: 0; background: var(--gbg);
  transition: transform var(--dur-1) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}
.swatch:active { transform: scale(.96); }
.swatch[aria-checked="true"] { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--ink); }
.swatch:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

.avatar-stack { display: inline-flex; align-items: center; }
.avatar-stack .avatar { --av: 30px; box-shadow: 0 0 0 2px var(--surface); }
.avatar-stack .avatar + .avatar { margin-left: -8px; }
.avatar-stack .avatar-more {
  font-size: 11px; font-weight: 700; color: var(--ink-2); background: var(--surface-2);
}
.avatar-stack + .stack-label { margin-left: 8px; font-size: 13px; color: var(--ink-2); }

/* ── cards ──
   .feed-card is the one card for Home, group and game lists. The whole
   card is the tap target: put an <a class="feed-card-link"> on the title
   and its ::after covers the card; inner buttons sit above it. */
.feed-card {
  position: relative; display: flex; flex-direction: column; gap: 16px;
  background: var(--surface); border: 0; border-radius: var(--r-card); padding: 22px;
  box-shadow: var(--sh-card);
  transition: box-shadow var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
@media (max-width: 767.98px) { .feed-card { padding: 18px; gap: 14px; } }
/* From 768 the sidebar's + New (#newBtn) starts a game or a post; the + and camera FABs are the phone's. */
@media (min-width: 768px) { #fabNew, #fabPost { display: none; } }
.feed-card:has(.feed-card-link):hover { box-shadow: var(--shadow-md); }
.feed-card:has(.feed-card-link:active) { transform: scale(.985); }
.feed-card.is-live { background: var(--hi-wash); }
.feed-card.is-off { background: var(--surface-2); box-shadow: none; }
.feed-card.is-off .feed-card-title { text-decoration: line-through; }
.feed-card-link { color: inherit; text-decoration: none; }
.feed-card-link::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.feed-card-link:focus-visible { outline: none; }
.feed-card:has(.feed-card-link:focus-visible) { outline: 2px solid var(--ink); outline-offset: 2px; }
.feed-card :is(button, .btn, .btn--state, .btn--quiet, .chip-link, a:not(.feed-card-link)) { position: relative; z-index: 1; }
.feed-card-kicker {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; min-width: 0;
  font-size: 13px; font-weight: 500; color: var(--ink-3);
}
.feed-card-kicker > .feed-ago:last-child { margin-left: auto; }
.feed-card-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.feed-card-title { margin: 0; font-size: var(--t-card); font-weight: 600; line-height: 1.3; }
.feed-card-meta { margin: 0; font-size: 13px; font-weight: 500; color: var(--ink-3); }
/* Going faces on the left, the answer buttons on the right. */
.feed-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; min-width: 0; }
.feed-going { display: inline-flex; align-items: center; min-width: 0; }
.feed-rsvp { display: inline-flex; gap: 6px; }
@media (max-width: 767.98px) {
  .feed-card-foot { flex-direction: column; align-items: stretch; }
  .feed-rsvp { display: grid; grid-template-columns: 1fr 2fr; }
}
.feed-card-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.feed-card-row { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }

/* Date tile (Sat / 17 / Oct). */
.date-tile {
  flex: none; width: 52px; text-align: center; line-height: 1.15;
  padding: 6px 0; border-radius: var(--r-inner); background: var(--surface-2);
  font-size: 11px; font-weight: 600; color: var(--ink-2);
}
.date-tile b { display: block; font-size: 20px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }

/* Personal line on a live card / game page ("You're up next · Court 2"):
   a quiet card (surface-3), its action an ink button. */
.you-banner {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 18px; border-radius: var(--r-card);
  font-weight: 600; font-size: var(--t-body);
  background: var(--surface-3); color: var(--ink); border: 0;
}
.you-banner.is-on { background: var(--lime-tint); color: var(--on-lime-tint); }


/* ── court card ── one court on game day (App v2 2f): the court and its
   clock on one line, then each side with its score at 24/700, tabular; the
   trailing side's score fades. A called court is the quiet tint. */
.court-card {
  display: flex; flex-direction: column; gap: 12px;
  background: var(--surface); border: 0; border-radius: var(--r-card); padding: 16px 18px;
  box-shadow: var(--sh-card);
}
.court-card.is-called { background: var(--lime-tint); box-shadow: none; }
.court-card.is-me { box-shadow: var(--sh-card), inset 0 0 0 2px var(--lime-ink); }
.court-card-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.court-card-head h3 { margin: 0; flex: 1; min-width: 0; font-size: 13px; font-weight: 600; color: var(--ink-2); overflow-wrap: anywhere; }
.court-card-head .tag { flex: none; }
.court-timer { font-variant-numeric: tabular-nums; font-size: 12.5px; font-weight: 500; color: var(--ink-3); }
.court-team {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-size: var(--t-body); font-weight: 500; min-width: 0;
}
.court-team .names { min-width: 0; overflow-wrap: anywhere; }
.court-team .score { font-size: 24px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; flex: none; }
.court-team.is-win { font-weight: 600; }
.court-card:has(.court-team.is-win) .court-team:not(.is-win) .score { color: var(--ink-4); }
.court-team.is-me .names { font-weight: 600; }
.court-card-actions { display: flex; gap: 6px; flex-wrap: wrap; }

/* ── queue row ── one waiting group in "Up next". */
.queue-row {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; align-items: center; gap: 10px;
  min-height: var(--tap); padding: 8px 0;
}
.queue-row:first-child { border-top: 0; }
.queue-row .q-no { font-size: 12px; color: var(--ink-3); text-align: center; }
.queue-row .q-names { font-weight: 600; overflow-wrap: anywhere; }
.queue-row .q-eta { font-variant-numeric: tabular-nums; font-size: 12px; color: var(--ink-3); white-space: nowrap; }
.queue-row.is-me { background: var(--hi-wash); border-radius: var(--r-inner); padding-inline: 8px; margin-inline: -8px; }
.queue-row.is-me .q-names { color: var(--ink); font-weight: 700; }

/* ── leaderboard row ── rank, name, W-L, rating, delta. */
.lb-row {
  display: grid; grid-template-columns: 26px minmax(0, 1fr) auto auto; align-items: center; gap: 10px;
  min-height: var(--tap); padding: 6px 0;
  color: inherit; text-decoration: none;
}
a.lb-row { transition: transform var(--dur-1) var(--ease-out); }
a.lb-row:active { transform: scale(.985); }
.lb-row:first-child { border-top: 0; }
.lb-row .lb-rank { font-size: 13px; font-weight: 700; color: var(--ink-3); text-align: center; }
.lb-row .lb-name { display: flex; align-items: center; gap: 10px; min-width: 0; font-weight: 600; }
.lb-row .lb-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-row .lb-wl { font-variant-numeric: tabular-nums; font-size: 12.5px; color: var(--ink-3); }
.lb-row .lb-rating { font-variant-numeric: tabular-nums; font-weight: 700; text-align: right; min-width: 52px; }
.lb-row.is-me { background: var(--hi-wash); border-radius: var(--r-inner); padding-inline: 8px; margin-inline: -8px; }
.lb-row.is-top .lb-rank { color: var(--hi-strong); }

.idle-mascot { position: absolute; z-index: 1; line-height: 0; pointer-events: none; }

/* Two big team buttons (record result). Tap one: that team won. */
.team-pick { display: grid; gap: var(--sp-3); }
.team-pick {
  min-height: var(--tap-xl); width: 100%; cursor: pointer; text-align: left;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font: inherit; font-size: var(--fs-md); font-weight: 700; color: var(--ink); border: 0; border-radius: var(--r-card);
  padding: 14px 18px;
  transition: border-color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out); background: var(--surface-2); }
.team-pick:hover { background: color-mix(in srgb, var(--ink) 7%, var(--surface-2)); }
.team-pick:active { transform: scale(.985); }
.team-pick[aria-pressed="true"] { background: var(--good-wash); color: var(--good); }

/* Number stepper: − 11 + (scores, courts count). No keyboard. */
.stepper { display: inline-flex; align-items: center; gap: 4px; }
.stepper button {
  font: inherit; font-size: 20px; font-weight: 700; line-height: 1;
  color: var(--ink); background: var(--surface-2); border: 0;
}
.stepper output, .stepper .stepper-val {
  min-width: 48px; text-align: center;
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-lg); font-weight: 700;
}

/* ── skeleton ── shape-of-content placeholders instead of "Loading…". */
.skeleton { display: flex; flex-direction: column; gap: var(--sp-3); }
.skel {
  border-radius: var(--r-field); min-height: 12px;
  background: var(--skel); animation: skel .9s ease-in-out infinite alternate;
}
.skel-card { height: 116px; border-radius: var(--r-card); }
.skel-line { height: 12px; width: 70%; }
.skel-line.is-short { width: 40%; }
.skel-title { height: 20px; width: 55%; }
.skel-circle { width: 36px; height: 36px; border-radius: 50%; }
@keyframes skel { to { opacity: .5; } }

/* ── offline bar ── quiet, never alarming: scoring works offline. */
.offline-bar {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 var(--sp-3); padding: 8px 12px; border-radius: var(--r-inner);
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2);
  background: var(--surface-2); border: 0;
}
/* Dink asleep (24px) leads the line: offline is a nap, not an alarm. */
.offline-bar dink-mascot, .offline-bar img { flex: none; margin: -4px 0 -4px -4px; }

/* ── empty states ── one component (emptyState() in groups.js).
   Page tone: mark, title, text, actions. Inline tone: one quiet line. */
.empty-state {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--sp-3); padding: var(--sp-8) var(--sp-4);
}
.empty-state h2 { margin: 0; font-size: var(--fs-lg); font-weight: 700; letter-spacing: -.01em; }
.empty-state p { margin: 0; color: var(--ink-2); max-width: var(--measure-narrow); }
.empty-state .empty-actions { display: flex; flex-direction: column; gap: var(--sp-2); width: 100%; max-width: var(--w-card-sm); margin-top: var(--sp-2); }
/* Dink (72px) heads every page-tone empty state: 'empty' by default,
   'sleep' offline, 'error' when the whole view is an error. The SVG's own
   padding gives the breathing room the old 48px mark needed a wash for. */
.empty-mascot { display: block; flex: none; line-height: 0; margin-bottom: calc(-1 * var(--sp-2)); }
.empty-inline {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px;
  padding: var(--sp-4) 0;
}
.empty-inline p { margin: 0; color: var(--ink-2); font-size: var(--fs-sm); }
.empty-inline p { min-width: 0; }

/* ── footer credit ──
   "Made with ❤️ by" + the vlbonite logo, the last thing in the
   scroll area. The logo is an <img> sized to the text cap height; it is
   inlined from assets/vlbonite-logo.svg (see .vlb-logo below). */
.footer-credit {
  display: flex; justify-content: center;
  padding: var(--sp-6) 0 calc(var(--sp-6) + var(--safe-b));
}
.footer-credit a {
  display: inline-flex; align-items: center; gap: 6px; min-height: var(--tap);
  font-size: 12.5px; font-weight: 500; color: var(--ink-3); text-decoration: none;
}
.footer-credit a:hover { color: var(--ink-2); }
/* The vlbonite mark, inlined from assets/vlbonite-logo.svg so it follows
   the app's own theme toggle (an <img> can't): the black V takes the ink
   colour, so it reads light in dark mode; the orange mark stays brand
   orange in both themes. It stands in for the name, so it sits a touch
   above the 12.5px text cap height. */
.vlb-logo { height: 15px; width: auto; flex: none; display: block; overflow: visible; }
.vlb-logo .vlb-v { fill: var(--ink); }
.vlb-logo .vlb-mark { fill: #ed5931; }

/* Sidebar copy of the credit (desktop only, see the shell region). */
.sidebar-credit { display: none; }
.sidebar-credit a {
  display: inline-flex; align-items: center; flex-wrap: wrap; gap: 2px 6px; min-height: var(--tap);
  font-size: 12px; font-weight: 500; line-height: 1.35; color: var(--ink-3); text-decoration: none;
}
.sidebar-credit a:hover { color: var(--ink-2); }

/* ══ §4 Masthead + nav ══════════════════════════════════════ */
/* ─────────────── masthead ─────────────── */
.masthead {
  background: var(--ground);
  position: sticky; top: 0; z-index: 10;
}
/* padding-block, not min-height alone: this wraps onto two lines on a
   phone, and once it does the min-height stops governing — leaving the
   tab pill sitting directly on the header's bottom border. */
.masthead-in {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 16px; min-height: 68px; flex-wrap: wrap;
  padding-block: 10px;
}
.brand { display: flex; align-items: center; gap: 10px; }
.brand-text { display: flex; flex-direction: column; line-height: 1.2; }
.brand-text strong { font-size: var(--fs-md); font-weight: 700; letter-spacing: -0.01em; }
/* Wordmark image (cropped tight, about 3.14:1): on-light by default, on-dark
   under a dark theme. The [data-theme] rules win over the media query so the
   manual toggle works. 34px phone, 40px tablet, 44px in the desktop sidebar. */
.brand-logo { display: block; line-height: 0; }
.brand-tagline { display: none; }   /* sidebar only (desktop, below) */
.logo-img { display: block; height: 40px; width: auto; }
@media (max-width: 767px) { .logo-img { height: 34px; } }
@media (min-width: 1280px) { .logo-img { height: 44px; } }
.logo-on-dark { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .logo-on-light { display: none; }
  :root:not([data-theme="light"]) .logo-on-dark { display: block; }
}
:root[data-theme="dark"] .logo-on-light { display: none; }
:root[data-theme="dark"] .logo-on-dark { display: block; }
:root[data-theme="light"] .logo-on-light { display: block; }
:root[data-theme="light"] .logo-on-dark { display: none; }

.header-tools { display: flex; align-items: center; gap: 8px; }
/* Help, theme, sign out: one row of outlined icon buttons. The current page
   (Help open) reads like a selected tab: surface-2 fill, Ink glyph. */
.shell-tools { display: flex; align-items: center; gap: 8px; }
.shell-tools .icon-btn[hidden] { display: none; }

.shell-tools a.icon-btn { text-decoration: none; }


/* Nav. One markup, three layouts (see the "shell" region for the switch):
   phone (<768) = bottom tab bar, tablet (768–1279) = pill row up here,
   desktop (>=1280) = fixed left sidebar. The current page is marked with
   aria-current="page" (the router) or aria-selected="true" (legacy); both
   are styled the same, here, so the tab and a.tab never drift apart. */
.tabs { display: flex; gap: 4px; background: var(--surface-2); padding: 4px; border-radius: var(--r-pill); }
.tab {
  font-size: 13.5px; font-weight: 500;
  background: none; border: 0; color: var(--ink-2);
  padding: 8px 16px; min-height: var(--tap); border-radius: var(--r-pill); cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  text-decoration: none;
  transition: color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.tab:hover { color: var(--ink); }
.tab[hidden] { display: none; }
/* Tablet pill row = a segmented control: the selected segment is surface +
   Ink + shadow-sm on the surface-2 track. */
.tab[aria-current="page"], .tab[aria-selected="true"] { color: var(--ink); font-weight: 600; background: var(--surface); box-shadow: var(--sh-seg); }
.tab-ico { display: none; width: 22px; height: 22px; flex: none; }
.tab-label { min-width: 0; }

/* No data-theme: follow the system. */
.ico-sun { display: none; }
.ico-moon { display: block; }
@media (prefers-color-scheme: dark) {
  .ico-sun { display: block; }
  .ico-moon { display: none; }
}
:root[data-theme="dark"] .ico-sun { display: block; }
:root[data-theme="dark"] .ico-moon { display: none; }
:root[data-theme="light"] .ico-sun { display: none; }
:root[data-theme="light"] .ico-moon { display: block; }

/* ══ §5 Courts finder ═══════════════════════════════════════ */
/* ─────────────── stat strip ───────────────
   One quiet line of number+label pairs instead of KPI tiles: these are
   mostly facts, not metrics, and typography carries them fine. Hairline
   dividers between items; wraps naturally on narrow screens. */
.statbar {
  display: flex; flex-wrap: wrap; align-items: baseline;
  row-gap: 8px; margin: 20px 0 18px;
}
.stat-item {
  display: inline-flex; align-items: baseline; gap: 6px;
  font-size: 13px; color: var(--ink-3); white-space: nowrap;
}
.stat-item b {
  font-size: 16px; font-weight: 700; color: var(--ink);
  letter-spacing: -0.01em; font-variant-numeric: tabular-nums;
  max-width: 180px; overflow: hidden; text-overflow: ellipsis;
}
.stat-item + .stat-item { margin-left: 14px; padding-left: 14px; }
.stat-item[title] { cursor: help; }

/* Stats on the left, the directory-wide tools (Sync, Share) on the right of
   the same row — they act on the whole list, not on a search. */
.dir-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 20px 0 18px;
}
.dir-head .statbar { margin: 0; min-width: 0; }
.dir-tools { display: flex; align-items: center; gap: 8px; flex: none; }

/* The result count and the view switcher read as one line: "126 courts,
   shown as cards". */
.result-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 0 0 16px;
}
.result-bar .result-line { margin: 0; }

/* ─────────────── controls ─────────────── */
.controls { display: flex; gap: 10px; margin-bottom: 14px; flex-wrap: wrap; }
/* Scoped to the row it was written for: unscoped, flex-basis 300px also
   applied inside column flex boxes (the court picker's .overlay), where it
   became a 300px-tall search box. */
.controls > .field.search { flex: 1 1 300px; }

input, select, textarea {
  font-family: var(--font-body); font-size: 14.5px; color: var(--ink); border: 0;
  border-radius: var(--r-inner); padding: 10px 14px; width: 100%;
  transition: border-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out); background: var(--surface-2); }
input:hover, select:hover, textarea:hover { background: color-mix(in srgb, var(--ink) 7%, var(--surface-2)); }
/* Focus is a soft ring outside the fill, never an ink outline (v2). */
input:focus, select:focus, textarea:focus { outline: none; background: var(--surface); box-shadow: var(--ring-field); }
textarea { resize: vertical; }
input::placeholder, textarea::placeholder { color: var(--ink-3); }
select {
  cursor: pointer;
  padding-right: 42px;
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    calc(100% - 20px) 50%,
    calc(100% - 15px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }

/* ─────────────── catalog layout: filter rail + content ─────────────── */
.catalog {
  display: grid; grid-template-columns: 248px minmax(0, 1fr);
  gap: 32px; align-items: start;
}
.main-col { min-width: 0; }

.sidebar {
  position: sticky; top: 84px;
  max-height: calc(100vh - 100px); overflow-y: auto;
  padding-right: 6px;
}
.sidebar-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 6px;
}
.sidebar-title { font-size: var(--fs-md); font-weight: 700; letter-spacing: -0.01em; margin: 0; }

.facet { padding: 14px 0; }
.facet:last-of-type { border-bottom: 0; }
.facet-title {
  font-size: 13px; font-weight: 700; color: var(--ink);
  margin: 0 0 8px;
}
.facet-list { display: flex; flex-direction: column; gap: 2px; }

.facet-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; text-align: left;
  font-size: 14px; font-weight: 500; color: var(--ink-2);
  background: none; border: 0; cursor: pointer;
  padding: 7px 10px; border-radius: var(--r-inner);
  transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.facet-row:hover { background: var(--surface-2); color: var(--ink); }
/* Selected facet goes solid ink — the accent stays reserved for actions. */
.facet-row[aria-pressed="true"] { background: var(--ink); color: var(--ground); }
.facet-n { font-size: 11px; opacity: .6; }

.facet-check {
  display: flex; align-items: center; gap: 9px;
  font-size: 14px; font-weight: 500; color: var(--ink-2); cursor: pointer;
  padding: 7px 10px; border-radius: var(--r-inner);
  transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
.facet-check:hover { background: var(--surface-2); color: var(--ink); }
.facet-check input { width: 16px; height: 16px; margin: 0; accent-color: var(--ink); flex-shrink: 0; }

/* The Apply button exists only for the mobile sheet. */
.sidebar-apply { display: none; }
.sidebar-backdrop { display: none; }

/* Mobile: the rail becomes a bottom sheet behind the Filters button. */
.filters-open { display: none; } /* chip box comes from §3; shown below 900px */
.filters-open svg { width: 15px; height: 15px; flex: none; }
.filter-badge {
  font-size: 11px;
  background: var(--ink); color: var(--ground);
  padding: 1px 6px; border-radius: var(--r-pill);
}

@media (max-width: 1279px) {
  .catalog { display: block; }
  .filters-open { display: inline-flex; }

  .sidebar { display: none; }
  .sidebar.open {
    display: block;
    position: fixed; left: 0; right: 0; bottom: 0; top: auto; z-index: 40;
    max-height: 78vh; overflow-y: auto;
    background: var(--surface);
    border-radius: var(--r-card) var(--r-card) 0 0;
    padding: 20px 20px calc(16px + env(safe-area-inset-bottom));
    box-shadow: var(--shadow-lg);
  }
  .sidebar.open .sidebar-apply {
    display: block; width: 100%;
    position: sticky; bottom: 0; margin-top: 10px;
  }
  .sidebar-backdrop.show {
    display: block;
    position: fixed; inset: 0; z-index: 39;
    background: var(--scrim);
  }
}

/* ─────────────── sync ─────────────── */
.sync-act { color: var(--ink-2); }
.sync-act:hover { color: var(--ink); }
.sync-act svg { width: 14px; height: 14px; flex-shrink: 0; }
.sync-act.busy svg { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }


/* ─────────────── show more ─────────────── */
.more-wrap { display: flex; justify-content: center; margin-top: 24px; }

.result-line {
  font-size: 13px; color: var(--ink-3); margin: 0 0 16px;
  font-variant-numeric: tabular-nums;
}

/* .notice shares the .tag box (§0). */
.notice { color: var(--ink); }
#pendingPill { margin: -4px 0 14px; }

/* ─────────────── time context ───────────────
   Sits above the sort and filters because it changes the question they
   answer. Deliberately not a <select>: the whole value is seeing that 7pm is
   two taps from 5pm, and that some hours are busier than others. */
.at-bar { margin: 0 0 14px; }
.at-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 7px; }
.at-label { font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); }
.at-clear {
  font-family: var(--font-body); font-size: var(--fs-xs); font-weight: 600;
  background: none; border: 0; cursor: pointer; padding: 0; min-height: var(--tap);
  color: var(--ink); text-decoration: underline; text-underline-offset: 2px;
}
.at-clear:hover { text-decoration-thickness: 2px; }

/* The strip holds a 3-part segmented control and a 7-up grid of hour chips:
   no horizontal scrolling. */
.at-strip { display: flex; flex-direction: column; gap: 8px; }
.at-parts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.at-part {
  min-height: var(--seg-h); padding: 0 8px; cursor: pointer;
  font-family: inherit; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2);
  background: none; border: 0; border-radius: var(--r-pill);
  transition: color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.at-part:hover { color: var(--ink); }
.at-part[aria-pressed="true"] { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }
.at-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
.at-grid .slot {
  position: relative; padding: 0; font-size: 12px; font-weight: 600;
}
/* The current hour is marked by a small dot, not a colour change. */
.slot.is-now::before {
  content: ""; position: absolute; top: 9px; right: 6px; width: 5px; height: 5px;
  border-radius: 50%; background: var(--hi-strong);
}
.slot.is-on.is-now::before { background: var(--accent); }
.at-note { font-size: var(--fs-xs); color: var(--ink-3); margin: 7px 2px 0; min-height: 1em; }

.near-me svg { width: 15px; height: 15px; flex-shrink: 0; }
/* Its row also holds the taller view switcher; don't stretch the chip. */
.near-me, .filters-open { align-self: center; }

/* ─────────────── court grid ─────────────── */
.grid {
  /* Default (stretch) alignment on purpose: every card in a row gets the
     row's height, and .court-foot's margin-top:auto absorbs the difference —
     so a 1-tier card and a 3-tier card sit flush top and bottom. */
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 16px;
}
.court {
  background: var(--surface); border: 0;
  border-radius: var(--r-card); padding: var(--sp-4) var(--sp-5);
  display: flex; flex-direction: column; gap: 11px;
  transition: box-shadow var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out), border-color var(--dur-2) var(--ease-out);
  content-visibility: auto;
  contain-intrinsic-size: 260px;
  /* Named so the query below reads against the box the card was placed in
     rather than the viewport. This is what lets the same markup serve the
     grid and the map popup: the popup is not narrow because the phone is
     narrow, it is narrow because it is a popup — which a media query has no
     way to express. */
  container: court / inline-size; box-shadow: var(--sh-card); }

/* Compact form. Everything the card would show is still there; the parts
   that need room are dropped, so a pin gives you the name, the price, a
   booking link, the favourite and Log — never a dead end. */
@container court (max-width: 260px) {
  .court { padding: 0; border: 0; background: none; gap: 8px; }
  .court:hover { box-shadow: none; transform: none; }
  .court .court-mid { flex-direction: column; align-items: flex-start; gap: 2px; }
  .court .court-meta { flex-direction: column; align-items: flex-start; gap: 0; }
  .court .court-meta .btn--quiet { margin: 0 0 0 -10px; }
  .court .court-name { font-size: 15px; }
  .court .book-link { padding: 0 12px; font-size: 12.5px; min-height: var(--tap); }
  .court .fav { margin: 0; }
  .court .court-foot { padding-top: 2px; }
}
.court:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}
.court:active { transform: scale(.985); }

.court-top { display: flex; justify-content: space-between; gap: 10px; align-items: flex-start; }
/* A player's approved photo (api/court-edits.php), edge to edge on top of the
   card. Only courts that have one get it: no placeholder, so a text card
   stays exactly as it was. */
.court-photo { display: block; margin: calc(-1 * var(--sp-4)) calc(-1 * var(--sp-5)) 0; border-radius: var(--r-card) var(--r-card) 0 0; overflow: hidden; }
.court-photo img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; background: var(--line); }
.t-court.has-thumb { display: grid; grid-template-columns: 44px minmax(0, 1fr); column-gap: 10px; align-items: center; }
.t-court.has-thumb > :not(.t-thumb) { grid-column: 2; }
.t-thumb { grid-row: 1 / span 3; width: 44px; height: 44px; object-fit: cover; border-radius: var(--r-field); background: var(--line); }
.court-name {
  font-size: var(--fs-md); font-weight: 700; letter-spacing: -0.01em;
  margin: 0; line-height: 1.3; text-wrap: balance;
}
.court-name a { transition: transform var(--dur-1) var(--ease-out); }
.court-loc { font-size: 13px; color: var(--ink-2); margin: 2px 0 0; font-style: normal; }
/* Place and facts (assets/court-meta.js): icon + short text, one wrapping row. */
.court-loc, .court-facts { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; }
.cm { display: inline-flex; align-items: flex-start; gap: 6px; min-width: 0; line-height: 20px; }
.cm-ico { flex: none; width: 16px; height: 16px; margin-top: 2px; color: var(--ink-3); }
.cm-t { min-width: 0; }
.court-meta-rows { display: flex; flex-direction: column; gap: 6px; }
.court-meta-rows .court-loc, .court-meta-rows .court-facts { margin: 0; }
.court-id { min-width: 0; }
/* The name links to the court's own page (courts/<id>/): a real link, so it
   is reachable by keyboard, opens in a new tab on demand and is crawlable. */
.court-open {
  font: inherit; color: inherit; letter-spacing: inherit; text-align: left;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  border-radius: 4px; text-decoration: none;
}
.court-open:hover { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }

/* One line of facts on the left, the price on the right. */
.court-mid { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.court-facts { margin: 0; font-size: 13px; color: var(--ink-2); min-width: 0; }
.court-price { margin: 0; font-size: 12.5px; color: var(--ink-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
.court-price b { font-size: 15px; font-weight: 600; color: var(--ink); }
.court-price span { color: var(--hi-strong); font-weight: 600; }
.court-price.is-missing { color: var(--ink-3); }

/* Under the one button: when it was checked, and the quiet booking log. */
.court-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: var(--tap); }
.court-meta .btn--quiet { margin-right: -10px; }


/* Between confirmed-open and unknown-hours courts when an hour is set. */
.group-label {
  grid-column: 1 / -1; margin: 10px 0 -2px;
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2);
}

.fav {
  background: none; border: 0; cursor: pointer; padding: 4px;
  min-width: var(--tap); min-height: var(--tap); display: grid; place-items: center;
  margin: -10px -10px 0 0; border-radius: 50%;
  color: var(--ink-3); line-height: 0; flex-shrink: 0;
  transition: color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.fav:hover { color: var(--ink); transform: scale(1.1); }
.fav:active { transform: scale(.96); }
.fav svg { width: 18px; height: 18px; }
.fav[aria-pressed="true"] { color: var(--danger); }
.fav[aria-pressed="true"] svg { fill: currentColor; }
/* Only the heart that was just tapped pops (app.js favBtn adds .is-burst),
   with a ring that breaks outward; a re-render never re-pops the others. */
.fav { position: relative; }
.fav.is-burst svg { animation: fav-pop var(--dur-3) var(--ease-out); }
.fav.is-burst[aria-pressed="true"]::after {
  content: ""; position: absolute; inset: 50% auto auto 50%; width: 30px; height: 30px; margin: -15px 0 0 -15px;
  border-radius: 50%; border: 2px solid var(--danger); pointer-events: none;
  animation: fav-ring var(--dur-3) var(--ease-out) forwards;
}
@keyframes fav-pop { 0% { transform: scale(1); } 35% { transform: scale(.8); } 65% { transform: scale(1.28); } 100% { transform: scale(1); } }
@keyframes fav-ring { 0% { opacity: .7; transform: scale(.4); } 100% { opacity: 0; transform: scale(1.35); border-width: 0; } }

.badges { display: flex; gap: 6px; flex-wrap: wrap; min-width: 0; }
/* Open at the chosen hour. The lime-family "yes, and it counts" colour. */
.badge-open { background: var(--hi-wash); color: var(--hi-strong); }
/* Not a warning — the venue may well be open. It says the data doesn't know,
   which is why these are never filtered out. */
.badge-unknown { background: var(--surface-2); color: var(--ink-3); }

.court-dist { font-size: 12px; color: var(--ink-2); }

/* The tier that actually applies at the chosen hour, so a changed headline
   price points at its own reason instead of just being different. */
.rate.is-now { color: var(--ink); font-weight: 600; }
.rate.is-now .rate-amt { color: var(--hi-strong); }
.rate.is-missing { color: var(--ink-3); }

.rates { display: flex; flex-direction: column; gap: 4px; margin-top: 2px; }
.rate {
  display: flex; justify-content: space-between; gap: 10px;
  font-size: 13px; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.rate-amt { font-size: 12.5px; color: var(--ink); font-weight: 500; }
.rate-free { color: var(--good); }

.court-foot {
  display: flex; flex-direction: column; align-items: stretch; gap: 4px;
  margin-top: auto; padding-top: 4px;
}
.court-foot > .book-link { width: 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── court details sheet ── */
.court-detail .fav { margin: -6px 0 0 0; }
/* The sheet's × sits top-right; the title row keeps its own room so the ♥ never lands under it. */
.overlay .court-detail .court-top { padding-right: calc(var(--tap) - var(--sp-2)); }
.court-detail .court-meta-rows { margin-top: -8px; }
.cd-sec { display: flex; flex-direction: column; gap: 8px; padding-top: var(--sp-4); }
.cd-h { margin: 0; font-size: var(--fs-sm); font-weight: 700; color: var(--ink); }
.cd-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.cd-actions > :first-child { grid-column: 1 / -1; }
.cd-actions > :last-child:nth-child(even) { grid-column: 1 / -1; }
.cd-actions > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.court-detail .court-meta { padding-top: var(--sp-3); }
.cd-player { margin: 0; font-size: var(--fs-xs); color: var(--ink-3); display: flex; align-items: center; gap: 4px; }

/* ── add a court ── */
.add-prompt {
  display: flex; align-items: center; justify-content: center; gap: 4px; flex-wrap: wrap;
  margin: 20px 0 0; font-size: var(--fs-sm); color: var(--ink-3);
}

.ac-details { display: flex; flex-direction: column; gap: var(--sp-3); }
.ac-details[hidden] { display: none; }
.ac-opt { font-weight: 500; color: var(--ink-3); }
.ac-pin { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ac-pin .dlg-hint { margin: 0; }
.ac-matches:empty { display: none; }
.ac-matches { display: flex; flex-direction: column; gap: 6px; }
.ac-m-head { margin: 0; font-size: var(--fs-sm); font-weight: 700; color: var(--ink); }
.ac-m-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.ac-match {
  width: 100%; text-align: left; font: inherit; color: var(--ink); cursor: pointer;
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 2px 8px;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--r-inner);
  padding: 10px 12px; min-height: var(--tap);
  transition: border-color var(--dur-1) var(--ease-out);
}
.ac-match:not(:disabled):hover { background: var(--surface-2); }
.ac-match:disabled { cursor: default; opacity: 1; }
.ac-m-name { font-weight: 600; font-size: 14px; min-width: 0; }
.ac-m-sub { grid-column: 1; font-size: 12px; color: var(--ink-3); }
.ac-match .tag { grid-column: 2; grid-row: 1 / span 2; }
.ac-confirm { padding-inline: 2px; }

/* ── admin review ── */
/* A notice pill that opens the review sheet, under the stats. */
.review-link { margin: -4px 0 14px; cursor: pointer; border: 0; font: inherit; font-size: var(--fs-xs); font-weight: 600; }
.review-link::after { content: " →"; }
.rv-item { display: flex; flex-direction: column; gap: 6px; }
.overlay .rv-item, .rv-mount:not(.dash-review) > .rv-item { padding-top: var(--sp-4); }
.rv-name { margin: 0; font-size: var(--fs-md); font-weight: 700; }
.rv-item p { margin: 0; }
.rv-links, .rv-by { font-size: var(--fs-xs); color: var(--ink-3); overflow-wrap: anywhere; }
.rv-links a { color: var(--ink-2); }
.rv-note { font-size: 13px; color: var(--ink-2); }
.rv-changes { margin: 0; padding-left: 18px; font-size: 13px; color: var(--ink-2); display: flex; flex-direction: column; gap: 2px; }
.rv-changes b { color: var(--ink); font-weight: 600; }
.rv-photo { width: 100%; max-height: 260px; object-fit: cover; border-radius: var(--r-inner); background: var(--line); }

/* The court sheet: photo on top, and "Know this court?" (suggest an edit,
   add a photo). */
.cd-photo { margin: 0; }
.cd-photo img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--r-inner); background: var(--line); }
.cd-photo figcaption { margin-top: 6px; font-size: var(--fs-xs); color: var(--ink-3); }
.cd-know-sub { margin: 0; font-size: 13px; color: var(--ink-2); }
.cd-know-acts { display: flex; flex-wrap: wrap; gap: 8px; }
.cd-know-scout { margin: 0; font-size: 13px; color: var(--ink-2); }
.cd-know-scout a { color: var(--ink); font-weight: 600; }
/* Suggest an edit: a quick "still right" line, then three short groups. */
.ce-ok { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; padding-bottom: var(--sp-4); }
.ce-ok p { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); flex: 1 1 180px; }
.ce-grp { display: flex; flex-direction: column; gap: var(--sp-3); }
.ce-grp > .sub-h { color: var(--ink); }
.ce-pair { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--sp-3) var(--sp-5); }
.ce-pair > .field-row { flex: 0 0 auto; }
.ce-pair > .field-row:last-child { flex: 1 1 160px; }
/* Court facts and Getting there (Set 8): a select each, two up when there is room. */
.ce-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-3); }
.ce-facts select { width: 100%; }
.ce-rate { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 44px; gap: 8px; align-items: center; }
.ce-rate ~ .ce-rate { padding-top: var(--sp-3); }
.ce-rate input, .ce-rate .picker-btn { min-width: 0; }
.ce-r-amt { position: relative; min-width: 0; }
.ce-r-amt > span { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--ink-2); font-weight: 600; pointer-events: none; }
.ce-r-amt input { width: 100%; padding-left: 30px; }
.ce-r-del { grid-column: 3; grid-row: 1; }
.ce-rate .tp-field { grid-row: 2; }
/* A time field: the word on the left, the time on the right, like a row. */
.tp-field { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tp-field .tp-k { color: var(--ink-2); font-weight: 500; }
.tp-field.is-empty .tp-v { color: var(--ink-3); font-weight: 500; }

#ceRates { display: flex; flex-direction: column; gap: var(--sp-3); }
.ce-add { align-self: flex-start; margin-left: -12px; }
.check-row { display: flex; gap: 10px; align-items: flex-start; font-size: 14px; color: var(--ink); cursor: pointer; }
.check-row input { flex: none; width: auto; min-height: 0; margin-top: 3px; }
.rv-dupes { font-size: 12.5px; color: var(--ink-2); background: var(--surface-2); border-radius: var(--r-inner); padding: 8px 12px; }
.rv-dupes p { font-weight: 600; color: var(--ink); }
.rv-dupes ul { margin: 4px 0 0; padding-left: 18px; }
.bk-via { font-size: var(--fs-xs); font-weight: 500; color: var(--ink-3); margin-left: 6px; white-space: nowrap; }

.verified { font-size: var(--fs-xs); color: var(--ink-3); font-variant-numeric: tabular-nums; }
/* Old listing: said in words, in a stronger ink — not a warning colour. */
.verified.is-stale { color: var(--ink-2); }
.walkin { font-size: var(--fs-xs); color: var(--ink-3); }

/* ══ §6 Bookings + pickers ══════════════════════════════════
   (§6 also holds the shared .bk and .foot rules that
   older screens use; change them only from the foundation pass.) */
/* ─────────────── bookings ─────────────── */

.card-title { margin: 0; }

.form-card { display: flex; flex-direction: column; gap: 12px; }
.field-row { display: flex; flex-direction: column; gap: 5px; }
.field-row > label,
.field-label { font-size: 12.5px; font-weight: 600; color: var(--ink-2); }
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* ── star rating ───────────────────────────────────────── */
/* Stars sit in reverse DOM order (5→1) and row-reverse flips them back, so a
   plain `input:checked ~ label` fills every star to the left of the checked
   one. Avoids fifteen :has() selectors for the same effect. */
.stars { display: flex; flex-direction: row-reverse; justify-content: flex-end; gap: 2px; padding: 3px 0; }
.stars input { position: absolute; opacity: 0; width: 0; height: 0; }
.stars label {
  cursor: pointer; font-size: 20px; line-height: 1.1;
  font-size: 0;                       /* the ★ in the markup; ::before draws it */
  transition: color var(--dur-1) var(--ease-out);
}
/* Unselected stars are drawn HOLLOW rather than dimmed. A faded solid star
   reads as "disabled" — at 4 stars the fifth looked greyed-out and broken
   rather than simply not chosen. An outline says "empty" unambiguously and
   needs no opacity trick. */
.stars label::before {
  content: '☆'; font-size: 20px; color: var(--ink-3);
}
.stars input:checked ~ label::before,
.stars label:hover::before,
.stars label:hover ~ label::before { content: '★'; color: var(--danger); }
.form-actions { display: flex; gap: 10px; align-items: center; margin-top: 4px; }
.form-msg { font-size: var(--fs-sm); margin: 0; color: var(--ink-3); }
/* An empty status line takes no height (it used to reserve 1em and leave
   a gap at the bottom of every card). Not display:none — it is a live
   region, and screen readers can miss text added to a hidden one. */
.form-msg:empty { min-height: 0; }
.form-msg.ok { color: var(--good); }
.form-msg.err { color: var(--danger); }

.bk {
  background: var(--surface); border: 0;
  border-radius: var(--r-inner); padding: 14px 16px; margin-bottom: 10px;
  display: flex; gap: 14px; align-items: center;
  transition: box-shadow var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out); box-shadow: var(--sh-card); }
a.bk:hover { box-shadow: var(--shadow-md); }
a.bk:active { transform: scale(.985); }
.bk.is-past { opacity: .62; }
.bk-when {
  font-size: 11px; color: var(--ink-2);
  text-align: center; line-height: 1.3; flex-shrink: 0;
  min-width: 54px; padding: 8px 6px;
  background: var(--surface-2); border-radius: var(--r-inner);
  font-variant-numeric: tabular-nums;
}
.bk-day { font-family: var(--font-body); font-size: 19px; font-weight: 700; color: var(--ink); display: block; }
.bk-body { flex: 1; min-width: 0; }
.bk-court { font-size: 14.5px; font-weight: 600; margin: 0; }
.bk-meta { font-size: 12.5px; color: var(--ink-3); margin: 2px 0 0; }

/* A cancelled row stays legible but visibly out of the running — it is
   excluded from every total, so it should not read as a normal session. */
.bk.is-off { opacity: .5; }
.bk.is-off .bk-cost { text-decoration: line-through; }
.bk .tag { margin-left: 6px; }
.bk-stars { margin-left: 6px; font-size: 12px; color: var(--danger); letter-spacing: 1px; }
.bk-cost { font-size: 13px; font-variant-numeric: tabular-nums; }
.bk-acts { display: flex; gap: 2px; flex-shrink: 0; }

.foot {
  margin: 48px auto 32px; padding-top: 20px;
  font-size: 12.5px; color: var(--ink-3);
}
.foot p { margin: 0 0 4px; }
.foot a { color: var(--ink-2); }
.foot-sub { display: flex; gap: 6px; align-items: center; }
.sync { font-size: 11px; }
.sync.on { color: var(--good); }
.sync.off { color: var(--ink-3); }

.overlay--s code { font-size: 12px; background: var(--surface-2); padding: 1px 5px; border-radius: var(--r-field); }
.overlay--s form { display: flex; flex-direction: column; gap: 10px; }
#opCfgFairOnly:not([hidden]) { display: flex; flex-direction: column; gap: 10px; }   /* the fields paddle stack hides, same rhythm */

/* ─────────────── view switcher ─────────────── */
.view-seg { display: flex; gap: 4px; align-self: center; }
.view-btn {
  background: none; border: 0; color: var(--ink-2); cursor: pointer;
  width: var(--tap); height: var(--seg-h); border-radius: var(--r-pill);
  display: inline-flex; align-items: center; justify-content: center;
  transition: color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.view-btn:hover { color: var(--ink); }
.view-btn[aria-pressed="true"] { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }
.view-btn svg { width: 15px; height: 15px; }

/* ─────────────── list view ─────────────── */
.table-wrap {
  border: 0; border-radius: var(--r-card);
  overflow-x: auto; background: var(--surface);
  box-shadow: var(--sh-card);
}
.courts-table { border-collapse: collapse; width: 100%; min-width: 840px; }
.courts-table th {
  font-size: 12px; font-weight: 600; color: var(--ink-3); text-align: left;
  padding: 12px 14px;
  position: sticky; top: 0; background: var(--surface); z-index: 1;
}
.courts-table td {
  font-size: 13.5px; padding: 11px 14px; vertical-align: middle;
}
.courts-table tr:last-child td { border-bottom: 0; }
.courts-table tbody tr { transition: background-color var(--dur-1) var(--ease-out); }
.courts-table tbody tr:hover { background: var(--surface-2); }
.courts-table .num { font-size: 12.5px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.t-name { display: block; font-weight: 600; }
.t-name.court-open { font-size: inherit; }
.t-unit { display: none; }
.courts-table .t-group th {
  position: static; text-align: left; font-size: 12px; font-weight: 600; color: var(--ink-2);
  background: var(--surface-2); padding: 10px 14px;
}
.t-loc { display: block; font-size: 12px; color: var(--ink-3); }
.t-check { white-space: nowrap; }
/* The row has no room for the open/unknown badge, so it gets the same
   meaning as a dot. Colour is backed by the title attribute, never alone. */
.t-dot { font-size: 15px; line-height: 0; vertical-align: middle; }
.t-dot.is-open { color: var(--good); }
.t-dot.is-unknown { color: var(--ink-3); }
.courts-table .fav { padding: 2px; margin: 0; }

/* ─────────────── map view ─────────────── */
.map-wrap {
  border: 0; border-radius: var(--r-card);
  overflow: hidden; background: var(--surface-2);
  /* The map library's controls, markers and popups carry their own z-indexes.
     Isolating here traps that whole scale inside this box, so nothing in the
     map can paint over the fixed tab bar or the app's sheets (all <= 40).
     (Leaflet's 400-1000 range once drew pins over the phone tab bar.) */
  isolation: isolate;
}
.map-wrap { position: relative; }
#map { height: min(62vh, 640px); min-height: 420px; width: 100%; }
.map-fail {
  position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 1001;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: var(--surface); border: 0; border-radius: var(--r-inner);
  padding: 10px 12px 10px 14px; box-shadow: var(--shadow-md);
}
.map-fail p { margin: 0; font-size: 13px; color: var(--ink-2); }
.map-note { font-size: 12px; color: var(--ink-3); margin: 8px 2px 0; }

/* MapLibre chrome, restyled to match the cards. The vector style itself
   (land, water, roads, labels, both themes) is patched in assets/map-style.js;
   --map-land matches its background so there's no flash before tiles arrive. */
#map { --map-land: #f1f1ee; background: var(--map-land); }
#map.maplibregl-map { font: inherit; font-family: var(--font-body); -webkit-tap-highlight-color: transparent; }

/* Court popup: a surface card. The popup holds a .court, so it only has to be
   a container and get out of the way; the card styles itself down via the
   @container rule above. Popups sit above every HTML marker (me dot, crew). */
.maplibregl-popup.pac-popup { z-index: 3; max-width: none !important; }
.pac-popup .maplibregl-popup-content {
  box-sizing: border-box; width: var(--w-card-sm); max-width: calc(100vw - 2 * var(--gutter));
  padding: 0; background: var(--surface); color: var(--ink);
  border-radius: var(--r-card);
  font-family: var(--font-body); line-height: 1.45;
}
.pac-popup.maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
.pac-popup.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip,
.pac-popup.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip { border-top-color: var(--surface); }
.pac-popup.maplibregl-popup-anchor-top .maplibregl-popup-tip,
.pac-popup.maplibregl-popup-anchor-top-left .maplibregl-popup-tip,
.pac-popup.maplibregl-popup-anchor-top-right .maplibregl-popup-tip { border-bottom-color: var(--surface); }
.pac-popup.maplibregl-popup-anchor-left .maplibregl-popup-tip { border-right-color: var(--surface); }
.pac-popup.maplibregl-popup-anchor-right .maplibregl-popup-tip { border-left-color: var(--surface); }
.pac-popup .maplibregl-popup-close-button {
  top: 6px; right: 6px; width: 32px; height: 32px; padding: 0;
  display: grid; place-items: center; border: 0; border-radius: var(--r-pill);
  font-size: 20px; line-height: 1; color: var(--ink-2); background: transparent; cursor: pointer;
  transition: background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.pac-popup .maplibregl-popup-close-button:hover { color: var(--ink); background: var(--surface-2); }
.pac-popup .maplibregl-popup-close-button:active { transform: scale(.97); }
.pac-popup .court { color: var(--ink); } /* one frame: the popup box; the card inside carries only its padding */
.pac-popup .court { background: transparent; border: 0; border-radius: 0; box-shadow: none; transform: none; }
/* A popup is always on screen, so the grid's content-visibility: auto buys
   nothing here. In iOS Safari it costs a lot: inside the map's transformed
   popup it judged the card off screen on the first tap of a link, painted it
   blank, and the tap after that only brought it back. */
.pac-popup .court { content-visibility: visible; contain-intrinsic-size: none; }
/* And no press-scale on its buttons: that repaint on the first tap is what
   Safari dropped the card on. */
.pac-popup .btn:active { transform: none; }
.pac-popup .court-top { padding-right: 34px; }   /* the ♥ sits left of the close button */
.pac-popup .court .fav { margin: -10px 0 0 0; }
.pac-popup :is(.court-name, .court-loc) { margin: 0; }
.pac-popup a.book-link { color: var(--accent-ink); }
.pac-popup a.book-link.social, .pac-popup a.book-link.alt, .pac-popup a.book-link.phone { background: var(--surface); border: 0; color: var(--ink); }

/* Zoom buttons: 36px, surface, hairline; glyphs are masks in currentColor so
   they follow the theme (MapLibre's own icons are fixed dark SVGs). */
#map .maplibregl-ctrl-group {
  background: var(--surface); border: 0; border-radius: var(--r-inner);
  overflow: hidden; box-shadow: var(--shadow-sm);
}
#map .maplibregl-ctrl-group button { width: 36px; height: 36px; color: var(--ink); background-color: transparent; }
#map .maplibregl-ctrl-group button:not(:disabled):hover { background-color: var(--surface-2); }
#map .maplibregl-ctrl-group button:focus-visible { box-shadow: inset 0 0 0 2px var(--ink); }
#map .maplibregl-ctrl-group .maplibregl-ctrl-icon {
  background-image: none; background-color: currentColor;
  -webkit-mask: var(--ctl-glyph) center / 18px 18px no-repeat; mask: var(--ctl-glyph) center / 18px 18px no-repeat;
}
#map .maplibregl-ctrl-zoom-in { --ctl-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Cpath d='M9 3.5v11M3.5 9h11' stroke='%23000' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
#map .maplibregl-ctrl-zoom-out { --ctl-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Cpath d='M3.5 9h11' stroke='%23000' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
#map .maplibregl-ctrl-group button:disabled .maplibregl-ctrl-icon { opacity: .3; }
#map .maplibregl-ctrl-bottom-right .maplibregl-ctrl { margin: 0 12px 12px 0; }
#map .maplibregl-ctrl-bottom-left .maplibregl-ctrl { margin: 0 0 12px 12px; }

/* Attribution (OpenFreeMap / OpenMapTiles / OSM, required): compact and
   quiet, a 28px "i" that opens a one-line credit. */
#map .maplibregl-ctrl-attrib {
  font-size: 11px; line-height: 16px; color: var(--ink-3);
  background: color-mix(in srgb, var(--surface) 85%, transparent);
}
#map .maplibregl-ctrl-attrib a { color: var(--ink-2); }
#map .maplibregl-ctrl-attrib a:hover { color: var(--ink); }
#map .maplibregl-ctrl-attrib.maplibregl-compact {
  box-sizing: border-box; min-height: 28px; padding: 0 28px 0 0; border-radius: var(--r-pill);
  color: var(--ink-3);
}
#map .maplibregl-ctrl-attrib.maplibregl-compact-show { padding: 6px 32px 6px 12px; max-width: calc(100vw - 88px); }
#map .maplibregl-ctrl-attrib-button {
  position: absolute; top: 0; right: 0; left: auto; width: 28px; height: 28px; border-radius: var(--r-pill);
  background-image: none; background-color: transparent; color: var(--ink-2);
}
#map .maplibregl-ctrl-attrib-button::before {
  content: ""; position: absolute; inset: 0; background-color: currentColor;
  -webkit-mask: var(--attrib-glyph) center / 16px 16px no-repeat; mask: var(--attrib-glyph) center / 16px 16px no-repeat;
  --attrib-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6.6' fill='none' stroke='%23000' stroke-width='1.4'/%3E%3Cpath d='M8 7.2v4' stroke='%23000' stroke-width='1.6' stroke-linecap='round'/%3E%3Ccircle cx='8' cy='4.9' r='1' fill='%23000'/%3E%3C/svg%3E");
}
#map .maplibregl-ctrl-attrib-button:hover { color: var(--ink); }
/* Same, mirrored, if the credit sits bottom-left. */
#map :is(.maplibregl-ctrl-bottom-left, .maplibregl-ctrl-top-left) .maplibregl-ctrl-attrib.maplibregl-compact { padding: 0 0 0 28px; }
#map :is(.maplibregl-ctrl-bottom-left, .maplibregl-ctrl-top-left) .maplibregl-ctrl-attrib.maplibregl-compact-show { padding: 6px 12px 6px 32px; }
#map :is(.maplibregl-ctrl-bottom-left, .maplibregl-ctrl-top-left) .maplibregl-ctrl-attrib-button { left: 0; right: auto; }
#map .maplibregl-ctrl-attrib-button:focus-visible { box-shadow: inset 0 0 0 2px var(--ink); }

/* ─────────────── view transitions ───────────────
   The three court views and the three tabs used to swap in a single frame.
   These names give the browser something to pair across the swap so it can
   tween rather than cut.

   Named at panel level on purpose. Naming each card would let a card morph
   into its own list row, but 40 named elements is 40 snapshot layers on a
   phone, and this app is used on phones — the panel cross-fade reads almost
   the same for a fraction of the work. */
.grid, .table-wrap, .map-wrap { view-transition-name: court-view; }
[role="tabpanel"] { view-transition-name: tab-panel; }

::view-transition-old(court-view),
::view-transition-new(court-view),
::view-transition-old(tab-panel),
::view-transition-new(tab-panel) {
  animation-duration: var(--dur-2);
  animation-timing-function: var(--ease-out);
}

/* The root cross-fade would run over the masthead and filter rail too, which
   is a lot of movement for switching a list to a map. */
::view-transition-old(root),
::view-transition-new(root) { animation: none; }

/* The ONE reduced-motion block. Kills animations and transitions, and any
   press / hover transform. withTransition() already bails out here, but a
   page loaded mid-flight shouldn't animate either. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  :is(.btn, .book-link, .btn--quiet, .btn--state, .icon-btn,
    #themeBtn, .cal-step, .share-act, .step-btn, .stepper button, .chip, .slot, .near-me, .city-pick,
    .op-pick, .ci-pick, .filters-open, .sync-act, .tab, .view-btn, .at-part, .seg-item, .tp-h, .tp-m, .tp-ap-btn,
    .fav, .fab, .team-pick, .rally-btn, .fire-btn, .story-ring, .page-back, .court, .feed-card, a.bk, a.lb-row,
    a.gcard, a.rail-card, a.profile-rel, .court-name a, .cp-item, .facet-row, .op-side, .ctl-btn):is(:hover, :active) {
    transform: none !important;
  }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

@media (max-width: 767px) {
  body { font-size: 14px; }

  .masthead {
    top: 0;
    padding-top: env(safe-area-inset-top);
  }
  /* Only the brand and the theme toggle stay up here now — the tabs move
     to the bottom (below), so this never wraps and stays one compact row. */
  .masthead-in {
    min-height: 52px;
    gap: 10px;
    padding-block: 8px;
    flex-wrap: nowrap;
  }
  .header-tools { margin-left: auto; }
  /* Primary navigation belongs at the bottom on a phone.
     Three destinations is the case both Apple's HIG and Material put in a
     bottom bar, and the bottom third is the only area a thumb reaches
     comfortably one-handed — which is how this gets used courtside. It
     also hands the header back its second row.
     z-index sits under the filter sheet (40) and its backdrop (39), so
     opening filters correctly covers the bar. */
  /* The tab bar itself now lives in the "shell" region (<768px), so it also
     covers a large phone in landscape and a small tablet in portrait. */

  /* A wrapped flex line still applies border-left to the item that starts
     it, so the stats showed a stray divider hanging at the beginning of the
     second row. A grid has no such edge case, and two columns is a better
     fit for four stats on a phone than an uneven wrap. */
  .statbar {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px 12px;
    margin: 14px 0 14px;
  }
  /* Two-by-two stats, the two round tools top-right of them. */
  .dir-head { align-items: flex-start; margin: 14px 0; }
  .dir-head .statbar { flex: 1; margin: 0; }
  .stat-item {
    font-size: 12px;
    white-space: normal;
  }
  .stat-item b {
    font-size: 15px;
    max-width: none;
    flex: none;        /* the label wraps; the number never truncates */
  }
  .stat-item + .stat-item {
    margin-left: 0;
    padding-left: 0;
    border-left: 0;
  }

  /* Three tracks, two rows: search, then Filters · sort · Near me. Views,
     Sync and Share live elsewhere now (result line, stat row). */
  .controls {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 8px;
    margin-bottom: 12px;
  }
  .field.search {
    grid-column: 1 / -1;
    grid-row: 1;
    min-width: 0;
  }
  input[type="search"] {
    min-height: 46px;
    font-size: 16px;
  }
  .filters-open {
    grid-column: 1;
    grid-row: 2;
    padding-inline: 14px;
  }
  .controls > .field:not(.search) {
    grid-column: 2;
    grid-row: 2;
    min-width: 0;
  }
  /* The list keeps every column on a phone: each row reflows into three
     lines instead of scrolling sideways past Price, Booking and Checked.
       name ........................ ♡
       Covered · 2 courts      ₱500–800/hr
       [Book on Onda]       Checked 4 months ago */
  .table-wrap { overflow: visible; }
  .courts-table { min-width: 0; }
  .courts-table thead { display: none; }
  .courts-table tbody tr {
    display: grid; align-items: center;
    grid-template-columns: auto auto minmax(0, 1fr) auto;
    grid-template-areas: "court court court fav" "type n price price" "foot foot foot foot";
    gap: 6px 6px; padding: 12px 14px;
  }
  .courts-table tbody tr:last-child { border-bottom: 0; }
  .courts-table td { padding: 0; border: 0; font-size: 13px; }
  .courts-table .t-court { grid-area: court; min-width: 0; }
  .courts-table .t-fav { grid-area: fav; align-self: start; margin: -8px -10px 0 0; }
  .courts-table .t-type { grid-area: type; color: var(--ink-2); text-align: left; }
  .courts-table .t-type::after { content: " ·"; }
  .courts-table .t-n { grid-area: n; justify-self: start; text-align: left; font-family: inherit; font-size: 13px; color: var(--ink-2); }
  .courts-table .t-unit { display: inline; }
  .courts-table .t-price { grid-area: price; justify-self: end; font-size: 13.5px; font-weight: 600; }
  /* Booking and Checked share the spanning bottom line: an item spanning the
     1fr track can't inflate the auto tracks above it. */
  .courts-table .t-book { grid-area: foot; justify-self: start; }
  .courts-table .t-check { grid-area: foot; justify-self: end; text-align: right; white-space: normal; }
  .courts-table .t-group { display: block; padding: 0; }
  .courts-table .t-group th { display: block; }

  .grid {
    grid-template-columns: 1fr;
    gap: 12px;
  }
  .court {
    padding: 16px;
  }
  .court:hover {
    transform: none;
  }
  .court-foot {
    align-items: stretch;
  }

  .sidebar.open {
    max-height: min(82vh, 680px);
    border-radius: var(--r-sheet) var(--r-sheet) 0 0;
    padding: 18px 16px calc(16px + env(safe-area-inset-bottom));
  }
  .facet-row,
  .facet-check {
    min-height: 42px;
  }

  .field-grid {
    grid-template-columns: 1fr;
  }
  .bk {
    display: grid;
    grid-template-columns: 54px minmax(0, 1fr);
    align-items: start;
    gap: 10px 12px;
    padding: 14px;
  }
  .bk-cost {
    grid-column: 2;
  }
  .bk-acts {
    grid-column: 1 / -1;
    justify-content: flex-end;
  }

  /* The map should end where the tab bar begins, so every pin is reachable
     without scrolling the map under the bar. The exact gap depends on how
     much chrome sits above it — stats, search, the controls grid, the result
     line — which wraps differently per device, so sizeMap() measures it and
     writes the height inline. This value is only the pre-measurement
     fallback; the old fixed 238px offset guessed ~95px short and pushed the
     bottom of the map off-screen. */
  #map {
    height: calc(100svh - 340px);
    min-height: 300px;
  }
}

/* ── date picker ────────────────────────────────────────
   A trigger button plus a popover reusing the bookings month grid.
   Native <input type="date"> renders as a different control in every
   browser and matched nothing else in the form; this reads as part of
   the app and shows the weekday, which is what you actually want to
   know when picking a court slot. */
.picker { position: relative; }

/* Matches the input styling exactly, so the row reads as one form. */
.picker-btn {
  width: 100%; text-align: left; cursor: pointer;
  font-family: var(--font-body); font-size: 14.5px; font-weight: 600; color: var(--ink); border: 0;
  border-radius: var(--r-inner); padding: 10px 14px;
  transition: border-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out); background: var(--surface-2); }
.picker-btn:hover { background: color-mix(in srgb, var(--ink) 7%, var(--surface-2)); }
.picker-btn[aria-expanded="true"] { background: var(--ink); color: var(--surface); }
.picker-btn.is-empty { font-weight: 500; color: var(--ink-3); }


/* Keeps the fallback box on screen when the field sits near the right edge. */
.field-grid > .field-row:last-child .overlay--pop:not([popover]) { left: auto; right: 0; }
#bDateBtn { anchor-name: --pk-date; }
#bTimeBtn { anchor-name: --pk-time; }
#bCourtBtn { anchor-name: --pk-court; }
#bDatePop[popover] { position-anchor: --pk-date; }
#bTimePop[popover] { position-anchor: --pk-time; }
#bCourtPop[popover] { position-anchor: --pk-court; }

.overlay--pop .cal-cell { min-height: 34px; border-radius: var(--r-inner); }
.overlay--pop .cal-head { margin: 0 0 10px; }
.overlay--pop .cal-month { font-size: var(--fs-base); }

/* Every day is selectable here — a booking log records the past as well
   as the future, so nothing is disabled. */
.cal--pick .cal-cell { cursor: pointer; color: var(--ink); }
.cal--pick .cal-cell:hover { background: var(--surface-2); }
.cal--pick .cal-cell.is-blank { cursor: default; }

/* ── time picker ────────────────────────────────────────
   One choice out of 76 became three small ones. Everything below is sized
   so the whole control — meridiem, twelve hours, four quarters — fits in a
   popover with no scrolling at any viewport. */
.pop-time { width: min(var(--w-pop-sm), calc(100vw - 44px)); }

/* Two halves of a single track, the same shape as the status segmented
   control, so it reads as "pick one" rather than two buttons. The track is
   a .seg, which hugs its labels (align-self: flex-start) in the popover's
   flex column; with the unpadded .tp-ap-btn that squashed it to the width
   of "AMPM". It spans the popover instead, as two equal halves. Covers the
   booking popover (.tp-half) and the inline rate picker (ui.js). */
.pop-time > .seg, .tp-half { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; align-self: stretch; width: auto; }
.tp-ap-btn {
  min-height: var(--seg-h); padding: 0; cursor: pointer;
  font-family: var(--font-body); font-size: var(--fs-sm); font-weight: 600;
  color: var(--ink-2);
  background: none; border: 0; border-radius: var(--r-pill);
  transition: background-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.tp-ap-btn:hover { color: var(--ink); }
.tp-ap-btn.is-on { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }

/* Four across, twelve total: a clock face flattened into a keypad. */
.tp-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; }
.tp-h {
  aspect-ratio: 1 / .82; min-height: 38px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-body); font-size: 14.5px; font-weight: 600;
  font-variant-numeric: tabular-nums; color: var(--ink);
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--r-inner);
  transition: background-color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.tp-h:hover:not(:disabled) { background: color-mix(in srgb, var(--ink) 7%, var(--surface-2)); }
.tp-h.is-picked { background: var(--ink); color: var(--surface); }
/* 12am–4am are not slots anyone books here. Shown rather than removed, so
   the keypad keeps the shape of a clock face instead of growing holes. */
.tp-h:disabled { background: none; color: var(--ink-3); opacity: .38; cursor: not-allowed; }

.tp-legend {
  margin: 12px 0 6px;
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2);
}
.tp-mins { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; }
.tp-m {
  padding: 9px 0; cursor: pointer;
  font-size: 12.5px; font-weight: 600; color: var(--ink-2);
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--r-inner);
  transition: background-color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.tp-m:hover { color: var(--ink); background: color-mix(in srgb, var(--ink) 7%, var(--surface-2)); }
.tp-m.is-picked { background: var(--ink); color: var(--surface); }

/* ── court picker ───────────────────────────────────────
   Wider than the other two popovers because these rows carry a name, a
   barangay and a rate. The list is the only part that scrolls, and the
   search box and city chips stay pinned above it. */
.pop-court { width: min(var(--w-pop-lg), calc(100vw - 44px)); }
/* Anchored (≥1280) it takes the Court field's own width and left edge, so it
   lines up inside the form card instead of spanning past its right edge. */
@supports (width: anchor-size(width)) {
  @media (min-width: 1280px) {
    #bCourtPop[popover] { width: anchor-size(width); min-width: 0; max-width: none; }
  }
}
.cp-search { margin-bottom: 10px; }
.cp-search input[type="search"] { box-shadow: none; font-size: 14px; padding-block: 9px; }

/* Four cities: they wrap rather than scroll, so every option is visible. */
.cp-cities { display: flex; flex-wrap: wrap; gap: 6px; }
.city-pick { flex: none; } /* chip box comes from §3 */
.city-pick span { font-size: 11px; color: var(--ink-3); }
.city-pick.is-on span { color: var(--surface); opacity: .7; }

.cp-list {
  max-height: min(44vh, 320px); overflow-y: auto; overscroll-behavior: contain;
  margin: 10px -4px 0; padding: 0 4px;
}
/* Sticky, so you always know which city you are scrolling through — the
   whole point of clustering rather than one A–Z run. */
.cp-group {
  position: sticky; top: 0; z-index: 1; margin: 0; padding: 7px 4px 5px;
  background: var(--surface);
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2);
}
.cp-item {
  width: 100%; text-align: left; cursor: pointer;
  display: flex; flex-direction: column; gap: 2px;
  padding: 9px 10px; margin-bottom: 2px;
  background: none; border: 1px solid transparent; border-radius: var(--r-inner);
  transition: background-color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.cp-item:hover { background: var(--surface-2); }
.cp-item.is-picked { background: var(--lime-tint); }
.cp-name {
  font-family: var(--font-body); font-size: 13.5px; font-weight: 600; color: var(--ink);
  line-height: 1.3;
}
.cp-star { color: var(--danger); font-size: 11px; margin-left: 5px; }
.cp-meta { font-size: 11px; color: var(--ink-3); }

.picker-quick { display: flex; gap: 6px; margin-top: 12px; flex-wrap: wrap; }
.picker-quick .chip { flex: 1; }

@media (max-width: 767px) {
  /* Full-width and centred under the field — 78vw next to a half-width
     field would hang off the screen. */
  .overlay--pop {
    width: calc(100vw - 44px);
    left: 0; right: auto;
  }
  .field-grid > .field-row:last-child .overlay--pop { left: auto; right: 0; }
  /* In the top layer a popover is the bottom sheet: full width, not the
     fallback box's 100vw - 44px pinned to one side. */
  .overlay--pop[popover], .field-grid > .field-row:last-child .overlay--pop[popover] { width: 100%; left: 0; right: 0; }
  .picker-btn { min-height: 46px; }
  /* Thumb-sized targets: the hour keypad is the one control here you tap
     twelve different places on. */
  .tp-h { min-height: 44px; }
  .tp-m { min-height: var(--tap); }
  .cp-item { padding: 11px 10px; }
}

/* ── bookings calendar ──────────────────────────────────
   The same bookings arranged by date. A list answers "what is next";
   a month grid answers "how busy was this month, and where are the
   gaps" — which is the question you actually have when booking. */
.cal-head { display: flex; align-items: center; gap: 8px; margin: 4px 0 14px; }
.cal-month {
  margin: 0; flex: 1; text-align: center;
  font-size: 15px; font-weight: 700; letter-spacing: -0.01em;
}
.cal-today { flex: none; }

.cal-dow, .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal-dow {
  margin-bottom: 6px;
  font-size: var(--fs-xs); font-weight: 600; color: var(--ink-2); text-align: center;
}

.cal-cell {
  aspect-ratio: 1; min-height: 42px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  background: none; border: 1px solid transparent; border-radius: var(--r-inner);
  font-family: inherit; color: var(--ink-3); cursor: default;
  transition: background-color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.cal-cell.is-blank { border: 0; }
.cal-num { font-size: 13px; font-variant-numeric: tabular-nums; }

/* Only days with something on them are interactive — an empty cell that
   highlights on hover promises something it cannot deliver. */
.cal-cell.has-bk { color: var(--ink); background: var(--surface-2); cursor: pointer; }
.cal-cell.has-bk:hover { background: color-mix(in srgb, var(--ink) 7%, var(--surface-2)); }
.cal-cell.is-today { font-weight: 700; background: var(--surface-2); }
.cal-cell.is-picked { background: var(--ink); color: var(--surface); }
.cal-cell.is-picked .cal-dot { background: var(--ground); }

.cal-dots { display: flex; gap: 3px; min-height: 5px; }
.cal-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--danger); }
/* Cancelled and no-shows still happened, so they show — hollow, because
   they cost nothing and count toward nothing. */
.cal-dot.is-off { background: none; box-shadow: inset 0 0 0 1px var(--ink-3); }

.cal-total {
  margin: 14px 0 0; text-align: center;
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2);
}
.cal-day-head {
  margin: 18px 0 10px; font-size: 14px; font-weight: 700; color: var(--ink);
}

.list-tools { display: flex; align-items: center; gap: 8px; }

@media (max-width: 767px) {
  .cal-cell { min-height: 38px; }
  .cal-grid, .cal-dow { gap: 3px; }
  
}

/* ══ §7 Scorer ══════════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════════════
   MATCH SCORING
   Two surfaces with opposite jobs. The SCORER is held at arm's
   length and tapped between rallies, so it is sized for a
   thumb and nothing else competes with the two rally buttons.
   The BOARD is read from the far baseline of a court — call it
   10 metres — so its type scales with the viewport rather than
   sitting at a fixed px size that only suits one iPad.
   ═══════════════════════════════════════════════════════════ */

/* A quiet dot, not a count: "a match is running" is the whole
   message, and a number here would invite reading it as a score. */
.tab-live { color: var(--hi-strong); font-size: 9px; line-height: 1; }

.match-setup {
  position: static;            /* not sticky: it is the whole panel here */
  margin: 0 0 8px;
}
.field-note { font-size: 12px; color: var(--ink-3); margin: -4px 0 2px; }

/* ── the set-up court ───────────────────────────────────
   Names are typed on a court instead of into four unlabelled
   fields. It costs no more space and carries information the
   list could not: who starts on which side, and therefore who
   serves first — the two right-hand boxes being diagonal is a
   thing you learn by seeing it once.

   The inputs are placed by grid-area, so DOM order stays
   A1 → A2 → B1 → B2 for tabbing while the picture matches the
   court and the scoreboard's own left/right convention. */
.setup-court {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-areas:
    "aLeft  bRight"
    "aRight bLeft";
  gap: 6px;
  padding: 10px 10px 26px;
  border: 1.5px solid var(--line-2);
  border-radius: var(--r-inner);
  background: var(--surface-2);
}
.sc-a-right { grid-area: aRight; }
.sc-a-left  { grid-area: aLeft; }
.sc-b-right { grid-area: bRight; }
.sc-b-left  { grid-area: bLeft; }

.sc-cell { display: flex; }
.sc-cell input {
  width: 100%; min-height: 52px;
  padding: 8px 10px; text-align: center;
  font-family: inherit; font-size: 14px; font-weight: 600; color: var(--ink);
  border: 0; border-radius: var(--r-inner); background: var(--surface-2); }
.sc-cell input::placeholder { font-weight: 500; color: var(--ink-3); }

.sc-a-right::after,
.sc-b-right::after {
  content: ""; position: absolute; width: 6px; height: 6px; border-radius: 50%;
  background: var(--ink-3); margin: 7px 0 0 7px;
}

/* The net, drawn between the two halves. */
.sc-net {
  position: absolute; top: 6px; bottom: 22px; left: 50%;
  width: 0; border-left: 2px dashed var(--ink-3); opacity: .5;
}
.sc-side-a, .sc-side-b {
  position: absolute; bottom: 7px;
  font-size: var(--fs-xs); font-weight: 600; color: var(--ink-2);
}
.sc-side-a { left: 12px; }
.sc-side-b { right: 12px; }

/* Singles: one box a side, and the partner rows collapse rather than
   leaving two empty holes in the court. */
.setup-court.is-singles {
  grid-template-areas: "aRight bRight";
}

/* First serve, placed under the half it refers to so the choice is
   spatial. Same hidden-radio pattern as .seg. */
.serve-seg { display: grid; grid-template-columns: 1fr auto 1fr; gap: 4px; }

/* Buttons carrying an icon alongside their label. */
.with-ico { display: inline-flex; align-items: center; gap: 7px; }
.with-ico svg { width: 14px; height: 14px; flex: none; }
#mCopyBoard.is-done { color: var(--good); }

.form-actions.stack { flex-direction: column; align-items: stretch; }
.form-actions.stack .btn, .form-actions.stack .btn--quiet { justify-content: center; }
.dlg-hint { font-size: 12px; color: var(--ink-3); margin: 4px 0 6px; }

/* ── the scorer ────────────────────────────────────────── */

.scorer { min-width: 0; }

/* Clears the sticky masthead. The other two tabs get this from their
   stat strip; the scorer leads with the call score and would otherwise
   tuck the one number that matters under the header. */
#view-match { padding-top: 22px; }

.scorer-head { text-align: center; margin-bottom: 18px; }
/* Win/loss Dink (96px) over a finished match: the scorer's header and the
   game page's result card. Only shown to someone who played. */
.scorer-mascot, .result-mascot { display: flex; justify-content: center; line-height: 0; }
.scorer-mascot[hidden] { display: none; }

/* The called score is the artefact players actually speak aloud
   ("nine, eight, two"), so it leads — the per-side numbers below
   are the reference, not the headline. */
.scorer-call {
  
  font-size: clamp(40px, 12vw, 58px); font-weight: 700;
  letter-spacing: -0.02em; line-height: 1; margin: 0;
  font-variant-numeric: tabular-nums;
}
.scorer-note { font-size: 13px; color: var(--ink-2); margin: 6px 0 0; }
.scorer-serve { font-size: 13px; color: var(--ink-3); margin: 2px 0 0; }
.scorer-switch {
  display: inline-block; margin: 10px 0 0;
  font-size: var(--fs-sm); font-weight: 600;
  background: var(--danger-wash); color: var(--danger);
  padding: 5px 12px; border-radius: var(--r-pill);
}

/* Sync state. Scoring is local-first, so the score on this phone can be
   ahead of the board courtside — that gap is the only thing this feature
   has to be candid about, and it gets a line rather than a silent icon. */
.scorer-sync {
  display: inline-block; margin: 8px 0 0;
  font-size: var(--fs-xs); font-weight: 600;
  color: var(--ink-3);
}
.scorer-sync.ok { color: var(--good); }
.scorer-sync.warn {
  color: var(--danger);
  background: var(--danger-wash); padding: 3px 10px; border-radius: var(--r-pill);
}

/* "Play for serve" is words, not a score; at the call's size it would
   wrap on a phone. */
.scorer-call.is-text { font-size: clamp(30px, 9vw, 40px); }

/* ── where everyone stands ──────────────────────────────
   The set-up court again, read-only and compact: same grid areas, so
   a pair sees the same picture they typed names into. The server's
   box is filled, the receiver's outlined — diagonal from each other,
   which is the thing people get wrong. */
.pos-court {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-areas:
    "aLeft  bRight"
    "aRight bLeft";
  gap: 4px 14px;
  padding: 6px;
  margin: 0 0 8px;
  border: 1.5px solid var(--line-2);
  border-radius: var(--r-inner);
  background: var(--surface-2);
}
.pos-court[hidden] { display: none; }
.pos-a-left  { grid-area: aLeft; }
.pos-a-right { grid-area: aRight; }
.pos-b-right { grid-area: bRight; }
.pos-b-left  { grid-area: bLeft; }
.pos-cell {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 46px; padding: 4px 8px; text-align: center;
  border-radius: calc(var(--r-inner) - 2px);
}
.pos-name {
  font-size: 14px; font-weight: 600; color: var(--ink);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pos-role { font-size: 11px; font-weight: 600; color: var(--ink-2); }
.pos-cell.is-server { background: var(--lime-tint); box-shadow: inset 0 0 0 1px var(--ink); }
.pos-cell.is-server .pos-name, .pos-cell.is-server .pos-role { color: var(--on-lime-tint); }
.pos-cell.is-receiver { box-shadow: inset 0 0 0 1.5px var(--ink-3); }
.pos-cell.is-first .pos-role { color: var(--ink-3); }
.pos-net {
  position: absolute; top: 6px; bottom: 6px; left: 50%;
  width: 0; border-left: 2px dashed var(--ink-3); opacity: .5;
}
.pos-swaps { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 0 0 4px; }
.pos-swaps[hidden] { display: none; }
.pos-swap { justify-content: center; }
.pos-note { font-size: 12px; color: var(--ink-3); text-align: center; margin: 2px 0 12px; }
.pos-note[hidden] { display: none; }

.scorer-prompt {
  text-align: center; font-size: var(--fs-sm); font-weight: 600;
  color: var(--ink-2); margin: 0 0 10px;
}

.scorer-teams { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* Deliberately huge. This is pressed with one thumb, often mid-
   conversation, sometimes in sunlight — a 44px target would be
   the wrong answer even though it passes the guideline. */
.rally-btn {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; min-height: 168px; padding: 20px 12px; color: var(--ink);
  border: 0; border-radius: var(--r-card);
  cursor: pointer; font-family: inherit;
  transition: border-color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out); background: var(--surface-2); }
.rally-btn:hover { background: color-mix(in srgb, var(--ink) 7%, var(--surface-2)); }
.rally-btn:active { transform: scale(.985); }
.rally-btn:disabled { opacity: .55; cursor: default; }

/* Serve is shown as a filled side rather than a small icon: which
   side is serving has to be legible at a glance from a pocket. */
.rally-btn.is-serving {
  box-shadow: inset 0 0 0 1px var(--ink); background: var(--lime-tint); }

/* The tap is optimistic-feeling but the server is authoritative, so
   the pending state dims rather than pre-drawing a score that might
   come back refused. */
.rally-btn.is-pending { opacity: .6; }

.rally-name {
  font-size: 14px; font-weight: 600; color: var(--ink-2);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rally-score {
  font-variant-numeric: tabular-nums;
  font-size: clamp(52px, 16vw, 72px); font-weight: 700; line-height: 1.05;
  letter-spacing: -0.03em;
}
.rally-games { font-size: 11.5px; color: var(--ink-3); min-height: 16px; }
.rally-tag {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  font-size: var(--fs-xs); font-weight: 700;
  background: var(--accent); color: var(--accent-ink);
  padding: 3px 9px; border-radius: var(--r-pill); white-space: nowrap;
  max-width: calc(100% - 8px); overflow: hidden; text-overflow: ellipsis;
}

.scorer-games { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin-top: 14px; }
.tag b { color: var(--ink); }

.scorer-acts {
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
  margin-top: 18px;
}


#mBoardUrl {
  font-size: 11.5px;
  width: 100%; padding: 10px 12px;
  border: 0; border-radius: var(--r-inner); color: var(--ink-2); background: var(--surface-2); }

@media (max-width: 767px) {
  .rally-btn { min-height: 200px; }   .scorer-acts .btn, .scorer-acts .btn--quiet { flex: 1 1 45%; }
}

/* ══ §8 iPad board ══════════════════════════════════════════ */
/* ═══════════════ the courtside board ═══════════════════

   Read from the far baseline of a court — call it 10 metres. Every
   size here is viewport-relative for that reason: a fixed px scale
   suits exactly one iPad and no other screen.

   The two point totals get the whole middle of the screen. Anything
   that is not a point total lives in the header or the footer, so
   nothing competes with the number people are squinting at.
   ═══════════════════════════════════════════════════════ */

/* Its own page, so it owns the viewport outright: no masthead, no
   scrolling, nothing to tap by accident when someone picks the
   iPad up to move it. */
.board-body {
  margin: 0; height: 100svh; overflow: hidden;
  background: var(--ground); color: var(--ink);
}
.board {
  height: 100svh; position: relative;
  display: flex; flex-direction: column;
  padding: max(16px, env(safe-area-inset-top)) max(20px, env(safe-area-inset-right))
           max(14px, env(safe-area-inset-bottom)) max(20px, env(safe-area-inset-left));
}

.board-idle { margin: auto; text-align: center; }
.board-idle-title { font-size: clamp(24px, 5vw, 40px); font-weight: 700; margin: 0; }
.board-idle-sub { font-size: clamp(14px, 2vw, 19px); color: var(--ink-3); margin: 10px 0 0; }

.board-live { flex: 1; display: flex; flex-direction: column; min-height: 0; gap: clamp(6px, 1.4vh, 18px); }

.board-head { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
.board-meta {
  font-size: clamp(11px, 1.5vw, 17px); font-weight: 600;
  color: var(--ink-3); text-transform: uppercase; letter-spacing: .09em;
}
.board-call {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: clamp(16px, 2.6vw, 34px); font-weight: 700;
  color: var(--ink-2);
}
.board-call.is-final-text {
  font-family: var(--font-body); font-weight: 700;
  letter-spacing: .12em; color: var(--good);
}

.board-sides {
  flex: 1; min-height: 0;
  display: grid; grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: clamp(8px, 2vw, 36px);
}

.board-side {
  min-width: 0; align-self: center; justify-self: center;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(2px, .6vh, 10px);
  padding: clamp(10px, 2vh, 26px) clamp(10px, 2vw, 28px);
  border-radius: clamp(12px, 2vw, 26px);
  border: 2px solid transparent;
  transition: border-color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out);
}
/* Serving side is drawn with a border and a live dot rather than a
   colour fill: the score has to stay maximum-contrast, and a filled
   panel would cost the digits their legibility from the baseline. */
.board-side.is-serving { background: var(--surface-2); box-shadow: inset 0 0 0 3px var(--hi-strong); }
.board-side.is-winner  { background: var(--good-wash); box-shadow: inset 0 0 0 3px var(--good); }

.board-team {
  display: flex; align-items: center; gap: .4em; margin: 0;
  font-size: clamp(13px, 2.2vw, 28px); font-weight: 700; letter-spacing: -0.01em;
  max-width: 100%; padding: 0 10px;
}
.board-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-serve {
  width: .42em; height: .42em; border-radius: 50%;
  background: var(--hi-strong); opacity: 0; flex: none;
}
.board-side.is-serving .board-serve { opacity: 1; }

/* ── the score dial ─────────────────────────────────────
   The ring is progress toward the target, so a glance answers "how
   far along is this game" without reading either number. Sizing is
   driven by the dial's own font-size, which lets the digits inside
   be expressed in em and stay centred at every viewport. */
.score-dial {
  position: relative;
  font-size: clamp(56px, min(22vw, 28vh), 280px);
  width: 2.05em; max-width: 100%;
  aspect-ratio: 1;
}
.score-dial .ring { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.ring circle { fill: none; stroke-width: 3.4; }
.ring-track { stroke: var(--line); }
.ring-fill {
  stroke: var(--ink-3);   /* linecap is set per-render: see scoreboard.js */
  transform: rotate(-90deg); transform-origin: 50% 50%;   /* start at 12 o'clock */
  transition: stroke-dasharray .45s cubic-bezier(.2, .8, .3, 1);
}
.board-side.is-serving .ring-fill { stroke: var(--hi-strong); }
.board-side.is-winner  .ring-fill { stroke: var(--good); }

/* Monospaced with tabular figures on purpose: every digit occupies
   the same width, so 9 and 11 sit on the same optical centre and the
   number does not shift sideways as the score climbs. That stability
   is the whole reason a scoreboard uses a readout face. */
.board-points {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 1.32em; font-weight: 700;
  letter-spacing: -0.04em; line-height: 1;
}

.board-towin {
  margin: 0; min-height: 1.3em;
  font-size: clamp(10px, 1.4vw, 16px); font-weight: 600;
  text-transform: uppercase; letter-spacing: .12em; color: var(--ink-3);
}
.board-games { font-size: clamp(12px, 1.9vw, 24px); color: var(--ink-3); letter-spacing: .2em; margin: 0; min-height: 1.2em; }
.board-tag {
  font-size: clamp(10px, 1.4vw, 17px); font-weight: 700;
  letter-spacing: .1em; color: var(--hi-strong); margin: 0; min-height: 1.3em;
}
.board-side.is-winner .board-tag,
.board-side.is-winner .board-games { color: var(--good); }

/* ── the serve cue ──────────────────────────────────────
   A whole court diagram outsized the score it was meant to
   annotate. This is the same information at a glyph's scale: four
   service boxes with the one being served from lit up.

   Only one box lights. An earlier version also washed the
   receiving box to imply the diagonal, but two fills at different
   opacities are indistinguishable at ten metres — it just read as
   a checkerboard. One lit box is unambiguous, which is the only
   thing that matters at this size. */
.serve-strip { display: flex; align-items: center; gap: .7em; justify-self: center; }
.court-mini { width: clamp(48px, 6vw, 88px); height: auto; flex: none; display: block; }

/* Lines sit above the fill and have to stay legible against it, so
   they take an ink tone rather than the usual hairline grey. */
.cm-line { stroke: var(--ink-3); stroke-width: 1.6; fill: none; opacity: .55; }
.cm-out  { stroke-width: 2.2; opacity: .75; }
.cm-net  { stroke: var(--ink-2); stroke-width: 2.4; }

.cm-quad { rx: 1.5; }
.cm-serve { fill: var(--danger); filter: drop-shadow(0 0 3px var(--danger)); }

.board-server {
  font-size: clamp(10px, 1.35vw, 16px); font-weight: 600;
  color: var(--ink-2); text-transform: uppercase; letter-spacing: .08em;
  white-space: nowrap;
}

/* Three tracks so the serve cue is centred on the board, not on
   whatever happens to be left of it. */
.board-foot {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px;
  font-size: clamp(11px, 1.5vw, 18px); color: var(--ink-2); min-height: 2em;
}
.board-games-log { display: flex; gap: 8px; flex-wrap: wrap; }
.board-game-pill {
  font-family: var(--font-mono);
  background: var(--surface-2); color: var(--ink-2);
  padding: .2em .7em; border-radius: var(--r-pill);
}
.board-switch {
  justify-self: end;
  font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  background: var(--accent); color: var(--accent-ink); box-shadow: inset 0 0 0 1px var(--accent-edge);
  padding: .3em 1em; border-radius: var(--r-pill);
}

.board-conn {
  position: absolute; bottom: 4px; left: 50%; transform: translateX(-50%);
  margin: 0; font-size: 11px; color: var(--ink-3);
}

/* The only motion on the board. A point landing is the one event
   worth animating, and a short pulse reads as "that counted" from
   across the court where a number changing by one does not. */
@keyframes board-bump {
  0%   { transform: scale(1); }
  38%  { transform: scale(1.045); }
  100% { transform: scale(1); }
}
.board-side.is-bumped .score-dial { animation: board-bump .42s cubic-bezier(.3, 1.4, .5, 1); }


/* ═══════════════ board controls, mirror, winner overlay ═══════════════ */

/* ── controls ───────────────────────────────────────────
   Invisible until the board is tapped, then gone again after a few
   seconds. visibility (not just opacity) so a hidden button can
   neither be tabbed to nor catch a stray tap meant for the board. */
.board-ctl {
  position: fixed; z-index: 30;
  top: max(10px, env(safe-area-inset-top)); right: max(12px, env(safe-area-inset-right));
  display: flex; gap: 8px;
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur-2) var(--ease-out), visibility 0s linear var(--dur-2);
}
.board-ctl.is-shown { opacity: 1; visibility: visible; transition-delay: 0s; }
.ctl-btn {
  display: inline-flex; align-items: center; gap: .5em;
  min-height: 44px; padding: 0 14px;
  font: inherit; font-size: 14px; font-weight: 700; color: var(--ink); border: 0; border-radius: var(--r-pill);
  box-shadow: var(--shadow-md); cursor: pointer; background: var(--surface-2); }
.ctl-btn svg { width: 20px; height: 20px; flex: none; }
.ctl-btn[aria-pressed="true"] { background: var(--lime); color: var(--on-lime); }
.ctl-btn:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }

/* ── mirror ─────────────────────────────────────────────
   Swapping sides is a ROTATION of the serve glyph, not a scaleX flip.
   Each team's "left"/"right" service box is relative to its own facing
   (see QUAD in scoreboard.js). A team that walks to the other half
   now faces the opposite way, so its left box lands on the opposite
   screen edge AND the opposite top/bottom half. A horizontal flip
   would move the half but keep top/bottom, lighting the wrong box;
   a 180deg turn moves both, and needs no change to the geometry. */
.is-mirrored #boardSide0 { order: 2; }
.is-mirrored #boardSide1 { order: 1; }
.is-mirrored .court-mini { transform: rotate(180deg); }

/* ── winner overlay ─────────────────────────────────────
   Opaque enough to be the only thing read, but not a solid fill:
   the score behind it stays faintly visible so the moment is
   still "this match". */
.board-win {
  position: fixed; inset: 0; z-index: 40;
  display: flex; align-items: center; justify-content: center;
  padding: max(20px, env(safe-area-inset-top)) max(20px, env(safe-area-inset-right))
           max(20px, env(safe-area-inset-bottom)) max(20px, env(safe-area-inset-left));
  background: color-mix(in srgb, var(--ground) 90%, transparent);
  cursor: pointer; text-align: center;
}
.win-confetti { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.win-card { position: relative; max-width: min(94vw, var(--w-sheet)); }
.win-mascot { display: block; margin: 0 auto .1em; }
/* The five friends at 72px under Dink, only ever on this overlay. */
.win-crew { display: flex; justify-content: center; flex-wrap: wrap; gap: 4px; margin: -.2em auto .3em; line-height: 0; }
.win-crew[hidden] { display: none; }
.win-title {
  margin: 0; font-weight: 700; letter-spacing: .22em; color: var(--good);
  font-size: clamp(20px, 4vw, 54px);
}
.win-names {
  margin: .25em 0 0; font-weight: 700; letter-spacing: -0.02em; line-height: 1.05;
  font-size: clamp(34px, min(9vw, 14vh), 150px); overflow-wrap: anywhere;
}
.win-line {
  margin: .5em 0 0; font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-weight: 700; color: var(--ink-2); font-size: clamp(18px, 3.4vw, 46px);
}
@keyframes win-in {
  from { opacity: 0; transform: scale(.94); }
  to   { opacity: 1; transform: scale(1); }
}
.board-win.is-in .win-card { animation: win-in .45s cubic-bezier(.2, .9, .3, 1.2); }

/* ═══════════════ open play (board) ═══════════════ */

/* Same rule as the match board: sizes follow the viewport so the
   courts read from the baseline. Everything is clipped, never
   scrolled: a board that scrolls is a board nobody can touch. */
.board-open {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  gap: clamp(8px, 1.6vh, 20px);
  font-size: clamp(12px, min(1.7vw, 2.6vh), 26px);
}
.bo-head { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.bo-title {
  font-size: 1.3em; font-weight: 700; letter-spacing: -0.01em;
}
.bo-stats {
  font-size: .85em; font-weight: 600; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: .09em;
}
.bo-main {
  flex: 1; min-height: 0; display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: clamp(10px, 2vw, 28px);
}
.bo-courts {
  --bo-cols: 2;
  min-height: 0; overflow: hidden; align-content: stretch;
  display: grid; grid-template-columns: repeat(var(--bo-cols), minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  gap: clamp(8px, 1.4vw, 18px);
}
.bo-court {
  min-width: 0; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column; gap: .35em; justify-content: space-between;
  padding: .7em .9em; border-radius: clamp(12px, 1.6vw, 22px);
  background: var(--surface-2); border: 0;
  box-shadow: var(--sh-card);
}
.bo-court-top { display: flex; justify-content: space-between; align-items: center; gap: .5em; }
.bo-court-no { font-weight: 700; font-size: 1.15em; }
.bo-status {
  padding: .15em .75em; border-radius: var(--r-pill);
  font-size: .8em; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  background: var(--line); color: var(--ink-2);
}
.bo-court.is-playing { box-shadow: inset 0 0 0 2px var(--good), var(--sh-card); }
.bo-court.is-playing .bo-status { background: var(--hi); color: var(--hi-ink); }
.bo-court.is-called  { background: var(--hi-wash); box-shadow: inset 0 0 0 2px var(--hi-strong), var(--sh-card); }
.bo-court.is-called  .bo-status { background: var(--accent); color: var(--accent-ink); }
.bo-court.is-free { opacity: .8; }

.bo-teams { display: flex; flex-direction: column; align-items: center; text-align: center; gap: .05em; min-height: 0; }
/* Names grow as courts get fewer: with two courts each card is half the
   board, and 20px names in a half-screen card are unreadable from the far
   baseline. --bo-cols is the grid's own column count. */
.bo-team { margin: 0; max-width: 100%; font-weight: 700; line-height: 1.15; overflow-wrap: anywhere;
  font-size: clamp(1.2em, calc(4.2em / var(--bo-cols, 2)), 2.2em); }
.bo-vs { font-size: .75em; font-weight: 700; color: var(--ink-3); text-transform: uppercase; letter-spacing: .14em; }
.bo-timer {
  margin: 0; text-align: center; font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: 1.5em; color: var(--ink-2);
}
.bo-timer.is-call { font-family: var(--font-body); font-size: 1em; letter-spacing: .06em; text-transform: uppercase; color: var(--hi-strong); }
.bo-court-free { margin: auto 0; text-align: center; font-weight: 600; color: var(--ink-3); }

/* Called is the one moment worth motion: players are being summoned.
   A two-beat ring pulse is visible across a hall without shouting. */
@keyframes bo-pulse {
  0%   { box-shadow: 0 0 0 0 var(--hi-strong); transform: scale(1); }
  35%  { box-shadow: 0 0 0 .5em transparent; transform: scale(1.03); }
  70%  { box-shadow: 0 0 0 0 var(--hi-strong); transform: scale(1); }
  100% { box-shadow: 0 0 0 .5em transparent; transform: scale(1); }
}
.bo-court.is-called-now { animation: bo-pulse 1.6s ease-out 2; }

.bo-side { min-height: 0; display: flex; flex-direction: column; gap: clamp(8px, 1.4vw, 18px); }
.bo-panel {
  min-height: 0; overflow: hidden; padding: .7em .9em;
  border-radius: clamp(12px, 1.6vw, 22px); border: 0; background: var(--surface);
  box-shadow: var(--sh-card);
}
.bo-next { flex: 1.5; }
.bo-lb   { flex: 1; }
.bo-h {
  margin: 0 0 .4em; font-size: .8em; font-weight: 700;
  text-transform: uppercase; letter-spacing: .14em; color: var(--ink-3);
}
.bo-list, .bo-lbl { list-style: none; margin: 0; padding: 0; }
.bo-next-row, .bo-lb-row {
  display: grid; align-items: baseline; gap: .6em;
  padding: .22em 0;
}
.bo-next-row:last-child, .bo-lb-row:last-child { border-bottom: 0; }
.bo-next-row { grid-template-columns: 1.4em minmax(0, 1fr) auto; font-size: 1.3em; font-weight: 700; }
.bo-lb-row   { grid-template-columns: 1.6em minmax(0, 1fr) auto; font-size: 1em; font-weight: 600; }
.bo-rank { color: var(--ink-3); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.bo-nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bo-nm { display: flex; align-items: center; gap: .45em; min-width: 0; }
.bo-next-row, .bo-lb-row { align-items: center; }
.bo-nm-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.bo-av {
  position: relative; flex: none; width: 1.6em; height: 1.6em; border-radius: 50%; overflow: hidden;
  display: inline-grid; place-items: center; font-weight: 700; line-height: 1;
  color: var(--ink); background: var(--surface-2); border: 0;
}
.bo-av:not([data-char]) { font-size: .62em; width: 2.58em; height: 2.58em; }
.bo-av[data-char] { background: var(--av-c); border-color: transparent; }
.bo-av svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.bo-eta, .bo-wl {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--ink-2);
}
.bo-eta { font-size: .75em; font-weight: 600; }
.bo-empty { color: var(--ink-3); font-weight: 600; list-style: none; }
.bo-break {
  margin: 0; font-size: .95em; font-weight: 600; color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Portrait or a narrow window: one column, courts above the lists. */
@media (max-aspect-ratio: 1/1) {
  .bo-main { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1.3fr) minmax(0, 1fr); }
  .bo-courts { --bo-cols: 2 !important; }
  .bo-side { flex-direction: row; }
  .bo-side .bo-panel { flex: 1; }
}

/* ══ §9 Open Play on a phone ════════════════════════════════ */
/* ═══════════════ open play (phone) ═══════════════ */

/* Control bar: live state left, Undo + Sound right; wraps under on narrow phones. */
.op-bar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2) var(--sp-3); flex-wrap: wrap; }
.op-bar-acts { display: flex; align-items: center; gap: var(--sp-2); flex: none; }
.op-bar-acts .with-ico svg { width: 16px; height: 16px; }
.op-top { display: flex; flex-direction: column; gap: 2px; flex: 1 1 120px; min-width: 0; }
.op-summary { margin: 0; font-size: 13px; font-weight: 600; color: var(--ink-2); }
.op-top .scorer-sync { margin: 0; align-self: flex-start; }
.op-head { display: flex; justify-content: space-between; align-items: center; }
.op-sub { font-size: 12.5px; font-weight: 700; color: var(--ink-2); margin: 16px 0 8px; }

/* steppers */
.op-step { display: inline-flex; align-items: center; gap: 4px; background: var(--surface-2); padding: 4px; border-radius: var(--r-pill); align-self: flex-start; }
.step-btn {
  background: var(--surface); color: var(--ink); font-size: 20px; line-height: 1; font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.op-step-val { min-width: 44px; text-align: center; font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; }
.op-modes, .match-setup .seg { min-width: 0; }
/* "How games run": four named options. On a phone they sit two by two so
   every label stays on one line (a pill track can't hold four of them). */
.seg.run-seg label { white-space: nowrap; font-size: var(--fs-sm); padding: 0 8px; }
@media (max-width: 560px) {
  
  .seg.run-seg label { min-height: var(--seg-h); border-radius: calc(var(--r-inner) - 3px); }
}
#opAwayKeep + label, #opAwayBack + label, #opCfgAwayKeep + label, #opCfgAwayBack + label { font-size: 12px; }

/* courts */
.op-courts { display: grid; gap: 10px; }
.op-court { border: 0; border-radius: var(--r-inner); padding: 12px; background: var(--surface); box-shadow: var(--sh-card); }
.op-court.is-called { background: var(--hi-wash); }
.op-court.is-playing { background: var(--good-wash); }
.op-court-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.op-court-head h3 { margin: 0; font-size: 14.5px; font-weight: 700; flex: 1; }
.op-time { font-size: 13px; font-variant-numeric: tabular-nums; color: var(--ink-2); }
.op-hint { margin: 0; font-size: 13px; color: var(--ink-3); }
.op-teams { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; margin-bottom: 10px; }
.op-team { margin: 0; font-size: 14px; font-weight: 600; overflow-wrap: anywhere; }
.op-team:last-child { text-align: right; }
.op-vs { font-size: var(--fs-xs); color: var(--ink-3); }
.op-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* queue rows */
.op-q { display: flex; align-items: center; gap: 10px; padding: 9px 0; }
.op-q:first-child { border-top: 0; }
.op-q-no { flex: none; width: 26px; height: 26px; border-radius: 50%; background: var(--surface-2); color: var(--ink-2); font-size: 12px; font-weight: 700; display: grid; place-items: center; }
.op-q-body { flex: 1; min-width: 0; }
.op-q-name { margin: 0; font-size: 14.5px; font-weight: 600; overflow-wrap: anywhere; }
.op-more { font-size: 20px; line-height: 1; min-width: 44px; min-height: 40px; text-decoration: none; }

/* check-in */
.op-add { display: flex; gap: 8px; }
.op-add input { flex: 1; min-width: 0; }
.op-levels { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.op-picks { display: flex; flex-wrap: wrap; gap: 6px; }
.op-letters { margin-bottom: 8px; }
.op-letter { padding: 6px 0; min-width: 34px; min-height: 44px; font-size: 12.5px; }
#opPickChips .op-picks + .op-picks { margin-top: 4px; }

/* leaderboard */
.op-lb { display: flex; flex-direction: column; }
.op-lb-row {
  display: grid; grid-template-columns: 26px minmax(0, 1fr) 40px 40px 34px; gap: 6px; align-items: center;
  font: inherit; font-size: 12.5px; color: var(--ink); text-align: left;
  background: none; border: 0; padding: 9px 0; width: 100%; min-height: 44px;
  font-variant-numeric: tabular-nums;
}
button.op-lb-row { cursor: pointer; }
button.op-lb-row:hover { background: var(--surface-2); }
.op-lb-head { border-top: 0; font-size: var(--fs-xs); font-weight: 600; color: var(--ink-2); padding-top: 0; }
.op-lb-name { font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* session actions */
.op-acts-grid { display: flex; flex-wrap: wrap; gap: 4px 6px; margin-bottom: 10px; }

/* dialogs */
.op-sides { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.op-side {
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer; color: var(--ink); border: 0; border-radius: var(--r-inner);
  padding: 14px 8px; min-height: 64px; overflow-wrap: anywhere;
  transition: border-color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out); background: var(--surface-2); }
.op-side[aria-pressed="true"] { background: var(--good-wash); color: var(--good); }
.op-score { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
#opResultDlg .form-actions { flex-wrap: wrap; }
.op-menu-btn { text-align: left; padding: 12px 10px; font-size: 14px; }
.op-stats { display: grid; grid-template-columns: auto 1fr; gap: 6px 14px; margin: 0; font-size: 13.5px; }
.op-stats dt { color: var(--ink-3); }
.op-stats dd { margin: 0; font-weight: 600; text-align: right; overflow-wrap: anywhere; }
.op-sub { margin: 16px 0 6px; font-size: var(--fs-xs); font-weight: 700; color: var(--ink-2); text-transform: uppercase; letter-spacing: .04em; }
.op-why-list, .op-pgame-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 4px; font-size: 13.5px; }
.op-why-list li { color: var(--ink-2); }
.op-why-list li:first-child { color: var(--ink); font-weight: 600; }
.op-pgame-list { max-height: 220px; overflow-y: auto; }
.op-pgame-list li { display: flex; gap: 8px; align-items: baseline; }
.op-pgame-list b { flex: none; width: 1.4em; text-align: center; border-radius: var(--r-pill); font-size: 12px; }
.op-pgame-list b.is-win { background: var(--good-wash); color: var(--good); }
.op-pgame-list b.is-loss { background: var(--surface-2); color: var(--ink-3); }

/* ══ §10 Groups regions ═════════════════════════════════════
   One region per owner. During a build wave each owner edits only
   between its own START and END markers (plus the legacy § sections
   it owns, listed in docs/design/components.md), so parallel edits
   never collide. Later regions win ties, so an owner restyles a shared
   class by adding a more specific rule in its own region, never by
   editing someone else's. */

/* ═══ groups-shell START ═══ */
/* The hash-routed shell: nav links, sign-in, Home, Me, a group, a game.
   Everything reuses the tokens and components above (.card, .seg, .btn,
   .bk, .op-pick, .op-lb, .op-court, .overlay); this region only adds layout and
   the few pieces that did not exist (role badge, group tabs, the + button). */

/* Nav: the tabs are links now, and there are three of them. Their active
   state is styled once, in §4 (.tab[aria-current="page"]). */

/* New game. Bottom right on a desktop; above the tab bar on a phone. */
.fab {
  position: fixed; right: calc(22px + var(--safe-r)); bottom: calc(22px + var(--safe-b)); z-index: var(--z-fab);
  width: var(--tap-lg); height: var(--tap-lg); border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  background: var(--ink); color: var(--surface);
  border: 0; box-shadow: var(--sh-float);
  transition: background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.fab:hover { background: color-mix(in srgb, var(--ink) 86%, var(--surface)); }
.fab:active { transform: scale(.96); }
.fab svg { width: 22px; height: 22px; }
@media (max-width: 767px) {
  /* Above the tab bar and the home-indicator inset, never on top of them. */
  .fab { right: calc(16px + var(--safe-r)); bottom: calc(var(--tabbar-h) + 16px + var(--safe-b)); }
}

/* Views */
.gview { padding-top: 22px; min-width: 0; }
.gview h1, .gview h2, .gview p { overflow-wrap: anywhere; }
.gsub { margin: 4px 0 14px; }
.gsec { margin: 0; }
.gform { display: flex; flex-direction: column; gap: 12px; }
.gform .form-actions { margin-top: 2px; }
.gleft { align-self: flex-start; padding-left: 0; }
.gwrap { flex-wrap: wrap; }
.gnote { margin: 0 0 12px; }
.gopt { font-weight: 500; color: var(--ink-3); margin-left: 4px; }

/* A row that is a link: the booking row, made tappable. */
a.gcard { color: inherit; text-decoration: none; cursor: pointer; }
.gcard.is-live { background: var(--hi-wash); }
.gcard.is-live .bk-when { background: var(--surface); color: var(--hi-strong); }
.gcard.is-off { opacity: .55; }
.bk-acts .btn--quiet { white-space: nowrap; }

.tag[class*=" role-"] { margin-left: 4px; }



/* A segmented pair (I'm in / Can't make it), and the period toggle: the
   .seg track and its .seg-item labels. */
.gseg-row { margin: 0 0 14px; max-width: var(--w-card-sm); }
/* Three longer labels (How we queue): room for each on one line. */
.gseg-row-wide { max-width: var(--w-sheet); }
.gseg-row-wide .seg label { flex: 1 1 auto; white-space: nowrap; padding: 0 10px; }

/* Sign in (the crew and the code boxes: "sign-in crew") */
.dev-hint { font-size: 12px; color: var(--ink-3); margin: 0; }
.dev-hint code { font-family: var(--font-mono); background: var(--surface-2); padding: 1px 5px; border-radius: var(--r-field); }

/* A game */
.gsec a:not(.btn):not(.btn--quiet) { color: var(--ink); text-underline-offset: 2px; }
.live-back { margin: 0 0 8px; }
.live-back .btn--quiet { margin-left: -14px; }
.glive-lb { grid-template-columns: 22px minmax(0, 1fr) 44px 44px 54px; }
.gcall { margin: 0 0 12px; text-align: center; }

/* The new/edit game form is one card in the Read column, not the sticky bookings rail. */
.gfcard textarea { min-height: 60px; }

/* Open Play and Match, mounted inside a game: the lists that belong to the
   History tab stay hidden, and the court is the game's. */
#view-openplay.in-game .past-head, #view-openplay.in-game #opHistory,
#view-match .past-head, #view-match #matchHistory,
#mCourtRow { display: none; }
#view-match, #view-openplay { padding-top: 0; }

@media (max-width: 767px) {
  .gview { padding-top: 16px; }
}
/* The credit (92px+ tall) always follows the content, and the + sits above
   the tab bar, so the last interactive row is never under the +: a view
   only needs a modest bottom gap, not a 150px one. */
.gview { padding-bottom: 32px; }
.foot { margin: 28px auto 0; }
.foot[hidden] { display: none; }
/* The footer only carries the sync status; with nothing to say it would be
   a bare hairline under the page, so it goes. */
.foot:has(.sync:empty) { display: none; }
@media (max-width: 767px) {
  .gview { padding-bottom: 40px; }
}
/* ═══ groups-shell END ═══ */

/* ═══ layout system START ═══ */
/* docs/audit/widths.md. Four templates, one page head, one split, three
   heading levels, one rhythm, so every page reads as made by one hand.
     Split  --w-page   page head, then .split (2/3 lead, 1/3 sticky aside)
     Board  --w-page   page head, then a card or tile grid, no aside
     Read   --w-read   page head above one column (the card)
     Shell  --w-shell  Courts: page head, toolbar, results
   main.wrap is the only positioner; a view only caps its own width and
   never centres itself, so the page title sits at one x on every tab. */
/* Widths come from the view's data-tpl (§0 page templates), never from the view. */

/* The page head (§0 .page-head, ui.pageHead) spans both columns of a split.
   A crew slot under it only takes room when something is in it. */
.crew-slot:empty { display: none; }
.sub-h { margin: 0; font-size: var(--t-label); font-weight: 600; line-height: 1.3; color: var(--ink-2); }

/* The split: the one two-column mechanism. On a phone it reads lead, then
   side, then more; from an 880px-wide view the side is a sticky 1/3 aside
   beside lead and more. The aside is always a stack of .cards. */
.gview, .cp, .live-page { container: view / inline-size; }
.split {
  display: grid; gap: var(--sp-6); grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "lead" "side" "more";
}
.split > * { min-width: 0; display: flex; flex-direction: column; gap: var(--stack); }
.split-lead { grid-area: lead; }
.split-side { grid-area: side; }
.split-more { grid-area: more; }
@container view (min-width: 880px) {
  .split {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    grid-template-areas: "lead side" "more side"; align-items: start;
  }
  .split-side { position: sticky; top: var(--sticky-top); }
  /* A tab that hides its aside gives the lead the full width. */
  .split:has(> .split-side[hidden]) { grid-template-columns: minmax(0, 1fr); grid-template-areas: "lead" "more"; }
}

/* Rhythm: blocks in any column are spaced by the column's gap, never by
   their own margins. .stack is the one-column body of a Board or Read page;
   a bare section with an L2 head gets --stack-sec above it. */
.stack { display: flex; flex-direction: column; gap: var(--stack); min-width: 0; }
.stack > .sec-bare, .split > * > .sec-bare { margin-top: calc(var(--stack-sec) - var(--stack)); }
.stack > .sec-bare:first-child, .split > * > .sec-bare:first-child { margin-top: 0; }
:is(.stack, .split > *) > :is(.card, .gsec, .sec-head) { margin-block: 0; }
/* Courts (Shell): the same top padding as a .gview; the toolbar is the
   first block under the head, so it drops its own top padding. In map
   view the map owns the screen and covers the head. */
#view-courts { padding-top: 22px; min-width: 0; }
#view-courts > .page-head { margin-bottom: var(--sp-4); }
#view-courts:not(.is-map) .ctool { padding-top: 0; }
@media (max-width: 767px) { #view-courts { padding-top: 16px; } }

/* An empty host never takes a slot in a column (Home's reserved
   #homeNextHost keeps its inline min-height). */
.split > * > :empty:not([style*="min-height"]) { display: none; }

/* Home: on one column the lead already has the next game, the strip and
   the rank line, so the aside stays out; on two columns the aside has
   them, so the strip and rank line go. */
@media (max-width: 1279.98px) { #homeSide { display: none; } }
/* Two columns: the strip and rank line live in the aside, so their hosts
   take no slot (an empty slot would add a second gap). */
@media (min-width: 1280px) { #homeStrip, #homeRankHost, #homeStripHost:not(:has(.gform:not([hidden]))) { display: none; } }
/* Bookings on a phone keeps today's order: the form, then the log. */
@container view (max-width: 879.98px) { #meBookings .split { grid-template-areas: "side" "lead"; } }

/* Aside cards that carry the old rail pieces. */
.side-card .rail-card, .side-card .rail-lb { border: 0; background: none; padding: 0; border-radius: 0; box-shadow: none; }
.side-card .group-list { padding: 0; }
.side-faces { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
/* The dressed player as a page-head tile. */
.ci-card .sub-h { margin-top: var(--sp-5); }
/* ═══ layout system END ═══ */

/* ═══ shell START ═══ */
/* Owner: shell. Tab bar ↔ sidebar ↔ two-column layout, right rail,
   FAB, footer credit placement, system states.

   BREAKPOINTS (one product, one markup):
     phone    < 768px         bottom tab bar (Home · Courts · Me) + FAB above it
     tablet   768 – 1279px    76px icon rail (the same <header>); templates centre
     desktop  >= 1280px       fixed left sidebar nav (the same <header>);
                              main.wrap left-aligned against it
   Page widths, the page head and the one two-column mechanism (.split) live
   in "layout system" (docs/audit/widths.md). */

/* The credit follows the content closely. The phone's body padding-bottom
   already clears the tab bar and the safe area, so none is added here.
   In the app it is never a page footer: the sidebar (desktop), the foot of
   the rail (tablet), the account sheet (phone) and, for a guest, under the
   page (.guest-credit). Court pages keep it as their footer. */
.footer-credit { padding: var(--sp-4) var(--sp-4) var(--sp-3); }
.acct-credit { padding: var(--sp-2) 0 0; }

/* The tab pill, the folded bar's button and the account button are phone-only. */
.tab-pill, .tabs-mini, .acct-btn { display: none; }

/* ── Phone: bottom tab bar ── */
@media (max-width: 767px) {
  /* Content must clear the fixed bar and the home indicator. Body, not the
     footer: the footer is not always the last painted box. */
  :root { --tabbar-h: 70px; }
  body { padding-bottom: calc(var(--tabbar-h) + var(--safe-b) + var(--sp-2)); }
  .masthead { padding-top: var(--safe-t); }
  .masthead-in { min-height: 52px; padding-block: 6px; gap: 10px; flex-wrap: nowrap; }
  .masthead-in.wrap { padding-inline: calc(var(--gutter) + var(--safe-l)) calc(var(--gutter) + var(--safe-r)); }
  .brand { min-width: 0; }
  .header-tools { margin-left: auto; }
  /* A floating capsule, inset from the edges and lifted off the home
     indicator (iOS 26's tab bar, solid rather than glass). One pill
     (.tab-pill, assets/ui.js) springs under the current tab, lifts and
     follows a finger dragged along the bar, and fades on a page that is
     no tab's (Me, Insights, Admin, Help). Scrolling down folds the bar to
     one round button (.tabs-mini); scrolling up opens it. Icons 23 /
     stroke 1.8, 10.5px labels; the current tab is 600 ink, the rest 500
     --ink-4. No lime here: lime is the page's action. */
  .tabs {
    --bar-w: calc(100vw - 32px - var(--safe-l) - var(--safe-r));
    position: fixed; left: calc(16px + var(--safe-l)); bottom: calc(8px + var(--safe-b) * .5); z-index: var(--z-tabbar);
    display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 0;
    width: var(--bar-w); height: 62px; padding: 4px; overflow: hidden;
    border-radius: var(--r-pill); background: var(--tabbar-bg);
    box-shadow: inset 0 0 0 .5px var(--tabbar-edge), var(--tabbar-sh);
    -webkit-backdrop-filter: blur(20px) saturate(1.5); backdrop-filter: blur(20px) saturate(1.5);
    touch-action: none; -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
    transition: width .5s var(--ease-spring), height .5s var(--ease-spring), bottom .5s var(--ease-spring);
  }
  .tab {
    position: relative; z-index: 1; flex-direction: column; justify-content: center; gap: 3px;
    min-height: 54px; padding: 0 2px; border-radius: var(--r-pill);
    font-size: 10.5px; font-weight: 500; line-height: 1.2; color: var(--ink-4);
    white-space: nowrap; background: none; box-shadow: none; -webkit-user-drag: none;
    transition: color var(--dur-2) var(--ease-out), opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-spring);
  }
  .tab-ico { display: block; width: 23px; height: 23px; stroke-width: 1.8; }
  .tab:hover { color: var(--ink-2); }
  .tab[aria-current="page"], .tab[aria-selected="true"] { color: var(--ink); font-weight: 600; background: none; box-shadow: none; }
  .tab.is-under { color: var(--ink); transform: scale(1.06); }
  .tab-pill {
    display: block; position: absolute; top: 4px; left: 0; height: 54px; width: 0;
    border-radius: var(--r-pill); background: var(--tabbar-pill); pointer-events: none;
    transform-origin: center; will-change: transform, width;
    transition: background-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out), opacity var(--dur-2) var(--ease-out);
  }
  .tab-pill.is-off { opacity: 0; }
  .tabs.is-pressed .tab-pill { background: var(--tabbar-pill-lift); }
  /* Folded: a 56px round button with the current tab's icon. */
  .tabs-mini {
    display: grid; place-items: center; position: absolute; inset: 0; z-index: 2;
    border: 0; padding: 0; background: none; color: var(--ink); cursor: pointer;
    opacity: 0; transform: scale(.6); pointer-events: none;
    transition: opacity .2s, transform .45s var(--ease-spring);
  }
  .tabs-mini .tab-ico { width: 24px; height: 24px; }
  .tabs.is-min { width: 56px; height: 56px; bottom: calc(10px + var(--safe-b) * .5); }
  .tabs.is-min .tab, .tabs.is-min .tab-pill { opacity: 0; pointer-events: none; }
  .tabs.is-min .tabs-mini { opacity: 1; transform: none; pointer-events: auto; }
  /* The account button (assets/groups.js, acctPaint) takes the tool row's
     place for a member: help, theme, install and sign out are in its sheet. */
  html.has-acct .shell-tools { display: none; }
  .acct-btn:not([hidden]) { display: inline-flex; align-items: center; justify-content: center; width: var(--tap); height: var(--tap); margin-right: -5px; }
  .acct-btn .avatar { --av: 34px; }
  .acct-btn[aria-current="page"] .avatar { box-shadow: 0 0 0 2px var(--canvas), 0 0 0 3.5px var(--ink); }
}

/* ── the account sheet (#acctDlg, acctOpen in assets/groups.js) ──
   Grouped rows on soft fills, like iOS Settings: you on top (the whole
   row opens Me), then Me's pages, Admin for the owner, the tools, and
   Sign out on its own at the foot. */
.acct-me {
  display: flex; align-items: center; gap: 14px; padding: 12px 14px;
  border-radius: var(--r-tile); background: var(--surface-2); color: var(--ink); text-decoration: none;
  -webkit-tap-highlight-color: transparent; transition: transform var(--dur-1) var(--ease-out);
}
.acct-me:active { transform: scale(.98); }
.acct-me-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.acct-me-text b { font-size: 17px; font-weight: 700; letter-spacing: -.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-me-text span { font-size: 13px; font-weight: 500; color: var(--ink-3); }
.acct-group { display: flex; flex-direction: column; border-radius: var(--r-tile); background: var(--surface-2); overflow: hidden; }
.acct-row {
  display: flex; align-items: center; gap: 12px; width: 100%; min-height: 48px; padding: 0 14px;
  border: 0; background: none; font: inherit; font-size: 15px; font-weight: 500; color: var(--ink);
  text-align: left; text-decoration: none; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.acct-row > span { flex: 1; min-width: 0; }
.acct-row:active:not(.acct-row--static) { background-color: color-mix(in srgb, var(--ink) 6%, transparent); }
.acct-row--static { cursor: default; padding-block: 6px; }
.acct-row--static > span { flex: 1 0 auto; }
.acct-row--static > .seg { flex: 0 1 auto; }
.acct-row--danger { color: var(--danger); }
/* The group clips its rows: the ring sits inside, rounded like the group. */
.acct-row:focus-visible { outline-offset: -2px; border-radius: var(--r-tile); }
.acct-ico { flex: none; width: 22px; height: 22px; color: var(--ink-2); }
.acct-row--danger .acct-ico { color: var(--danger); }
.acct-chev { flex: none; width: 14px; height: 14px; color: var(--ink-4); }

/* ── Tablet: 76px icon rail (the masthead, restyled) ── */
.brand-mark { display: none; }
@media (min-width: 768px) and (max-width: 1279.98px) {
  body:has(> .masthead) { padding-left: calc(var(--rail-w) + var(--safe-l)); }
  body:has(> .masthead) :is(main.wrap, .foot.wrap) { margin-inline: 0; max-width: none; }
  .masthead {
    position: fixed; top: 0; bottom: 0; left: 0; width: calc(var(--rail-w) + var(--safe-l));
    background: var(--canvas); overflow-y: auto; z-index: var(--z-sticky);
  }
  .masthead-in.wrap {
    max-width: none; min-height: 100%; flex-direction: column; align-items: center;
    justify-content: flex-start; flex-wrap: nowrap; gap: var(--sp-5);
    padding: calc(var(--sp-4) + var(--safe-t)) 0 var(--sp-4) var(--safe-l);
  }
  .brand-logo { display: none; }
  .brand-mark { display: block; width: 44px; height: 44px; border-radius: var(--r-inner); }
  .header-tools { flex: 1; flex-direction: column; align-items: center; gap: var(--sp-4); min-height: 0; }
  .tabs { flex-direction: column; gap: 4px; padding: 0; background: none; border-radius: 0; }
  .tab {
    flex-direction: column; gap: 4px; width: 64px; min-height: 58px; padding: 6px 2px;
    border-radius: var(--r-inner); font-size: 11px; color: var(--ink-2); box-shadow: none;
  }
  .tab-ico { display: block; }
  .tab:hover { background: color-mix(in srgb, var(--panel) 60%, transparent); color: var(--ink); }
  .tab[aria-current="page"], .tab[aria-selected="true"] { background: var(--panel); color: var(--ink); font-weight: 600; box-shadow: var(--sh-nav); }
  .shell-tools { margin-top: auto; flex-direction: column; }
  /* The credit at the foot of the rail: three short centred lines. */
  .sidebar-credit { display: flex; justify-content: center; width: 64px; }
  .sidebar-credit a { flex-direction: column; justify-content: center; gap: 4px; font-size: 10.5px; line-height: 1.25; text-align: center; }
}

/* ── Desktop: fixed left sidebar (the masthead, restyled) ── */
@media (min-width: 1280px) {
  /* Only the app has the sidebar. The iPad board and the live page share
     this stylesheet; a bare body selector gave them an empty 232px strip
     on the left on any screen 1280px or wider (an iPad Pro in landscape). */
  body:has(> .masthead) { padding-left: var(--sidebar-w); }
  /* main.wrap (and the footer under it) sits against the sidebar, not
     centred in what is left: every page title shares one x. */
  body:has(> .masthead) :is(main.wrap, .foot.wrap) { margin-inline: 0; max-width: none; }

  .masthead {
    position: fixed; top: 0; bottom: 0; left: 0; width: var(--sidebar-w);
    background: var(--canvas); overflow-y: auto; z-index: var(--z-sticky);
  }
  .masthead-in.wrap {
    max-width: none; min-height: 100%; flex-direction: column; align-items: stretch;
    justify-content: flex-start; flex-wrap: nowrap; gap: var(--sp-6);
    padding: var(--sp-6) var(--sp-4) var(--sp-5);
  }
  /* The wordmark sits at 90% of the nav column, the tagline under it. */
  .brand { padding-inline: 0; }
  .brand-text { width: 100%; gap: 6px; }
  .brand-logo { width: 90%; }
  .masthead .logo-img { width: 100%; height: auto; }
  .brand-tagline { display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); line-height: 1.3; }
  .header-tools { flex: 1; flex-direction: column; align-items: stretch; gap: var(--sp-4); min-height: 0; }
  .tabs { flex-direction: column; gap: 2px; padding: 0; background: none; border-radius: 0; }
  .tab {
    justify-content: flex-start; gap: 12px; min-height: 42px; padding: 0 12px; border-radius: var(--r-field);
    font-size: 15px; font-weight: 500; color: var(--ink-2);
  }
  .tab-ico { display: block; width: 20px; height: 20px; stroke-width: 1.8; }
  .tab:hover { background: color-mix(in srgb, var(--panel) 60%, transparent); color: var(--ink); }
  .tab[aria-current="page"], .tab[aria-selected="true"] {
    background: var(--panel); color: var(--ink); font-weight: 600; box-shadow: var(--sh-nav);
  }
  /* Icon only, as on phone. The 3px nudge puts the 22px glyph (11px inside
     its 44px button) on the nav icons' line (tab padding 14px). */
  .shell-tools { margin-top: auto; align-self: flex-start; margin-left: 3px; }

  /* The credit sits in the sidebar, under the theme button. */
  .sidebar-credit { display: block; padding: var(--sp-2) 14px 0; }

  /* No sticky masthead up top any more, so the sticky rails sit near the top. */
  .catalog .sidebar { top: var(--sticky-top); max-height: calc(100vh - 48px); }
}

/* ── + New (768 and up, #newBtn, groups.js newPaint) ──
   An ink pill over the sidebar's nav; on the rail a 44px round + with
   the tabs' small label under it. Both allowed, it opens #newMenu (a
   popover, placed by groups.js under the pill or beside the round +). */
.new-btn { display: none; }
.new-menu { display: none; }
@media (min-width: 768px) {
  .new-btn:not([hidden]) {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px; flex: none;
    min-height: 42px; padding: 0 18px 0 14px; border: 0; border-radius: var(--r-pill);
    background: var(--ink); color: var(--surface); font: inherit; font-size: 15px; font-weight: 600;
    cursor: pointer; box-shadow: var(--sh-nav);
    transition: background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
  }
  .new-btn:hover { background: color-mix(in srgb, var(--ink) 86%, var(--surface)); }
  .new-btn:active { transform: scale(.96); }
  .new-btn svg { width: 15px; height: 15px; }
  .new-menu:popover-open {
    display: flex; flex-direction: column; gap: 2px;
    position: fixed; inset: auto; margin: 0; width: 280px; padding: 6px;
    border: 0; border-radius: var(--r-tile); background: var(--surface); color: var(--ink); box-shadow: var(--sh-float);
  }
  .new-item {
    display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 12px; border: 0; border-radius: var(--r-field);
    background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer;
  }
  .new-item:is(:hover, :focus-visible) { background: var(--surface-2); }
  .new-item > svg { flex: none; width: 22px; height: 22px; color: var(--ink-2); }
  .new-item > span { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
  .new-item b { font-size: 15px; font-weight: 600; }
  .new-item small { font-size: 13px; color: var(--ink-3); }
}
@media (min-width: 1280px) {
  .new-btn:not([hidden]) { align-self: flex-start; }
}
@media (min-width: 768px) and (max-width: 1279.98px) {
  .new-btn:not([hidden]) { width: 44px; height: 44px; min-height: 0; padding: 0; border-radius: 50%; }
  .new-btn svg { width: 18px; height: 18px; }
  .new-btn-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}

/* ── The panel sheet (768 and up) ── the sidebar or rail sits on the warm
   --canvas with no divider; every view lives on one raised --panel sheet,
   --r-shell, with a 10px gutter on the right, top and bottom. */
@media (min-width: 768px) {
  body:has(> .masthead) { background: var(--canvas); }
  body:has(> .masthead) > main.wrap {
    background: var(--panel); border-radius: var(--r-shell);
    margin: 10px 10px 10px 0; width: auto; min-height: calc(100svh - 20px); overflow: clip;
  }
}

/* ── Home's aside pieces (paintHomeSide in groups.js), inside .side-card cards ── */
.rail-card {
  display: flex; align-items: center; gap: var(--sp-3); min-width: 0; min-height: var(--tap);
  padding: var(--sp-4); background: var(--surface); border: 0;
  border-radius: var(--r-card); color: inherit; text-decoration: none;
  box-shadow: var(--sh-card);
}
a.rail-card { transition: box-shadow var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out); }
a.rail-card:hover { box-shadow: var(--shadow-md); }
a.rail-card:active { transform: scale(.985); }
.rail-card.is-live { background: var(--hi-wash); }
.rail-card-body { min-width: 0; }
.rail-card-title { margin: 0; font-weight: 700; overflow-wrap: anywhere; }
.rail-card-meta { margin: 2px 0 0; font-size: var(--fs-sm); color: var(--ink-2); overflow-wrap: anywhere; }
.rail-lb {
  padding: var(--sp-2) var(--sp-4); background: var(--surface);
  border: 0; border-radius: var(--r-card);
  box-shadow: var(--sh-card);
}

/* ── home rail board (oct14) ──
   "Leaderboard", the group as a quiet sub-label, a row of group tiles
   (two or more groups; the picked one ringed in Ink, the rest a touch
   quieter), then the card of rows. Tiles are 32px inside 44px targets. */
.rail-board .sec-head { align-items: flex-start; }
.rail-board .sec-head > a { flex: none; margin-top: -10px; }
.rb-title { min-width: 0; flex: 1; }
.rb-sub {
  margin: 2px 0 0; font-size: var(--fs-sm); font-weight: 500; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* No negative margin: the Ink ring sits 4px outside the tile and the rail clips
   at its edge, so a 40px-wide target puts the ring flush with the heading. */
.rb-switch { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 var(--sp-2); }
.rb-pick {
  width: 40px; height: 44px; padding: 0; border: 0; background: none; cursor: pointer;
  display: inline-grid; place-items: center; border-radius: var(--r-inner); color: var(--ink-2);
  transition: transform var(--dur-1) var(--ease-out);
}
.rb-pick:active { transform: scale(.97); }
.rb-pick .gtile { opacity: .6; transition: opacity var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out); }
.rb-pick:hover .gtile { opacity: 1; }
.rb-pick[aria-pressed="true"] { cursor: default; }
.rb-pick[aria-pressed="true"] .gtile {
  opacity: 1;
  box-shadow: 0 0 0 2px var(--ground), 0 0 0 4px var(--ink);
}
.rb-fold-tile {
  width: 32px; height: 32px; display: inline-grid; place-items: center;
  border-radius: var(--r-field); background: var(--surface-2); color: var(--ink);
  font-size: var(--fs-xs); font-weight: 600; font-variant-numeric: tabular-nums;
}
.rb-fold:hover .rb-fold-tile { background: var(--line); }
.rail-board .rail-lb { transition: opacity var(--dur-2) var(--ease-out); }
.rail-board .rail-lb[aria-busy="true"] { opacity: .5; }
.rail-board .rail-lb .empty-inline { padding: var(--sp-3) 0; }
/* Courts is a finder, not a feed: no New game / Post photo buttons over
   its results or the full-screen map (they covered the zoom control). */
body:has(#view-courts:not([hidden])) .fab { display: none; }
/* ═══ shell END ═══ */

/* ═══ feed START ═══ */
/* Owner: feed. Home feed cards, empty state, stories slot. */
#view-home #feedHost { display: flex; flex-direction: column; gap: var(--stack); min-width: 0; }
#view-home #feedHost:empty { display: none; }
#view-home .feed-card { min-width: 0; }
#view-home .feed-card-title, #view-home .feed-card-kicker { overflow-wrap: anywhere; }
.feed-card-quiet { box-shadow: none; background: var(--surface-2); }
#view-home .feed-going { display: flex; align-items: center; gap: var(--sp-2); }
#view-home .feed-rsvp { display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
#view-home .feed-err { margin: 0; }
#view-home .feed-create { margin-top: var(--sp-5); display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-3); }
#view-home .feed-create .gform { width: 100%; }
/* Under a section head's "New group" action: no gap until it opens. */
.feed-create.in-head, #view-home .feed-create.in-head { margin: 0; }
.feed-create.in-head:has(> form[hidden]) { display: none; }
.feed-create.in-head .gform { width: 100%; }
.me-groups .feed-create.in-head { margin-bottom: var(--sp-3); }
#view-home .feed-empty { padding-top: var(--sp-7); }
#view-home .feed-paste { display: flex; gap: var(--sp-2); width: 100%; max-width: var(--w-sheet); }
#view-home .feed-paste input { flex: 1; min-width: 0; min-height: var(--tap); }

/* P2 Home: stories slot, feed list, cards, viewer, composer, share sheet. */
#view-home .feed-list { display: flex; flex-direction: column; gap: var(--stack); }
#view-home .feed-more { margin-top: var(--sp-3); min-height: var(--tap); }
#view-home .feed-ago { margin-left: auto; font-weight: 500; color: var(--ink-3); white-space: nowrap; }
#view-home .feed-empty-quiet { padding-block: var(--sp-6); }
#view-home .feed-card { position: relative; }
#view-home .feed-card .feed-card-actions { margin-top: var(--sp-1); }
#view-home .you-slot:empty { display: none; }
#view-home .result-top { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
#view-home .result-row {
  display: grid; grid-template-columns: 1.6em minmax(0, 1fr) auto; align-items: center; gap: var(--sp-2);
  min-height: var(--tap);
}
#view-home .result-row:first-child { border-top: 0; }
#view-home .result-p, #view-home .movement-row .lb-name {
  display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; color: inherit; text-decoration: none; min-height: var(--tap);
}
#view-home .result-name { min-width: 0; overflow-wrap: anywhere; font-weight: 600; }
#view-home .result-row .result-score, #view-home .result-me .result-score { font-size: var(--fs-md); }
#view-home .result-me { margin: 0; display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; font-weight: 600; background: var(--hi-wash); color: var(--ink); padding: var(--sp-2) var(--sp-3); border-radius: var(--r-inner); }
#view-home .milestone-card .feed-card-row { align-items: center; }
#view-home .milestone-card { background: var(--hi-wash); }
#view-home .movement-row .move { white-space: nowrap; }
#view-home .movement-row .move.is-up { color: var(--hi-strong); }
#view-home .post-media { border-radius: 0; }
#view-home .fire-btn { min-width: var(--tap); justify-content: center; }
.stories-rail .story-ring:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: var(--r-card); }
.story-ring .ring { display: block; }
.story-ring .ring > img { box-sizing: border-box; }

/* Story viewer is a <dialog>: keep it hidden until open, and fill the screen. */
dialog.story-viewer { width: 100%; height: 100%; max-width: none; max-height: none; margin: 0; border: 0; overflow: hidden; }
dialog.story-viewer:not([open]) { display: none; }
dialog.story-viewer::backdrop { background: var(--ink); }
.story-viewer .story-head .avatar { flex: none; }
.story-viewer .story-head > div { min-width: 0; }
.story-viewer .story-group, .story-viewer .story-game { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.story-viewer .story-tap:focus-visible { outline: 2px solid var(--white); outline-offset: -4px; }
.story-viewer .story-caption { overflow-wrap: anywhere; }
.story-viewer .story-foot { align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.story-viewer .story-confirm { margin-right: auto; font-weight: 600; }
.story-viewer .story-btn { color: var(--white); min-height: var(--tap); }

/* Composer sheet + share sheet. */
.composer .op-pick { min-height: var(--tap); }
.composer-file { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; pointer-events: none; }
.composer-drop:has(+ .composer-file:focus-visible) { outline: 2px solid var(--ink); outline-offset: 2px; }
.composer-drop { aspect-ratio: 4 / 3; }
.composer .field-row { display: flex; flex-direction: column; gap: var(--sp-1); }
.composer textarea { width: 100%; min-height: var(--tap); resize: vertical; font: inherit; }
.composer-count { align-self: flex-end; font-size: var(--fs-xs); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.composer .overlay-foot, .share-pane .overlay-foot { display: flex; justify-content: flex-end; gap: var(--sp-2); }
.share-pane .share-card { aspect-ratio: 4 / 5; max-width: var(--w-card-sm); }

/* Post photo button: sits left of New game when both show. */
.fab-post { background: var(--surface); color: var(--ink); border: 0; box-shadow: var(--sh-float); }
.fab-post:hover { background: var(--surface-2); }

.fab-post svg { width: 24px; height: 24px; }
#fabNew:not([hidden]) ~ .fab-post { right: calc(22px + var(--safe-r) + var(--tap-lg) + 12px); }
@media (max-width: 767px) {
  #fabNew:not([hidden]) ~ .fab-post { right: calc(16px + var(--safe-r) + var(--tap-lg) + 12px); }
}
/* ═══ feed END ═══ */

/* ═══ game START ═══ */
/* Owner: game. Game page per status, check-in, record-result sheet,
   live scorer polish. */

/* ── shared bits ── */
.feed-card-main { min-width: 0; flex: 1; }
.feed-card-main .feed-card-title { overflow-wrap: anywhere; }

/* ── a. upcoming ── */
.game-where { overflow-wrap: anywhere; }
.game-where a { color: var(--ink); text-underline-offset: 3px; }
.game-where span { color: var(--ink-3); font-weight: 500; }
/* One line of facts, dot-separated ("2 courts · Doubles · Rotate · Winners & losers"). */
.game-head-meta { display: flex; align-items: center; gap: 0; flex-wrap: wrap; }
.game-head-meta > span + span::before { content: '·'; margin: 0 6px; color: var(--ink-3); }
.game-head-meta .chip-link { margin: 0 0 0 var(--sp-3); }
.game-head-share { flex: none; }
.game-head-meta .chip-link { margin: 0; }

.rsvp-row { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.rsvp-row .btn--state { flex: 1 1 180px; }
.going-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.going-row { display: flex; align-items: center; gap: var(--sp-3); min-height: var(--tap); min-width: 0; }
.going-name { min-width: 0; font-weight: 600; overflow-wrap: anywhere; }

.org-card { display: flex; flex-direction: column; gap: var(--sp-2); }
.org-quiet { display: flex; align-items: center; justify-content: center; gap: var(--sp-2); flex-wrap: wrap; }


/* ── b. check-in ── */
.ci-summary {
  position: sticky; top: var(--ci-top, calc(var(--safe-t) + 60px)); z-index: var(--z-sticky);
  display: flex; align-items: baseline; gap: 4px;
  margin: 0 0 var(--sp-4); padding: 10px var(--sp-4);
  font-size: var(--fs-md); font-weight: 700; border: 0; border-radius: var(--r-inner);
  box-shadow: var(--shadow-sm); background: var(--surface-3); }
.ci-summary b { font-variant-numeric: tabular-nums; font-size: var(--fs-xl); line-height: 1; color: var(--good); }
.ci-dot { color: var(--ink-3); margin: 0 4px; }
@media (min-width: 768px) { .ci-summary { --ci-top: 72px; } }
@media (min-width: 1280px) { .ci-summary { --ci-top: 8px; } }
.ci-card .op-sub { margin-top: var(--sp-5); }
.ci-chips { gap: var(--sp-2); }
.ci-pick { min-height: 52px; padding: 0 18px; font-size: var(--fs-base); }
.ci-pick[aria-pressed="true"]::before { content: ""; display: inline-block; width: 1em; height: 1em; margin-right: 6px; vertical-align: -0.125em; background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; }
/* PAC_UI.check(): the shared check mark, sized to the text around it. */
.ico-check { display: inline-block; width: 1em; height: 1em; vertical-align: -0.125em; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.fin-game .q-no .ico-check { width: 16px; height: 16px; }
.ci-letters .op-letter { min-height: var(--tap); min-width: var(--tap); }
.ci-add { margin-top: var(--sp-3); }
.ci-add input { min-height: 48px; }

.ci-start { margin-top: var(--sp-5); }
.ci-card #ciAllGoing { min-height: var(--tap); }
.ci-open-head { margin-bottom: var(--sp-2); }

/* ── c. live (everyone) ── */
.live-view .you-banner { margin-bottom: var(--sp-4); }
.court-list { display: grid; gap: var(--sp-3); }
.court-card { min-width: 0; }
.court-vs { display: flex; align-items: center; gap: 8px; font-size: var(--fs-xs); font-weight: 600; color: var(--ink-3); line-height: 1; margin: 2px 0; }
.court-vs::before, .court-vs::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.queue-card { padding: var(--sp-4); }
.queue-row .q-sub { display: block; font-size: var(--fs-xs); font-weight: 500; color: var(--ink-3); }
.queue-row.has-menu { grid-template-columns: 28px minmax(0, 1fr) auto auto; }
.queue-row.break-row, .queue-row.game-row { grid-template-columns: minmax(0, 1fr) auto; }
.break-list { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
/* live link v113: stats line, board, one-liners, up next */
.live-stats { margin: 0 0 var(--sp-4); font-size: var(--fs-xs); color: var(--ink-2); }
.live-page .page-meta.live-stats { margin: 0; }
/* Live boards (the organizer's Open Play, the member live view, the live
   link): the courts lead, never the leaderboard. The feed template's
   rail-first rule (its first aside card on top under 1280px) is not for them:
   beside the courts once the view has room (an iPad on its side), else one
   column. Viewers get courts, leaderboard, then the rest (v113's order); the
   organizer gets the controls first and the session panel after them. */
[data-tpl="feed"]:has(.op-live:not([hidden]), .live-split) { max-width: calc(var(--w-feed) + var(--sp-6) + var(--w-aside)); }
.split:is(.op-live, .live-split, #liveBody) > * { order: 0; grid-area: auto; }
.split.op-live > .split-side { display: flex; }
@container view (min-width: 760px) {
  .split:is(.op-live, .live-split, #liveBody) {
    grid-template-columns: minmax(0, 1fr) minmax(260px, var(--w-aside)); grid-template-areas: none; gap: var(--sp-6); align-items: start;
  }
  .split:is(.op-live, .live-split, #liveBody) > :is(.split-lead, .split-more) { grid-column: 1; }
  .split:is(.op-live, .live-split, #liveBody) > .split-side { display: flex; grid-column: 2; grid-row: 1 / span 2; position: sticky; top: var(--sticky-top); }
}
@container view (max-width: 759.98px) {
  .split:is(.op-live, .live-split, #liveBody) { grid-template-columns: minmax(0, 1fr); grid-template-areas: none; }
  .split:is(.live-split, #liveBody) > :is(.split-lead, .split-side) { display: contents; }
  .split:is(.live-split, #liveBody) > .split-lead > * { order: 2; }
  .split:is(.live-split, #liveBody) > .split-lead > :first-child { order: 0; }
  .split:is(.live-split, #liveBody) > .split-side > * { order: 1; }
  .split:is(.live-split, #liveBody) > .split-side > * + * { order: 3; }
  .split:is(.live-split, #liveBody) > .split-more { order: 4; }
}
/* A section on the live board: its heading, then its cards, a --stack apart.
   Every column opens with one, so the first cards line up across columns. */
.live-sec { display: flex; flex-direction: column; gap: var(--stack); min-width: 0; }
.live-last, .live-break { margin: 0; font-size: 13px; line-height: 1.4; color: var(--ink-2); overflow-wrap: anywhere; }
.live-last svg { width: 14px; height: 14px; margin-right: 6px; vertical-align: -2px; color: var(--good); }
/* Results: finished games, quieter than the live sections above. */
.live-past .sec-head > h2 { font-size: var(--t-card); font-weight: 600; color: var(--ink-2); }
.live-past { gap: var(--sp-2); }
.live-results { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.live-lb-row { grid-template-columns: 26px minmax(0, 1fr) 44px 48px; }
.live-lb-row > :first-child { white-space: nowrap; overflow: visible; }
.live-next { display: grid; gap: var(--sp-3); }
.live-next > div { min-width: 0; }
.live-next p { margin: 0; }
.live-next-teams, .live-next-list { font-size: var(--fs-md); font-weight: 600; overflow-wrap: anywhere; }
.live-next-list { font-weight: 500; color: var(--ink-2); }
.live-vs { font-size: var(--fs-xs); font-weight: 600; color: var(--ink-3); margin: 0 2px; }
@media (max-width: 767px) {
  .live-board .live-lb-row:not(.op-lb-head):nth-child(n+5) { display: none; }
  .live-board .live-lb > .list-row:nth-child(n+4) { display: none; }
}
@media (min-width: 768px) {
  .live-next { grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); gap: var(--sp-5); }
  .live-next-game + .live-next-then { padding-left: var(--sp-5); }
  .live-next-game:only-child, .live-next-then:only-child { grid-column: 1 / -1; }
}

/* Host view: courts as .court-card, actions at 44px+. */
.op-courts { gap: var(--sp-3); }
@media (min-width: 768px) { .op-courts { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); } }

.court-card-actions .btn { flex: 1 1 auto; }
.court-card .court-team .names { font-size: var(--fs-md); }
.op-more { min-width: var(--tap); min-height: var(--tap); font-size: 22px; }

/* ── d. record-result sheet ── */
#opResultDlg .team-pick { min-height: var(--tap-xl); font-size: var(--fs-lg); overflow-wrap: anywhere; }
.res-add { align-self: center; min-height: var(--tap); }
.res-score { display: grid; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); background: var(--surface-2); border-radius: var(--r-inner); }
.res-stepper-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.res-stepper-row .field-label { margin: 0; font-weight: 600; }
.res-actions { flex-wrap: wrap; }
.res-actions .btn { flex: 1 1 140px; }

/* ── e. live scorer ── */
.scorer-call { font-size: clamp(48px, 15vw, 72px); font-weight: 700; }
.scorer-teams { gap: var(--sp-3); }
.rally-btn {
  min-height: clamp(var(--tap-xl), 38svh, 360px);
  border-width: 2px; border-radius: var(--r-card);
}
.rally-score { font-size: clamp(64px, 20vw, 104px); font-weight: 700; }
.rally-name { font-size: var(--fs-md); font-weight: 700; color: var(--ink); white-space: normal; text-align: center; overflow-wrap: anywhere; }
@media (max-width: 767px) { .rally-btn { min-height: clamp(var(--tap-xl), 38svh, 360px); } }
@media (min-width: 768px) { .rally-btn { min-height: 240px; } }
.scorer-acts { margin-top: var(--sp-4); }
.scorer-acts .scorer-undo { flex: 1 1 100%; min-height: var(--tap-lg); font-size: var(--fs-md); font-weight: 700; }


/* ── f. finished ── */
.mvp-card {
  display: flex; align-items: center; gap: var(--sp-4); margin-bottom: var(--sp-4);
  padding: var(--sp-4); border-radius: var(--r-card);
  background: var(--hi-wash); border: 0;
  box-shadow: var(--sh-card);
}
.mvp-body { min-width: 0; }
.mvp-name { margin: 4px 0 0; font-size: var(--fs-lg); font-weight: 700; letter-spacing: -.01em; overflow-wrap: anywhere; }
.mvp-meta { margin: 2px 0 0; font-variant-numeric: tabular-nums; font-size: var(--fs-sm); color: var(--ink-2); }
.fin-game .q-no { color: var(--good); font-size: 14px; }
.fin-beat { font-weight: 500; color: var(--ink-3); }
.fin-game .q-eta { font-size: 13px; color: var(--ink-2); }
.photos-prompt { margin-top: var(--sp-4); }

/* ═══ game END ═══ */

/* ═══ group START ═══ */
/* Owner: group. Group page, sign-in + invite, Me, create game, courts
   booking buttons. */

/* Cover band: the invite landing. Gradient from tokens only. */
.cover-band {
  position: relative; overflow: hidden; isolation: isolate;
  border-radius: var(--r-card); border: 0;
  background: var(--surface); margin-bottom: var(--sp-4);
  box-shadow: var(--sh-card);
}
.cover-art {
  position: absolute; inset: 0; z-index: -1;
  background: var(--hi-wash);
}
.cover-in {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-6) var(--sp-5);
}
.cover-in .avatar { flex: none; box-shadow: 0 0 0 3px var(--surface); }
.cover-text { min-width: 0; }
.cover-kicker { margin: 0 0 2px; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); }
.cover-name { margin: 0; font-size: var(--fs-xl); font-weight: 700; letter-spacing: -.02em; line-height: 1.15; overflow-wrap: anywhere; }
.cover-sub { margin: 4px 0 0; font-size: var(--fs-sm); color: var(--ink-2); }
.cover-text .tag { margin: var(--sp-2) 0 0; }
.join-body { display: flex; flex-direction: column; gap: var(--sp-2); }
.join-body .form-msg { text-align: center; margin: 0; }
@media (max-width: 767px) {
  .cover-in { flex-direction: column; text-align: center; padding: var(--sp-6) var(--sp-4); }
}

/* Sign-in card */
.si-card { padding: var(--sp-6) var(--sp-5); }
.si-card input:not([type="radio"]):not([type="checkbox"]) { font-size: max(16px, var(--fs-md)); min-height: var(--tap-lg); }
.si-links { display: flex; justify-content: space-between; gap: var(--sp-2); flex-wrap: wrap; }


/* Group page */
.lb-card { padding-block: var(--sp-2); }
/* The season header over "This season" (groups.js seasonHead): the title
   and days left, the dates 4px under them, then the .bar of time gone. */
.lb-season { gap: var(--sp-3); }
.lb-season .card-head + .card-kicker { margin-top: calc(var(--sp-1) - var(--sp-3)) !important; }
.lb-list { display: flex; flex-direction: column; }
.lb-list .lb-row { min-height: var(--tap); }
.lb-list .lb-rating { display: inline-flex; align-items: center; gap: 6px; justify-content: flex-end; }
.member-row { align-items: center; gap: var(--sp-3); }
.member-row .avatar { flex: none; }

.hist-row { margin-bottom: var(--sp-3); }
.hist-row .feed-card-title { overflow-wrap: anywhere; }
@media (min-width: 768px) {
  .hist-list, #groupBody { min-width: 0; }
}

/* Me */

/* Create / edit game */
.gfcard { padding: var(--sp-5); gap: var(--sp-5); }
.gf-hint { margin: -4px 0 0; font-size: var(--fs-sm); font-weight: 600; color: var(--hi-strong); background: var(--hi-wash); border-radius: var(--r-pill); padding: 6px 12px; align-self: flex-start; }
.gf-sec { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
/* Form section heading (When / Where): a step above the field labels. */
/* Game form: Where → When (+ length) → Play → optional extras. Small
   section labels and hairlines instead of a headline per question. */
.gfcard .gf-sec + .gf-sec { padding-top: var(--sp-5); }
.gfcard #bCourtLabel { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.gf-row { display: flex; flex-direction: row; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.gf-rl { display: flex; flex-direction: column; font-size: var(--fs-base); font-weight: 600; color: var(--ink); }
.gf-ends { font-size: var(--fs-sm); font-weight: 500; color: var(--ink-2); }
.gf-ends:empty { display: none; }
.gf-more > summary { display: flex; align-items: center; gap: 6px; min-height: var(--tap); cursor: pointer; list-style: none;
  font-size: var(--fs-base); font-weight: 600; color: var(--ink-2); }
.gf-more > summary::-webkit-details-marker { display: none; }
.gf-more > summary::before { content: "+"; width: 1em; text-align: center; font-weight: 700; }
.gf-more[open] > summary::before { content: "−"; }
.gfcard .gf-more { display: block; }
.gf-more > .field-row { margin-top: var(--sp-3); }
/* "1h 30m" is the widest value; a fixed width keeps − and + from jumping. */
.gf-dur output { min-width: 6.5ch; }
.gf-row .op-step { align-self: center; }
.gf-cancel { align-self: center; min-height: var(--tap); }
.gfcard .form-actions { flex-direction: column; align-items: stretch; }

/* Tap floor for the finder toolbar and every text field: 44px, at every
   width. Placed last in this region so it beats the older phone rules. */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), select { min-height: var(--tap); }
.view-btn { width: var(--tap); height: var(--seg-h); min-height: 0; }
.field select { min-height: var(--tap); }
.picker-btn, .facet-row, .facet-check { min-height: var(--tap); }
@media (max-width: 767px) {
  /* Views now sit on the result line and Sync/Share beside the stats, so
     the toolbar is two rows: search, then Filters · sort · Near me. Near me
     goes icon-only to leave the sort menu room for "Recently checked". */
  #view-courts .controls > .field:not(.search) { grid-column: 2; grid-row: 2; }
  #view-courts .controls > .near-me { grid-column: 3; grid-row: 2; width: var(--tap); padding: 0; justify-content: center; }
  #view-courts .near-me #nearLabel,
  #view-courts .sync-act #syncLabel {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  #view-courts .sync-act { width: var(--tap); padding: 0; justify-content: center; }
}
/* Person profile, #/u/<uid> (Set 5): Insights' composition (page head,
   .my-tiles, .my-grid of cards); only the season line and the link in are its own. */
#view-user { max-width: 1180px; }
.person-head .page-mark .avatar { --av: 56px; }
.person-streak .stat-delta { min-height: 22px; display: flex; align-items: center; }
.person-spark { position: relative; color: var(--ink); }
.person-spark svg { display: block; width: 100%; height: 64px; overflow: visible; }
.person-spark-now { position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%; background: var(--lime); box-shadow: 0 0 0 2px var(--ink); }
.person-link { margin: 0 0 var(--sp-3); font-size: 13.5px; font-weight: 500; color: var(--ink-3); }
.person-link a { color: var(--ink); font-weight: 600; text-decoration: none; }
.person-link a:hover { text-decoration: underline; text-underline-offset: 2px; }
@media (max-width: 767.98px) { .person-head .page-mark .avatar { --av: 48px; } }
/* Player profile (§11 components, wired up here) */
.profile-sec { margin-bottom: var(--sp-4); }
.profile-rel { transition: transform var(--dur-1) var(--ease-out); display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px var(--sp-3); align-items: center; min-height: var(--tap); padding: var(--sp-2) 0; color: inherit; text-decoration: none; }
.profile-rel:first-child { border-top: 0; }
.profile-rel-k { grid-column: 1 / -1; font-size: var(--fs-xs); color: var(--ink-2); }
.profile-rel-who { display: flex; align-items: center; gap: 10px; min-width: 0; font-weight: 600; overflow-wrap: anywhere; }
.profile-rel-n { font-size: 12.5px; color: var(--ink-2); }
.profile-rel-none { grid-column: 1 / -1; color: var(--ink-2); }
.photo-grid img { min-width: 0; background: var(--surface-2); }
/* Me › Groups: one card per group, no outer card. Head: avatar, name, role
   and player count; foot (once the roster loads): my games, record, last
   game there, and My profile. */
.me-group-list { display: flex; flex-direction: column; gap: var(--sp-3); }
.me-group .feed-card-meta { display: flex; align-items: center; gap: 0 6px; flex-wrap: wrap; }
.me-group-chev { margin-left: auto; flex: none; color: var(--ink-3); display: inline-flex; }
.me-group-chev svg { width: 18px; height: 18px; }
.me-group-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; padding-top: var(--sp-4); }
.me-group-foot[hidden] { display: none; }
.me-group-stats { display: flex; gap: var(--sp-6); margin: 0; min-width: 0; }
.me-group-stats dt { font-size: 12px; font-weight: 500; color: var(--ink-3); }
.me-group-stats dd { margin: 2px 0 0; font-size: var(--fs-md); font-weight: 600; font-variant-numeric: tabular-nums; }
.me-group-none { margin: 0; font-size: 13px; color: var(--ink-3); }
a.profile-rel:active { transform: scale(.985); }
/* ═══ group END ═══ */

/* ═══ board START ═══ */
/* Owner: board. iPad board polish + corner credit. */

/* Reserve a strip under the footer for the credit (bottom-right) and the
   connection note (bottom-centre), so neither sits on .board-foot / .bo-break. */
.board {
  padding-bottom: max(30px, calc(env(safe-area-inset-bottom) + 16px));
}

/* ── footer credit ── */
.board-credit {
  position: fixed; z-index: 20; margin: 0;
  right: max(14px, env(safe-area-inset-right));
  bottom: max(7px, calc(env(safe-area-inset-bottom) - 4px));
  font-size: 10px; line-height: 1.2; font-weight: 500; letter-spacing: .01em;
  color: var(--ink-3); opacity: .55; text-decoration: none; white-space: nowrap;
}
.board-credit { display: inline-flex; align-items: center; gap: 4px; }
.board-credit .vlb-logo { height: 10px; }
.board-credit:hover, .board-credit:focus-visible { opacity: .9; }
.board-credit:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; border-radius: 3px; }
/* The overlay precedes the credit in the DOM, so a sibling selector hides it. */
#boardWin:not([hidden]) ~ #boardCredit { display: none; }
.board-conn { bottom: 8px; font-size: 10px; }
.board-conn:empty { display: none; }

/* ── live match: the two totals own the screen ──
   Both sides fill equal halves of the board (one panel each, the serving
   one outlined), and each dial is sized from its own panel with container
   units, never from the viewport. Sized from the viewport, a dial could be
   wider than its panel and a panel taller than its row, so on an iPad the
   ring spilled over the panel edge and the panel over the serve strip. */
.board-team { font-size: clamp(16px, 2.6vw, 34px); }
.board-points { font-size: 1.42em; }
.board-towin, .board-games, .board-tag { opacity: .9; }
.board-sides { grid-template-rows: minmax(0, 1fr); align-items: stretch; }
.board-side {
  container-type: size; align-self: stretch; justify-self: stretch;
  width: auto; min-height: 0; overflow: hidden; background: var(--surface);
}
/* The rest of the panel (name, "to win", games, tag, gaps, padding) is
   about 190px; the dial gets what is left, and never more than 80% wide. */
.score-dial { font-size: max(40px, min(39cqw, calc((100cqh - 190px) / 2.05), 300px)); width: 2.05em; max-width: none; }
@media (max-aspect-ratio: 1/1) {
  .board-sides { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(6px, 1.4vh, 20px); }
  .board-side { padding-block: clamp(6px, 1.2vh, 16px); }
  .board-team { font-size: clamp(18px, 3.8vw, 36px); }
  .board-foot { grid-template-columns: 1fr auto; row-gap: 6px; }
  .board-games-log { grid-column: 1 / -1; order: 3; }
}

/* ── controls ── */
.board-ctl { gap: 10px; }
.ctl-btn {
  min-height: 52px; padding: 0 20px; font-size: 16px; letter-spacing: .01em;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
}
.ctl-btn svg { width: 22px; height: 22px; }

/* ── winner overlay ── */
.win-card { display: flex; flex-direction: column; align-items: center; gap: .1em; }
.win-title {
  display: inline-flex; align-items: center; gap: .9em;
  font-size: clamp(18px, 3.2vw, 44px); letter-spacing: .28em; padding-left: .28em;
}
.win-title::before, .win-title::after {
  content: ""; width: clamp(24px, 6vw, 90px); height: 3px; border-radius: var(--r-pill);
  background: currentColor; opacity: .45;
}
.win-names {
  margin: .2em 0 0; font-size: clamp(34px, min(9.5vw, 14vh), 160px);
  font-weight: 700; letter-spacing: -0.03em; line-height: 1.04; text-wrap: balance;
}
.win-line {
  margin: .45em 0 0; padding: .1em .7em; border-radius: var(--r-pill);
  background: var(--hi); color: var(--hi-ink);
  font-size: clamp(20px, 4vw, 54px); letter-spacing: .02em;
}
.win-line[hidden] { display: none; }

/* ── open play ── */
.bo-title { font-size: 1.5em; }
.bo-stats { font-size: .9em; }

/* Court cards: names large, one player per line, state unmistakable. */
.bo-court { padding: .8em 1em; gap: .5em; justify-content: flex-start; }
.bo-court-no { font-size: 1.3em; }
.bo-status { font-size: .9em; padding: .2em .9em; }
.bo-teams { flex: 1; justify-content: center; gap: .15em; }
.bo-team { display: flex; flex-direction: column; align-items: center; gap: 0; line-height: 1.12; width: 100%; }
.bo-pl { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bo-vs { display: flex; align-items: center; justify-content: center; gap: .6em; width: 60%; margin: .15em 0; }
.bo-vs::before, .bo-vs::after { content: ""; flex: 1; height: 1px; background: var(--line-2); }
.bo-timer { font-size: 1.9em; line-height: 1; }
.bo-timer.is-call { font-size: 1.1em; font-weight: 700; }
.bo-court.is-playing { background: var(--good-wash); }
.bo-court.is-free { opacity: 1; background: transparent; border-style: dashed; justify-content: center; }
.bo-court.is-free .bo-court-top { opacity: .75; }
.bo-court-free { margin: auto 0; font-size: 1.05em; }

/* Side panels size to their rows. Empty ones stay a slim strip. */
.bo-side { justify-content: flex-start; }
.bo-panel { flex: 0 1 auto; padding: .8em 1em; }
.bo-next, .bo-lb { flex: 0 1 auto; }
.bo-h { margin-bottom: .5em; font-size: .85em; }
.bo-next-row[hidden], .bo-lb-row[hidden] { display: none; }
.bo-next-row { font-size: 1.45em; padding: .25em 0; }
.bo-next-row:first-child .bo-eta { color: var(--hi-strong); }
.bo-eta { font-size: .65em; }
.bo-lb-row { grid-template-columns: 1.6em minmax(0, 1fr) auto; font-size: 1.15em; padding: .28em 0; }
.bo-lb-row:first-child .bo-nm { font-weight: 700; }
.bo-empty { padding: .1em 0; font-size: .9em; }
.bo-panel:has(> ol > .bo-empty) { padding-block: .55em; }
.bo-panel:has(> ol > .bo-empty) .bo-h { margin-bottom: .2em; }
.bo-break { font-size: 1em; }

@media (max-aspect-ratio: 1/1) {
  .bo-main { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto; }
  .bo-side { flex-direction: row; align-items: flex-start; max-height: 42svh; min-height: 0; }
  .bo-side .bo-panel { flex: 1 1 0; min-width: 0; max-height: 42svh; }
  .bo-next-row { font-size: 1.3em; }
  .bo-lb-row { font-size: 1.05em; }
}
@media (prefers-reduced-motion: reduce) {
  .ctl-btn { -webkit-backdrop-filter: none; backdrop-filter: none; }
}
/* ═══ board END ═══ */

/* ═══ me-stage START ═══ */
/* Owner: Me + player profile (groups.js stageHtml). data-tpl="stage";
   the grid itself is §0 (.stage-grid). Mockups 3a/3b/3c. The lockers and
   the floor are drawn here (art, not a component); everything else is
   §0 parts placed by layout. */
.me-tabs { margin: 0 0 var(--sp-4); }
.stage {
  position: relative; isolation: isolate; overflow: hidden;
  display: grid; grid-template-rows: auto auto minmax(0, 1fr) auto; gap: var(--sp-4);
  min-height: clamp(560px, calc(100svh - 120px), 860px); padding: var(--sp-6);
  background: var(--surface-2); border-radius: var(--r-sheet);
}
/* The floor: the bottom 34% a shade down. */
.stage::after {
  content: ""; position: absolute; z-index: -1; left: 0; right: 0; bottom: 0; height: 34%;
  background: color-mix(in srgb, var(--ink) 5%, var(--surface-2));
}
/* Three lockers behind the player, standing on the floor line. */
.stage-lockers {
  position: absolute; z-index: -1; left: 50%; bottom: 34%; translate: -50% 0;
  width: min(72%, 440px); height: 44%; display: flex; gap: 8px;
}
.stage-lockers i {
  position: relative; flex: 1; border-radius: var(--r-inner) var(--r-inner) 0 0;
  background: color-mix(in srgb, var(--ink) 4%, var(--surface-2));
}
.stage-lockers i::before {
  content: ""; position: absolute; top: 18px; left: 50%; translate: -50% 0; width: 46%; height: 15px;
  background: repeating-linear-gradient(to bottom, color-mix(in srgb, var(--ink) 9%, transparent) 0 3px, transparent 3px 6px);
}
.stage-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); min-width: 0; }
.stage-id { min-width: 0; }
.stage-name {
  margin: 0; display: flex; align-items: center; gap: 10px; min-width: 0;
  font-size: 36px; font-weight: 700; letter-spacing: -.02em; line-height: 1.1;
}
.stage-name > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stage-sub { margin: 6px 0 0; font-size: var(--t-body); color: var(--ink-2); }
.stage-coll { display: none; }
.stage-moves { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; overflow-x: auto; scrollbar-width: none; padding-block: 4px; }
.stage-moves::-webkit-scrollbar { display: none; }
.stage-moves .chip { flex: none; }
.stage-moves-k { flex: none; font-size: var(--t-meta); font-weight: 500; color: var(--ink-2); margin-right: var(--sp-1); }
/* Slot chips in a column to the left of the player, never over it. */
.stage-floor { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: end; gap: var(--sp-4); min-height: 0; }
.stage-slots { align-self: center; display: flex; flex-direction: column; gap: var(--sp-4); }
.stage-slot {
  display: flex; align-items: center; gap: 10px; padding: 0; border: 0; background: none;
  font: inherit; color: var(--ink); text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.stage-slot-art {
  flex: none; width: 64px; height: 64px; display: grid; place-items: center; border-radius: 50%;
  background: var(--surface);
  transition: box-shadow var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.stage-slot-art svg { width: 40px; height: 40px; }
.stage-slot-art.is-empty { color: var(--ink-3); }
.stage-slot-art.is-empty svg { opacity: .3; }
.stage-slot:active .stage-slot-art { transform: scale(.96); }
.stage-slot[aria-pressed="true"] .stage-slot-art { box-shadow: 0 0 0 3px var(--accent), 0 0 0 5px var(--ink); }
.stage-slot:focus-visible { outline: none; }
.stage-slot:focus-visible .stage-slot-art { outline: 2px solid var(--ink); outline-offset: 7px; }
.stage-slot-t { display: none; flex-direction: column; line-height: 1.25; }
.stage-slot-t b { font-size: var(--t-meta); font-weight: 700; }
.stage-slot-t span { font-size: 12px; color: var(--ink-2); }
.stage-av { justify-self: center; width: clamp(220px, 100%, 360px); line-height: 0; }
.stage-av svg { width: 100%; height: auto; display: block; }
/* The stats strip, pinned to the bottom of the stage. */
.stage-stats { grid-template-columns: repeat(4, auto) minmax(0, 1fr); align-items: end; }
.stage-stats .is-form { align-items: flex-end; }
.stage-form { display: flex; gap: 4px; }
@media (min-width: 1600px) { .stage-slot-t { display: flex; } }
@media (min-width: 1680px) { .stage-av { width: min(100%, 480px); } }

/* Panels. */
.stage-sheet-top { display: none; }
.stage-l, .stage-r { display: flex; flex-direction: column; gap: var(--stack); min-width: 0; }
.gear-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
.medal-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-2); }
.gear-tile, .medal {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-height: 132px; padding: var(--sp-4) var(--sp-2); text-align: center;
}
.medal { min-height: 112px; padding-inline: 6px; }
.gear-tile b, .medal b { font-size: var(--t-meta); font-weight: 700; line-height: 1.25; }
.gear-sub { font-size: 12px; line-height: 1.3; color: var(--ink-2); overflow-wrap: anywhere; }
.gear-art { display: grid; place-items: center; width: 56px; height: 56px; margin-bottom: 4px; color: var(--line-2); }
.medal .gear-art { width: 48px; height: 48px; }
.gear-art svg { width: 100%; height: 100%; display: block; }
.gear-new { position: absolute; top: 8px; right: 8px; }
/* A row inside the Trophies card (Pins, Streak medals): a small head over its grid. */
.trophy-row-h { display: flex; align-items: baseline; justify-content: space-between; gap: 4px 12px; }
.trophy-row-h h3 { margin: 0; font-size: var(--t-meta); font-weight: 700; color: var(--ink-2); }
.medal-more { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.medal-mini { display: inline-flex; width: 40px; height: 40px; }
/* 3D pins (assets/pin-3d.js): a struck still in the tile, the live pin in its sheet. */
/* The struck pin sits inside a margin the flat art doesn't have: a touch larger to match. */
.pin-still { display: block; width: 116%; height: 116%; max-width: none; filter: drop-shadow(0 2px 2px color-mix(in srgb, var(--ink) 22%, transparent)); }
.pin-view { display: grid; place-items: center; width: min(280px, 100%); aspect-ratio: 1; margin: var(--sp-2) auto; }
.pin-view > svg { width: 86%; height: 86%; display: block; }
.pin-3d-canvas { display: block; touch-action: none; cursor: grab; filter: drop-shadow(0 10px 10px color-mix(in srgb, var(--ink) 22%, transparent)); }
.pin-3d-canvas:active { cursor: grabbing; }
.pin-view-how { text-align: center; }
.stage-colour .kit-swatches { grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
.stage-colour .lk-hint { margin-top: calc(-1 * var(--sp-2)); }
:is(.stage-gear, .stage-colour) .form-msg:empty { display: none; }
.next-row { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.next-art { flex: none; width: 56px; height: 56px; padding: 0; display: grid; place-items: center; color: var(--line-2); }
.next-art svg { width: 40px; height: 40px; }
.next-t { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.next-t b { font-size: var(--t-meta); font-weight: 700; }
.next-t b span { font-weight: 500; color: var(--ink-2); }
/* The locker door: 8 spots, 4 × 2. Placed stickers keep their tilt. */
.locker-door { position: relative; padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-3); }
.ld-spots { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-2); }
.ld-spot {
  position: relative; aspect-ratio: 1; min-width: 0; padding: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px; border: 0; background: none; color: var(--ink-3);
  font: inherit; border-radius: var(--r-inner);
}
.ld-spot:not(.is-full) { background: color-mix(in srgb, var(--ink) 5%, var(--surface-3)); }
button.ld-spot { cursor: pointer; transition: transform var(--dur-1) var(--ease-out); }
button.ld-spot:active { transform: scale(.95); }
.ld-plus { font-size: 20px; font-weight: 500; line-height: 1; }
.ld-art { display: block; width: 86%; aspect-ratio: 1; transform: rotate(var(--tilt, 0deg)); }
.ld-art svg { width: 100%; height: 100%; display: block; }
.ld-spot:has(.ld-from) .ld-art { width: 70%; }
.ld-from { max-width: 100%; font-size: 11px; font-weight: 600; line-height: 1.1; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lk-pile-item { padding-left: 4px; }
.lk-pile-art { display: inline-flex; width: 28px; height: 28px; }
.stage-rel > * { margin: 0; }

/* Medium: the stage spans the top; a shorter stage. */
@media (min-width: 768px) and (max-width: 1279.98px) {
  .stage { min-height: 520px; }
}
/* Compact: large title, the stage ~260 tall, Moves right under it, and the
   panels in one persistent sheet with three detents (peek / mid / full). */
@media (max-width: 767.98px) {
  .me-tabs { margin-bottom: var(--sp-3); }
  .stage {
    min-height: 0; padding: 0; background: none; border-radius: 0; overflow: visible;
    grid-template-rows: auto 260px auto; grid-template-areas: "head" "floor" "moves"; gap: var(--sp-3);
  }
  .stage::after { display: none; }
  .stage-head { grid-area: head; align-items: center; }
  .stage-name { font-size: var(--t-page); }
  .stage-sub { margin-top: 2px; font-size: var(--t-meta); }
  .stage-sub .stage-coll { display: inline; }
  .stage-share { width: var(--tap); padding: 0; }
  .stage-share span { display: none; }
  .stage-floor, .stage-lockers, .stage-stats { grid-area: floor; }
  .stage-floor {
    position: relative; overflow: hidden; padding: var(--sp-3);
    background: linear-gradient(to bottom, var(--surface-2) 66%, color-mix(in srgb, var(--ink) 5%, var(--surface-2)) 66%);
    border-radius: var(--r-sheet); align-items: end;
  }
  .stage-lockers { position: relative; z-index: 1; left: auto; bottom: auto; translate: none; align-self: start; justify-self: center; width: 58%; height: 52%; margin-top: 54px; pointer-events: none; }
  .stage-lockers { z-index: 1; }
  .stage-floor > * { position: relative; z-index: 2; }
  .stage-slots { gap: var(--sp-2); align-self: start; }
  .stage-slot-art { width: 48px; height: 48px; }
  .stage-slot-art svg { width: 30px; height: 30px; }
  .stage-av { width: 220px; justify-self: center; margin-left: -48px; }
  .stage-stats { align-self: start; justify-self: end; margin: var(--sp-3); z-index: 3; grid-template-columns: auto; }
  .stage-stats .is-wide { display: none; }
  .stage-stats .stat { align-items: flex-end; }
  .stage-moves { grid-area: moves; margin-inline: calc(-1 * var(--gutter)); padding-inline: var(--gutter); }
  .stage-moves-k { display: none; }

  .stage-sheet {
    position: fixed; z-index: calc(var(--z-tabbar) - 1); left: 0; right: 0;
    bottom: calc(var(--tabbar-h) + var(--safe-b));
    height: var(--sheet-h, 50svh); display: flex; flex-direction: column; overflow: hidden;
    background: var(--surface); border-radius: var(--r-sheet) var(--r-sheet) 0 0;
    clip-path: inset(-40px -40px 0 -40px);
    transition: height var(--dur-3) var(--ease-out);
  }
  .stage-sheet.is-drag { transition: none; }
  /* The top strip is the drag handle (no page scroll or pull-to-refresh
     from it); the chevron opens and closes the sheet. */
  .stage-sheet-top { display: flex; flex-direction: column; gap: var(--sp-1); padding: var(--sp-2) var(--gutter) var(--sp-3); cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; }
  .stage-grip { align-self: center; width: 40px; height: 5px; border-radius: var(--r-pill); background: var(--line-2); }
  .stage-sheet-bar { display: flex; align-items: center; gap: var(--sp-2); }
  .stage-sheet-bar .seg { flex: 1; }
  .stage-sheet-tog svg { transition: rotate var(--dur-2) var(--ease-out); }
  .stage-sheet[data-detent="peek"] .stage-sheet-tog svg { rotate: 180deg; }
  html:has([data-tpl="stage"]) { overscroll-behavior-y: none; }
  .stage-sheet > :is(.stage-l, .stage-r) { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 0 var(--gutter) var(--sp-5); gap: var(--sp-6); }
  
  .gear-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-2); }
  .stage-grid[data-pane="gear"] .stage-r,
  .stage-grid:not([data-pane="gear"]) .stage-l,
  .stage-grid[data-pane="locker"] [data-pane-of="trophies"],
  .stage-grid[data-pane="trophies"] [data-pane-of="locker"] { display: none; }
  /* The page under the sheet never scrolls past the stage. */
  [data-tpl="stage"] .profile-sec { display: none; }
}
@media (min-width: 768px) {
  .stage-sheet { display: contents; }
}
@media (prefers-reduced-motion: reduce) { .stage-sheet { transition: none; } }
/* ═══ me-stage END ═══ */

/* ══ §11 P2 components (CSS only) ════════════════════════════
   Styled now so phase 2 can drop markup in. Nothing renders these yet. */
/* ═══ p2 START ═══ */

/* Stories rail: one ring per group with game-day photos from the last
   48 h. Swipes sideways; never wraps. */
.stories-rail {
  display: flex; gap: var(--sp-3); overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; scrollbar-width: none;
  padding: 4px var(--sp-1) var(--sp-2); margin: 0 calc(-1 * var(--sp-1)) var(--sp-2);
}
.stories-rail::-webkit-scrollbar { display: none; }
.story-ring {
  transition: transform var(--dur-1) var(--ease-out);
  flex: none; width: 72px; scroll-snap-align: start;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--ink);
  font: inherit;
}
.story-ring .ring {
  width: 68px; height: 68px; border-radius: 50%; padding: 3px;
  background: var(--dink);
}
.story-ring.is-seen .ring { background: var(--line-2); }
.story-ring .ring > * {
  width: 100%; height: 100%; border-radius: 50%; display: block; object-fit: cover;
  border: 3px solid var(--ground); background: var(--surface-2);
}
.story-ring .story-label {
  font-size: 11.5px; font-weight: 600; max-width: 72px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Story viewer: full screen, tap left/right halves to go back/forward.
   Always dark: it is a photo viewer, not a page. */
.story-viewer {
  position: fixed; inset: 0; z-index: var(--z-sheet);
  display: flex; flex-direction: column;
  background: var(--ink); color: var(--white);
  padding: calc(var(--sp-2) + var(--safe-t)) var(--sp-3) calc(var(--sp-3) + var(--safe-b));
}
.story-progress { display: flex; gap: 4px; }
.story-progress span { flex: 1; height: 3px; border-radius: var(--r-pill); background: color-mix(in srgb, var(--white) 30%, transparent); overflow: hidden; }
.story-progress span::after {
  content: ""; display: block; height: 100%; width: calc(var(--p, 0) * 100%);
  background: var(--white);
}
.story-head { display: flex; align-items: center; gap: 10px; padding: var(--sp-3) 0; }
.story-head .story-group { font-weight: 700; }
.story-head .story-game { font-size: 12.5px; color: color-mix(in srgb, var(--white) 75%, transparent); }
.story-head .icon-btn { margin-left: auto; }
.story-media { position: relative; flex: 1; min-height: 0; display: grid; place-items: center; }
.story-media img, .story-media video { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--r-field); }
.story-tap { position: absolute; top: 0; bottom: 0; width: 50%; background: none; border: 0; cursor: pointer; }
.story-tap.is-prev { left: 0; }
.story-tap.is-next { right: 0; }
.story-caption { margin: var(--sp-3) 0 0; font-size: var(--fs-base); }
.story-foot { display: flex; justify-content: flex-end; padding-top: var(--sp-3); }

/* One-tap 🔥. No comments, ever. */
.fire-btn {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: var(--tap); padding: 0 14px; cursor: pointer;
  font: inherit; font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--ink-2); border: 0;
  border-radius: var(--r-pill);
  transition: transform var(--dur-1) var(--ease-out), background-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out); background: var(--surface-2); }
.fire-btn[aria-pressed="true"] { color: var(--on-lime-tint); background: var(--lime-tint); }
.fire-btn:active { transform: scale(.96); }
.story-viewer .fire-btn { color: var(--white); background: color-mix(in srgb, var(--white) 14%, transparent); }

/* Photo post card: a .feed-card with a 4:5 photo and a caption. */
.post-media {
  margin: 0 calc(-1 * var(--sp-4)); aspect-ratio: 4 / 5; overflow: hidden;
  background: var(--surface-2);
}
.post-media img, .post-media video { width: 100%; height: 100%; object-fit: cover; display: block; }
.post-caption { margin: 0; font-size: var(--fs-base); overflow-wrap: anywhere; }

/* Post composer: one screen. Photo, tag the game, caption, Post. */
.composer { display: flex; flex-direction: column; gap: var(--sp-4); }
.composer-drop {
  aspect-ratio: 4 / 5; max-height: 52svh; width: 100%;
  display: grid; place-items: center; gap: var(--sp-2); cursor: pointer;
  border: 0; border-radius: var(--r-card); color: var(--ink-2); font-weight: 600;
  overflow: hidden; background: var(--surface-2); }
.composer-drop img { width: 100%; height: 100%; object-fit: cover; }
.composer-tags { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* Result card extras: winners, score, your rating change. */
.result-score {
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xl); font-weight: 700; letter-spacing: -.02em;
}

/* Milestone / streak card: one big number, one line. */
.milestone-card .milestone-big {
  font-size: var(--fs-2xl); font-weight: 700; line-height: 1; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
}
.milestone-card .milestone-emoji {
  font-size: 20px; line-height: 1; display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 50%; background: var(--hi); color: var(--hi-ink);
}

/* Weekly leaderboard movement: who moved, from → to. */
.movement-list { display: flex; flex-direction: column; }
.movement-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 10px;
  min-height: var(--tap);
}
.movement-row:first-child { border-top: 0; }
.movement-row .move {
  font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 700;
}
.movement-row .move.is-up { color: var(--good); }
.movement-row .move.is-down { color: var(--ink-3); }

/* Share card preview (the PNG itself is drawn on a canvas). */
.share-card {
  aspect-ratio: 1 / 1; width: 100%; max-width: var(--w-card-sm); margin-inline: auto;
  border-radius: var(--r-card); overflow: hidden; box-shadow: var(--shadow-md);
  background: var(--surface-2);
}
.share-card canvas, .share-card img { width: 100%; height: 100%; display: block; }

/* Player profile: W-L headline, form pills, stat grid. */
.profile-rating {
  margin: 0; font-variant-numeric: tabular-nums;
  font-size: var(--fs-lg); font-weight: 700; letter-spacing: -.01em; line-height: 1.2; color: var(--ink-2);
}
.photo-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px; border-radius: var(--r-inner); overflow: hidden; }
.photo-grid img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }

/* ═══ p2 END ═══ */

a.gcard, .court-name a, .picker-btn { transition: transform var(--dur-1) var(--ease-out), border-color var(--dur-2) var(--ease-out); }
a.gcard:active { transform: scale(.985); }
.tp-h:active, .tp-m:active, .op-side:active, .story-ring:active,
.page-back:active, .court-name a:active, .picker-btn:active, .facet-row:active, .cp-item:active { transform: scale(.97); }

/* Live Open Play bento (index.html #opLive). Phone: one column, the two
   wrappers dissolve and the control bar and messages are pulled to the top.
   Wide: courts work in the left two thirds, the session panel on the right. */
.op-live[hidden] { display: none; }
.org-card .op-bar { padding-bottom: var(--sp-2); margin-bottom: var(--sp-1); }

/* ══ §12 Motion ═══════════════════════════════════════════════
   Owner: motion & shell. Sheet/dialog enter + exit, toast, route
   transitions, translucent chrome, phone filter sheet. assets/ui.js adds
   .is-closing / .is-dragging / #toast / html.is-scrolled; groups.js route()
   sets html[data-nav="push|pop|fade"] around document.startViewTransition. */
body { overscroll-behavior-y: none; }
[tabindex="-1"]:focus { outline: none; }
/* Animate off (Me › Settings, assets/motion.js): nothing moves. States
   swap instantly; the OS reduced-motion setting is honoured section by
   section below and in each module. */
html[data-motion="off"] *, html[data-motion="off"] *::before, html[data-motion="off"] *::after {
  animation: none !important; transition: none !important; scroll-behavior: auto !important;
}
html[data-motion="off"]::view-transition-group(*), html[data-motion="off"]::view-transition-old(*), html[data-motion="off"]::view-transition-new(*) { animation: none !important; }

/* ── toast ── */
.toast {
  position: fixed; left: 50%; z-index: var(--z-toast);
  bottom: calc(var(--tabbar-h) + var(--safe-b) + 16px);
  width: max-content; max-width: min(calc(100vw - 2 * var(--gutter)), var(--w-toast));
  padding: 10px 16px; border-radius: var(--r-pill);
  background: var(--ink); color: var(--ground);
  font-size: var(--fs-sm); font-weight: 600; text-align: center;
  box-shadow: var(--shadow-lg); pointer-events: none;
  opacity: 0; transform: translate(-50%, 8px);
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.toast.is-on { opacity: 1; transform: translate(-50%, 0); }
/* A one-shot Dink before the text on wins worth a smile (RSVP, join, post). */
.toast.has-mascot { display: flex; align-items: center; gap: 6px; padding-left: 8px; text-align: left; }
.toast.has-mascot dink-mascot, .toast.has-mascot img { flex: none; margin: -6px 0; }
@media (min-width: 768px) { .toast { bottom: 24px; } }
/* With an action (Undo): the toast takes taps, the button sits at the end,
   and a 2px bar drains along the bottom for the toast's lifetime. */
.toast.has-action { display: flex; align-items: center; gap: 12px; padding: 6px 6px 6px 16px; text-align: left; overflow: hidden; pointer-events: auto; }
.toast.has-action.has-mascot { padding-left: 8px; gap: 6px; }
.toast-text { min-width: 0; }
.toast-act {
  flex: none; margin-left: auto; min-height: 32px; padding: 0 12px; border: 0; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--ground) 18%, transparent); color: inherit; font: inherit; font-weight: 700; cursor: pointer;
}
.toast-act:hover { background: color-mix(in srgb, var(--ground) 28%, transparent); }
.toast-act:active { transform: scale(.96); }
.toast-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--accent); opacity: .7; transform-origin: left; transform: scaleX(0); }
.toast.has-action.is-on .toast-bar { animation: toast-drain var(--toast-ms, 5s) linear forwards; }
@keyframes toast-drain { from { transform: scaleX(1); } to { transform: scaleX(0); } }

/* A number that changed (PAC_UI.tick): rolls up into place. */
.is-ticking { display: inline-block; animation: num-tick var(--dur-3) var(--ease-out); }
@keyframes num-tick { 0% { opacity: 0; transform: translateY(.6em); } 100% { opacity: 1; transform: none; } }

/* The answer that just took a tap (PAC_UI.land) fills from the tap point. */
.btn--state.is-landing { animation: btn-land .48s var(--ease-out); }
@keyframes btn-land { from { clip-path: circle(0% at var(--fx, 50%) var(--fy, 50%)); } to { clip-path: circle(150% at var(--fx, 50%) var(--fy, 50%)); } }
.btn--state.is-landing .ico-check { animation: check-in var(--dur-3) var(--ease-out) .12s backwards; }
@keyframes check-in { from { opacity: 0; transform: scale(.4) rotate(-20deg); } to { opacity: 1; transform: none; } }

/* Copy buttons say "Copied" in place (PAC_UI.copied). */
.is-copied { color: var(--hi-strong); }
.is-copied .ico-check { animation: check-in var(--dur-3) var(--ease-out); }

/* The Going list: your row slides in when you say I'm in. */
.going-row.is-new { animation: row-in var(--dur-3) var(--ease-out); }
@keyframes row-in { from { opacity: 0; transform: translateX(-12px); } to { opacity: 1; transform: none; } }

/* Phone tab bar: the icon of the tab you just picked gives one small hop. */
@media (max-width: 767.98px) {
  .tab.is-hop .tab-ico { animation: tab-hop .42s var(--ease-out); }
}
@keyframes tab-hop { 0% { transform: none; } 35% { transform: translateY(-4px) scale(1.08); } 70% { transform: translateY(1px) scale(.98); } 100% { transform: none; } }

/* ── launch splash ── assets/splash.js drives the classes:
   is-drop (fall + bounce at screen centre, big) → is-move (the ball travels
   to its lockup spot as the letters pop out of it) → is-done (bubble) → is-out.
   --dx/--dy are measured in JS: centre of screen minus the ball's final spot. */
.splash { display: none; }
html.has-splash .splash {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: var(--ground); color: var(--ink); overflow: hidden;
  transition: opacity .35s var(--ease-out);
  /* Failsafe: if splash.js never runs, get out of the way anyway. */
  animation: sp-failsafe .3s 7s forwards;
}
.splash.is-out { opacity: 0; pointer-events: none; }
@keyframes sp-failsafe { to { opacity: 0; visibility: hidden; pointer-events: none; } }
.sp-lockup {
  --F: clamp(40px, 12vw, 84px);
  display: flex; align-items: center; font-size: var(--F); line-height: 1;
  font-weight: 700; letter-spacing: -.035em; white-space: nowrap;
}
.sp-word { display: inline-flex; }
.sp-l { margin-right: -.08em; }
.sp-r { margin-left: -.04em; }
.sp-ch { display: inline-block; opacity: 0; transform: translateY(.3em) scale(.6); }
.splash.is-move .sp-ch {
  animation: sp-ch .5s cubic-bezier(.2, .9, .3, 1.35) forwards;
  animation-delay: calc(120ms + var(--i) * 55ms);
}
@keyframes sp-ch { to { opacity: 1; transform: none; } }
/* The ball's box is 1.9em (the SVG keeps ~14% air round the ball), so the
   ball is ~1.6x cap height as in the logo; nudged up so its centre sits a
   touch above the cap-height middle. splash.js sizes the SVG to match. */
.sp-ball {
  position: relative; flex: none; width: 1.9em; height: 1.9em; margin-top: -.22em; line-height: 0;
  transform: translate(var(--dx, 0), var(--dy, 0)) scale(1.7);
  transition: transform .75s cubic-bezier(.65, 0, .25, 1);
}
.splash.is-move .sp-ball { transform: none; }
.sp-drop { display: block; opacity: 0; transform-origin: 50% 80%; }
.splash.is-drop .sp-drop { opacity: 1; animation: sp-drop 1.25s both; }
@keyframes sp-drop {
  0%   { transform: translateY(-80vh) scale(.94, 1.08); animation-timing-function: cubic-bezier(.55, 0, 1, .45); }
  36%  { transform: translateY(0) scale(1.2, .78); animation-timing-function: cubic-bezier(0, .55, .45, 1); }
  58%  { transform: translateY(-11vh) scale(.97, 1.04); animation-timing-function: cubic-bezier(.55, 0, 1, .45); }
  76%  { transform: translateY(0) scale(1.1, .88); animation-timing-function: cubic-bezier(0, .55, .45, 1); }
  87%  { transform: translateY(-3vh) scale(1, 1); animation-timing-function: cubic-bezier(.55, 0, 1, .45); }
  95%  { transform: translateY(0) scale(1.04, .96); }
  100% { transform: none; }
}
/* "Dink!" bubble: sits on the ball's top edge, starting ~70% across it,
   tail pointing down at it. Placed in --F units (the lockup's font size),
   not its own smaller em. */
.sp-bubble {
  position: absolute; left: calc(var(--F) * 1.2); bottom: calc(100% - var(--F) * .34);
  padding: .16em .55em .18em; border-radius: 999px; line-height: 1;
  font-size: calc(var(--F) * .34); letter-spacing: -.02em;
  background: var(--bubble); color: var(--ink);
  opacity: 0; transform: scale(.2); transform-origin: 25% 100%;
}
.sp-bubble::after {
  content: ""; position: absolute; left: 22%; bottom: -.16em; width: .4em; height: .4em;
  background: inherit; border-radius: 0 0 .1em 0; transform: rotate(45deg);
}
.splash.is-done .sp-bubble { animation: sp-pop .4s cubic-bezier(.2, .9, .3, 1.5) forwards; }
@keyframes sp-pop { to { opacity: 1; transform: none; } }

/* ── translucent chrome ── */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  /* The blur sits on a ::before layer, never on .masthead itself: a
     backdrop-filter makes its element the containing block for fixed
     descendants, and the phone tab bar (.tabs, fixed to the bottom) lives
     inside the masthead — it would pin itself to the header instead. */
  @media (max-width: 767px) {
    .masthead { background: transparent; isolation: isolate; }
    .masthead::before {
      content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
      background: color-mix(in srgb, var(--ground) 82%, transparent);
      -webkit-backdrop-filter: saturate(1.8) blur(18px); backdrop-filter: saturate(1.8) blur(18px);
    }
  }
}

/* ── phone filter sheet (courts .sidebar): always in the DOM, slides ── */
@media (max-width: 1279px) {
  aside.sidebar#sidebar {
    display: block; visibility: hidden; pointer-events: none;
    position: fixed; left: 0; right: 0; bottom: 0; top: auto; z-index: 40;
    max-height: min(82vh, 680px); overflow-y: auto; overscroll-behavior: contain;
    background: var(--surface); border-radius: var(--r-card) var(--r-card) 0 0;
    padding: 20px 16px calc(16px + var(--safe-b)); box-shadow: var(--shadow-lg);
    transform: translateY(100%);
    transition: transform var(--dur-3) var(--ease-in), visibility 0s linear var(--dur-3);
  }
  aside.sidebar#sidebar.open {
    visibility: visible; pointer-events: auto; transform: none;
    transition: transform var(--dur-3) var(--ease-out), visibility 0s;
  }
  aside.sidebar#sidebar .sidebar-apply { display: block; width: 100%; position: sticky; bottom: 0; margin-top: 10px; }
  #sidebarBackdrop {
    display: block; position: fixed; inset: 0; z-index: 39;
    background: var(--scrim); opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity var(--dur-2) var(--ease-out), visibility 0s linear var(--dur-2);
  }
  #sidebarBackdrop.show {
    opacity: 1; visibility: visible; pointer-events: auto;
    transition: opacity var(--dur-2) var(--ease-out), visibility 0s;
  }
}

@media (prefers-reduced-motion: no-preference) {
  /* ── sheets + dialogs ── */
  .overlay[open] { animation: sheet-in-up var(--dur-3) var(--ease-out); }
  .overlay[open].is-closing { animation: sheet-out-down var(--dur-2) var(--ease-in) forwards; }
  .overlay[open]::backdrop { animation: scrim-in var(--dur-2) var(--ease-out); }
  .overlay[open].is-closing::backdrop { animation: scrim-out var(--dur-2) var(--ease-in) forwards; }
  .overlay.is-dragging { transition: none; animation: none; }
  .overlay--s[open] { animation: dlg-in var(--dur-2) var(--ease-out); }
  .overlay--s[open].is-closing { animation: dlg-out var(--dur-2) var(--ease-in) forwards; }
  .overlay--s[open]::backdrop { animation: scrim-in var(--dur-2) var(--ease-out); }
  .overlay--s[open].is-closing::backdrop { animation: scrim-out var(--dur-2) var(--ease-in) forwards; }
  @media (min-width: 768px) {
    .overlay[open] { animation-name: dlg-in; animation-duration: var(--dur-2); }
    .overlay[open].is-closing { animation-name: dlg-out; }
  }

  /* ── route transitions (View Transitions API) ── */
  html[data-nav] main > .gview:not([hidden]),
  html[data-nav] #view-courts:not([hidden]) { view-transition-name: page; }
  html[data-nav] .masthead { view-transition-name: masthead; }
  html[data-nav] .tabs { view-transition-name: tabbar; }
  /* Only one name per element: the page owns its descendants while navigating. */
  html[data-nav] .grid, html[data-nav] .table-wrap, html[data-nav] .map-wrap,
  html[data-nav] [role="tabpanel"] { view-transition-name: none; }

  ::view-transition-group(masthead), ::view-transition-group(tabbar),
  ::view-transition-new(masthead), ::view-transition-new(tabbar) { animation: none; }
  ::view-transition-old(masthead), ::view-transition-old(tabbar) { display: none; }

  /* One transition for every route, phone and desktop: the old page fades out,
     the new one fades in with a 6px settle. The group itself never animates,
     or the browser morphs between pages of different size (the "corner" swoop).
     Capped at 150ms (FEEL-03): the pseudo overlay swallows taps while it runs,
     and pointer-events: none lets the next tap reach the real page. */
  ::view-transition { pointer-events: none; }
  html[data-nav] ::view-transition-group(page) { animation: none; }
  html[data-nav] ::view-transition-old(page) { animation: vt-fade-out .1s var(--ease-out) both; }
  html[data-nav] ::view-transition-new(page) { animation: vt-settle-in .15s var(--ease-out) both; }
}

@keyframes sheet-in-up { from { transform: translateY(100%); } to { transform: none; } }
@keyframes sheet-out-down { from { transform: translateY(var(--drag-y, 0px)); } to { transform: translateY(100%); } }
@keyframes dlg-in { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: none; } }
@keyframes dlg-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(.96); } }
@keyframes scrim-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes scrim-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes vt-settle-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes vt-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes vt-fade-out { from { opacity: 1; } to { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .toast, aside.sidebar#sidebar, #sidebarBackdrop { transition: none; }
  .is-ticking, .btn--state.is-landing, .btn--state.is-landing .ico-check, .is-copied .ico-check, .going-row.is-new,
  .tab.is-hop .tab-ico, .fav.is-burst svg, .fav.is-burst[aria-pressed="true"]::after { animation: none; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}
/* ══ end §12 ══ */

/* ── Live card ── Home's Live Activity (renderHome, assets/groups.js): an Ink card. */
.live-stack { display: grid; gap: 10px; margin: 0 0 14px; }
.live-stack:empty { display: none; }
.live-card {
  display: grid; gap: 12px; padding: 15px 16px; border-radius: var(--r-card);
  background: var(--ink); color: var(--white); text-decoration: none; min-width: 0;
}
.la-link { display: grid; gap: 12px; min-width: 0; color: inherit; text-decoration: none; }
.live-card:has(.la-link:active) { transform: scale(.985); }
.la-head { display: flex; align-items: center; gap: 10px; min-width: 0; }
.la-tile { flex: none; display: inline-flex; }
.la-id { flex: 1; min-width: 0; display: grid; gap: 1px; }
.la-name { font-weight: 700; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.la-kind { font-size: 12px; color: var(--inv-ink-2); }
.la-live { flex: none; display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 700; letter-spacing: .06em; color: var(--dink); }
.la-el { font-weight: 600; letter-spacing: 0; color: var(--inv-ink-2); font-variant-numeric: tabular-nums; margin-left: 2px; }
.la-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--dink); animation: la-pulse 1.6s ease-out infinite; }
@keyframes la-pulse { 50% { opacity: .35; } }
.la-board { display: grid; grid-template-columns: minmax(0,1fr) auto minmax(0,1fr); align-items: center; gap: 10px; }
.la-team { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: 14px; font-weight: 600; line-height: 1.25; }
.la-t0 { justify-content: flex-start; text-align: left; }
.la-t1 { justify-content: flex-end; text-align: right; flex-direction: row-reverse; }
.la-tn { min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
.la-ball { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--dink); }
.la-sno { flex: none; font-size: 11px; font-weight: 700; color: var(--inv-ink-2); font-variant-numeric: tabular-nums; }
.la-score { display: inline-flex; align-items: baseline; gap: 8px; font-size: 38px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
.la-dash { color: var(--inv-ink-2); font-weight: 600; }
.la-num { display: inline-block; }
.la-num.is-lead { color: var(--dink); }
.la-num.pop { animation: la-pop .25s ease-out; }
@keyframes la-pop { 0% { transform: scale(1); } 40% { transform: scale(1.3); } 100% { transform: scale(1); } }
.la-courts { display: grid; gap: 6px; }
.la-court { display: grid; grid-template-columns: auto minmax(0,1fr) auto; align-items: center; gap: 8px; font-size: 13px; }
/* The matchup flows as one phrase, so "vs" hugs both teams. */
.la-match { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.la-match .la-cn { min-width: 0; flex: 0 1 auto; }
.la-vs { flex: none; }
.la-cno { font-weight: 700; color: var(--inv-ink-2); white-space: nowrap; }
.la-cn { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.la-vs { font-size: 11px; color: var(--inv-ink-2); }
.la-ct { color: var(--inv-ink-2); font-variant-numeric: tabular-nums; white-space: nowrap; }
.la-foot { margin: 0; font-size: 13px; color: var(--inv-ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.la-foot.is-me { color: var(--dink); font-weight: 700; }
.live-more { margin: 0; text-align: center; font-size: 13px; color: var(--ink-2); }
@media (prefers-reduced-motion: reduce) { .la-dot, .la-num.pop { animation: none; } .live-card:has(.la-link:active) { transform: none; } }

/* ── P5: rank marks, guest tag, win odds, session results card ── */
.rank-medal { font-size: 18px; line-height: 1; }
.rank-num { font-size: 13px; font-weight: 700; color: var(--ink-3); }
.lb-row .lb-rank { display: inline-flex; justify-content: center; }
.guest-tag {
  flex: none; display: inline-flex; align-items: center; height: 18px; padding: 0 7px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .02em; line-height: 1;
  color: var(--ink-2); background: var(--surface-2); 
}
.lb-row .lb-name .guest-tag { overflow: visible; }
.court-team .odds { margin-left: auto; padding-left: var(--sp-2); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); font-weight: 600; color: var(--ink-3); flex: none; }

/* Session results: an Ink card (Smash's results). */
.rcard {
  --rc-dim: var(--inv-ink-2); --rc-line: var(--inv-line);
  margin: 0 0 var(--sp-3); padding: var(--sp-5, 24px) var(--sp-4, 16px) var(--sp-4, 16px);
  border-radius: var(--r-card); background: var(--ink); color: var(--white);
  min-width: 0; overflow: hidden;
}
.rcard-group { margin: 0; font-size: 13px; font-weight: 700; color: var(--rc-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rcard-title { margin: 2px 0 0; font-size: 28px; font-weight: 700; letter-spacing: -.02em; line-height: 1.1; }
.rcard-cap { margin: 4px 0 0; font-size: 13px; color: var(--rc-dim); }
.rcard-hero { position: relative; width: 72px; margin: var(--sp-4, 16px) auto; transform: translateX(12px); }
.rcard-dink { position: absolute; left: -22px; top: -4px; }
.rcard-trophy { position: relative; display: block; }
.rcard-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.rc-stat { padding: 14px 8px; border-radius: var(--r-field); background: var(--rc-line); text-align: center; min-width: 0; }
.rcard .rc-stat b { display: block; font-size: 30px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; color: var(--cream); }
.rc-stat span { display: block; margin-top: 6px; font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--rc-dim); }
.rcard-rows { margin-top: var(--sp-3, 12px); }
.rcard-row {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; align-items: center; gap: 10px;
  min-height: 52px; padding: 6px 0;
}
.rcard-row.is-me { background: color-mix(in srgb, var(--dink) 16%, transparent); border-radius: var(--r-field); padding-inline: 8px; margin-inline: -8px; }
.rcard-rank { text-align: center; }
.rcard .rank-num { color: var(--rc-dim); }
.rcard-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.rcard-name { min-width: 0; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rcard .guest-tag { color: var(--rc-dim); background: var(--rc-line); }
.rcard-wins { text-align: right; line-height: 1.1; }
.rcard-wins b { display: block; font-size: 20px; font-weight: 700; color: var(--dink); font-variant-numeric: tabular-nums; }
.rcard-wins small { font-size: 11px; color: var(--rc-dim); }
.rcard-more {
  display: block; width: 100%; min-height: var(--tap, 44px); margin-top: 2px; padding: 0; border: 0;
  background: none; color: var(--rc-dim); font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
}
.rcard-more:hover, .rcard-more:focus-visible { color: var(--cream); }
.rcard-more:active { transform: scale(.97); }
.rcard-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-top: var(--sp-3, 12px); padding-top: 12px; }
.rcard-where { min-width: 0; font-size: 12.5px; color: var(--rc-dim); overflow-wrap: anywhere; }
.rcard-brand { display: inline-flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--rc-dim); }
.rcard-brand img { height: 18px; width: auto; display: block; }
/* Share: an icon button in the card's top right corner. */
.rcard { position: relative; }
.rcard :is(.rcard-group, .rcard-title) { padding-right: 48px; }
.rcard-share {
  position: absolute; top: 12px; right: 12px; display: grid; place-items: center;
  width: var(--tap, 44px); height: var(--tap, 44px); padding: 0; border: 0; border-radius: 50%;
  background: var(--rc-line); color: var(--white); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.rcard-share:hover, .rcard-share:focus-visible { background: color-mix(in srgb, var(--white) 22%, var(--ink)); }
.rcard-share:active { transform: scale(.96); }
.rcard-share svg { width: 18px; height: 18px; }

/* ── P5 host + board (openplay.js / scoreboard) ── */
.last-result { margin-top: var(--sp-3); padding: var(--sp-3) var(--sp-4); border-radius: var(--r-inner); background: var(--surface-2); border-top: 0; }
.last-result .last-k { display: block; font-size: var(--fs-xs); color: var(--ink-3); font-weight: 600; }
.bo-rank { white-space: nowrap; }
.op-lb-name .guest-tag, .bo-nm .guest-tag { margin-left: .4em; vertical-align: middle; }
/* courtside board odds: secondary to names, still readable across the court */
.bo-team .odds { display: block; margin-top: .1em; font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: .62em; letter-spacing: .02em; color: var(--ink-2); }
.bo-court.is-playing .bo-team .odds { color: var(--good); }

/* ── Insights (#/insights, renderInsights): four stat tiles, then wins per
   week beside your partners. On a phone it is reached from Me, so its tab
   stays out of the five-tab bar. */
#view-insights { max-width: 1180px; }
.my-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-bottom: 16px; }
@media (min-width: 1024px) { .my-tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.my-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
@media (min-width: 1024px) { .my-grid { grid-template-columns: minmax(0, 1fr) 360px; } }
/* --half: two equal cards a row, the same height (the person profile). */
@media (min-width: 1024px) { .my-grid--half { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; } }
/* On a phone Insights, Me and Admin open from the account button (VB), so the bar keeps four tabs. */
@media (max-width: 767.98px) { #tab-insights, #tab-me, #tab-admin { display: none; } }

/* ── Group Insights (#/g/<gid>/insights, groupInsights): the same tiles,
   then one card per question down the lead column. The column's gap
   spaces them. */
.gi-tiles { margin-bottom: 0; }

/* ── Insights (admin) ── quiet tiles, plain ranked rows, a 7×24 heatmap. */
a.sync-act { text-decoration: none; }
/* ── Admin dashboard (assets/insights.js) ──
   Tabs over one 12-column grid: cards in a row line up and share a height.
   One column below 720px of the view; spans halve up to 1040px. The §0
   parts carry the look (card, stat tile, list row and its bar, seg); this
   block lays them out and draws the dashboard's own marks: the traffic
   chart, the two-part split bar and the heatmaps. Series colours are the
   validated two-slot pair (blue / orange), their own steps per theme;
   text never wears them. */
#view-admin {
  container-type: inline-size;
  --dash-1: #2a78d6; --dash-2: #eb6834;
}

.ins-period { margin: 0; }
.ins-period[hidden] { display: none; }
.dash { display: flex; flex-direction: column; gap: var(--sp-5); min-width: 0; }
.dash-tabs-wrap { min-width: 0; overflow-x: auto; scrollbar-width: none; }
.dash-tabs-wrap::-webkit-scrollbar { display: none; }
.dash-body { min-width: 0; transition: opacity var(--dur-2) var(--ease-out); }
.dash-body[aria-busy="true"]:not(:empty) { opacity: .5; }
.dash-panel, .dash-block { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.dash-panel[hidden] { display: none; }
.dash-panel > .dash-block { margin-top: var(--sp-4); }

.dash-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
.dash-col { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.dash-col > .card { flex: 1 1 auto; }
/* A card holding only an empty state keeps its own height, not its row's. */
.dash-grid > .card:has(> .empty-inline) { align-self: start; }
@container (min-width: 720px) {
  .dash-grid { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .dash-grid > * { grid-column: span 6; }
  .dash-grid > .dash-span-12 { grid-column: 1 / -1; }
}
@container (min-width: 1040px) {
  .dash-grid > .dash-span-4 { grid-column: span 4; }
  .dash-grid > .dash-span-5 { grid-column: span 5; }
  .dash-grid > .dash-span-7 { grid-column: span 7; }
  .dash-grid > .dash-span-8 { grid-column: span 8; }
}

/* stat tiles: two up on a phone, three on a tablet, one row on a wide view */
.dash-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
@container (min-width: 720px) { .dash-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@container (min-width: 1040px) { .dash-tiles { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } }

/* ranked lists: list rows, each with its bar; rows past eight wait for "Show all" */
.dash-list { margin: 0; padding: 0; list-style: none; }
.dash-list > [hidden] { display: none; }
.dash-list > .list-row { align-items: start; }
.dash-list .list-rank, .dash-list .list-end { line-height: 1.45; }
.dash-more { align-self: center; }

.dash-foot { margin: 0; font-size: var(--fs-xs); font-weight: 500; color: var(--ink-3); }
.dash-status { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }
.dash-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* legends: a swatch or line key beside the words, never coloured text */
.dash-legend { display: flex; flex-wrap: wrap; gap: 4px 16px; font-size: var(--fs-xs); font-weight: 500; color: var(--ink-3); }
.dash-legend > span { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.dash-legend b { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.ins-key { flex: none; display: inline-block; width: 14px; height: 2px; border-radius: var(--r-pill); background: var(--dash-1); }
.ins-key-devices { background: var(--dash-2); }
.dash-swatch { flex: none; display: inline-block; width: 10px; height: 10px; border-radius: 50%; }

/* the split bar: two parts of one whole, a 2px gap between them */
.dash-split { display: flex; flex-direction: column; gap: var(--sp-2); }
.dash-split-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); }
.dash-split-head b { font-size: var(--fs-lg); font-weight: 700; color: var(--ink); }
.dash-split-bar { display: flex; gap: 2px; height: 12px; }
.dash-split-bar > i { flex-basis: 0; min-width: 4px; border-radius: var(--r-pill); }
.dash-split-bar.is-empty { border-radius: var(--r-pill); background: var(--surface-2); }
.dash-k1 { background: var(--dash-1); }
.dash-k2 { background: var(--dash-2); }

/* heatmaps: one hue, light to dark over --surface-2 */
.dash-heat { display: grid; grid-template-columns: 30px repeat(var(--cols), minmax(0, 1fr)); gap: 2px; align-items: center; }
.dash-heat-h { min-width: 0; font-size: 10px; font-weight: 600; color: var(--ink-3); white-space: nowrap; }
.dash-heat-d { font-size: 11px; font-weight: 600; color: var(--ink-3); }
.dash-cell { display: block; height: 16px; border-radius: var(--r-pill); font-style: normal; background: color-mix(in srgb, var(--dash-1) calc(var(--o, 0) * 100%), var(--surface-2)); }
.dash-cell.is-zero { background: var(--surface-2); }
.dash-heat.is-labelled { grid-template-columns: 30px repeat(var(--cols), minmax(0, 1fr)); }
.dash-heat.is-labelled .dash-heat-h { text-align: center; }
.dash-heat.is-labelled .dash-cell { height: 34px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.dash-heat.is-labelled .dash-cell.is-dark { color: var(--white); }
.dash-scale { display: flex; align-items: center; gap: 3px; font-size: 11px; font-weight: 500; color: var(--ink-3); }
.dash-scale .dash-cell { width: 16px; height: 10px; }
.dash-scale > span:first-child { margin-right: 4px; }
.dash-scale > span:last-child { margin-left: 4px; }

/* the one true table (answers per day): hairline rows, numbers right */
.dash-table-wrap { overflow-x: auto; }
.dash-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.dash-table th, .dash-table td { padding: 10px 0 10px 16px; text-align: right; white-space: nowrap; }
.dash-table :is(th, td):first-child { padding-left: 0; text-align: left; }
.dash-table thead th { padding-top: 0; font-size: var(--fs-xs); font-weight: 600; color: var(--ink-3); }
.dash-table tbody th { font-weight: 600; }

/* the partner report's pickers: two selects, the downloads on the right */
.dash-picks { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sp-3); }
.dash-picks .field-row { flex: 1 1 180px; max-width: 280px; }
.dash-picks .field-row select { width: 100%; }
.dash-picks > .dash-actions { margin-left: auto; }
@container (max-width: 560px) { .dash-picks .field-row { max-width: none; } .dash-picks > .dash-actions { margin-left: 0; } }

/* To review on Overview: each item a card, up to three across */
.dash-review[hidden] { display: none; }
.dash-review[data-rvhost] { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); align-items: start; }
@container (min-width: 720px) { .dash-review[data-rvhost] { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container (min-width: 1040px) { .dash-review[data-rvhost] { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.dash-review > :not(.rv-item) { grid-column: 1 / -1; }

/* the traffic chart */
.ins-chart { position: relative; min-height: 200px; }
.ins-svg { display: block; max-width: 100%; overflow: visible; touch-action: pan-y; }
.ins-svg:focus { outline: none; }
.ins-svg:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; border-radius: var(--r-inner); }
.ins-axis text { font-size: 11px; font-weight: 600; fill: var(--ink-3); font-variant-numeric: tabular-nums; }
.ins-grid { stroke: var(--line); stroke-width: 1; shape-rendering: crispEdges; }
.ins-base { stroke: var(--line-2); stroke-width: 1; shape-rendering: crispEdges; }
.ins-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ins-line.ins-s-opens { stroke: var(--dash-1); }
.ins-line.ins-s-devices { stroke: var(--dash-2); }
.ins-area { fill: var(--dash-1); opacity: .1; }
.ins-dot { stroke: var(--surface); stroke-width: 2; }
.ins-dot.ins-s-opens { fill: var(--dash-1); }
.ins-dot.ins-s-devices { fill: var(--dash-2); }
.ins-cross { stroke: var(--ink-3); stroke-width: 1; shape-rendering: crispEdges; }
.ins-tip {
  position: absolute; z-index: 2; pointer-events: none; min-width: 120px;
  background: var(--surface); border: 0; border-radius: var(--r-inner);
  padding: 8px 10px; font-size: var(--fs-sm); box-shadow: var(--sh-float); }
.ins-tip-day { font-size: var(--fs-xs); font-weight: 600; color: var(--ink-2); margin-bottom: 2px; }
.ins-tip-row { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.ins-tip-row b { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ink); }
.ins-tip-row span { color: var(--ink-2); }

/* ── Up next: the next game, already split into sides, tops the line. */
.op-next { display: grid; gap: 4px; padding: 10px 12px; margin-bottom: 6px; border-radius: var(--r-inner); background: var(--hi-wash); }
.op-next-label { margin: 0; font-size: var(--fs-xs); font-weight: 600; color: var(--ink-3); }
.op-next .court-team .names { font-weight: 600; }
.op-next-name { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; text-align: left; }
/* Names in the next game and on courts open that player's menu (swap,
   break, lock, leave): a soft pill of the text colour so they read as
   buttons on the light Up next card and the dark court card alike. */
.op-next-name { display: inline-block; margin: 2px 0; padding: 2px 10px; border-radius: var(--r-pill, 999px); line-height: 1.35;
  background: color-mix(in srgb, currentColor 9%, transparent);
  transition: background var(--dur-1, .12s) var(--ease-out, ease-out), box-shadow var(--dur-1, .12s) var(--ease-out, ease-out); }
.op-next-name:hover, .op-next-name:focus-visible { background: color-mix(in srgb, currentColor 16%, transparent); }

/* Board: the next game tops "Up next", split into sides. */
.bo-next-game { display: grid; gap: .1em; padding: .4em .6em; margin-bottom: .3em; border-radius: var(--r-inner); background: var(--hi-wash); font-size: 1.3em; font-weight: 700; }
.bo-next-label { font-size: .6em; font-weight: 600; color: var(--ink-3); }
.bo-vs { font-size: .6em; font-weight: 600; color: var(--ink-3); }

/* ── Organizer swap + announce (docs/live-extras.md) ── */
.op-pick-group { margin: 18px 0 8px; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); }
.op-pick-group:first-child { margin-top: 0; }
/* Sound switch (control bar) and the speaker in a card's head. */
.op-head { gap: 6px; }
.op-head .card-title { margin-right: auto; }
.op-sound svg { width: 18px; height: 18px; }
.op-sound .op-sound-wave { display: none; }
.op-sound[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
.op-sound[aria-pressed="true"] .op-sound-wave { display: inline; }
.op-sound[aria-pressed="true"] .op-sound-off { display: none; }
#opAnnounceMsg { margin: 0; }
/* A head button keeps its 44px target without making the head taller, so
   every card title sits at the same height (Courts level with Leaderboard). */
.op-head > .btn--quiet, .op-head > .op-call { flex: none; margin-block: -10px; }
/* An empty status line takes no room (it would add a gap above Courts). */
#opMsg:empty { display: none; }
.court-card-head .op-call { flex: none; margin: -10px 0; }   /* full 44px target; the head keeps its height */

/* Court states (host view). Called: inverted card, "Waiting m:ss" from the
   call, one full-width Start, the rest quiet under it. Playing: the clock
   is the status (the header pill already says the session is live).
   Free: dashed and recessive. Members and spectators keep the plain look. */
#opCourts .court-card.is-called { background: var(--ink); color: var(--ground); }
#opCourts .court-card.is-called .court-vs,
#opCourts .court-card.is-called .court-team .odds,
#opCourts .court-card.is-called .court-timer,
#opCourts .court-card.is-called .op-call { color: var(--inv-ink-2); }
#opCourts .court-card.is-called .court-vs::before,
#opCourts .court-card.is-called .court-vs::after { background: var(--inv-line); }
#opCourts .court-card.is-called .court-timer.is-late { color: var(--inv-late); font-weight: 700; }
#opCourts .court-cue { margin: -4px 0 0; font-size: var(--fs-xs); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--inv-cue); }
#opCourts .court-card.is-called .court-card-actions { flex-direction: column; gap: 2px; }
#opCourts .court-card.is-called .court-card-actions .btn { width: 100%; }
#opCourts .court-minor { display: flex; justify-content: center; gap: var(--sp-1); }



#opCourts .court-card.is-playing .court-timer { font-size: var(--fs-lg); font-weight: 700; color: var(--ink); }
#opCourts .court-card.is-playing .court-card-actions .btn--soft { flex: 1 1 auto; }
#opCourts .court-card.is-free { background: transparent; border-style: dashed; }
#opCourts .court-card.is-free .court-card-head h3 { color: var(--ink-2); }
/* The court title renames the court (host only). A quiet pencil says it's
   tappable; the full 44px target comes from negative block margins. */
.court-rename { all: unset; box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px; margin-block: -10px; max-width: 100%; cursor: pointer; font: inherit; color: inherit; overflow-wrap: anywhere; border-radius: var(--r-field); }
.court-rename::after { content: ""; flex: none; width: 14px; height: 14px; background: currentColor; opacity: .45;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 20h9'/%3E%3Cpath d='M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 20h9'/%3E%3Cpath d='M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z'/%3E%3C/svg%3E") center / contain no-repeat;
  transition: opacity var(--dur-1) var(--ease-out); }
.court-rename:hover::after { opacity: .8; }
.court-rename:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* ── Announcements (docs/live-extras.md) ── */
.board-ctl { flex-wrap: wrap; justify-content: flex-end; }
.ann-status { flex-basis: 100%; margin: 0; text-align: right; font-size: 13px; font-weight: 600; color: var(--ink-2); }
.ann-status:empty { display: none; }

/* ── Spectator live page (docs/live-extras.md) ── */
.live-page { max-width: calc(var(--w-page) + 2 * var(--gutter)); margin: 0 auto; padding: var(--sp-4) var(--gutter) calc(var(--sp-6) + env(safe-area-inset-bottom)); }
.live-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-4); }

.live-sound { grid-column: 2; grid-row: 1; display: flex; flex-direction: column; align-items: flex-end; }
.live-sound .op-hint { margin: 0; text-align: right; }
.live-sound .op-hint:empty { display: none; }

.live-page [hidden] { display: none; }
.live-join { display: grid; gap: var(--sp-2); justify-items: start; }
.live-join p { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); color: var(--ink-2); }
.live-noscript { padding: var(--sp-4); color: var(--ink-2); }
/* Share sheet (#liveShareDlg): two labelled choices. */
.share-opt { flex-direction: column; align-items: flex-start; gap: 2px; height: auto; min-height: 64px; padding: 10px 16px; text-align: left; white-space: normal; }
.share-opt b { font-weight: 700; }
.share-opt small { font-size: var(--fs-sm); font-weight: 500; color: var(--ink-2); }

/* ── court board (docs/court-boards.md) ── */
/* One iPad, one court: the header says "Court N", the single card fills the
   screen, and everything about other courts or the queue is gone. */
body.is-court-board .bo-stats,
body.is-court-board .bo-side,
body.is-court-board .bo-break,
body.is-court-board .bo-court-no { display: none; }
body.is-court-board .bo-main { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); }
/* Beats the portrait rule that forces two columns of courts. */
body.is-court-board .bo-courts { --bo-cols: 1 !important; }
body.is-court-board .bo-title { font-size: 2em; }
body.is-court-board .bo-court { padding: 1.2em 1.4em; gap: .8em; justify-content: center; }
body.is-court-board .bo-court-top { justify-content: flex-end; }
body.is-court-board .bo-status { font-size: 1.1em; }
/* Sized from the viewport so names read from the baseline and still fit. */
body.is-court-board .bo-teams { gap: .3em; }
body.is-court-board .bo-team { font-size: clamp(2.6em, min(8vh, 9vw), 5.5em); line-height: 1.1; }
body.is-court-board .bo-vs { font-size: .9em; margin: .3em 0; }
body.is-court-board .bo-timer { font-size: 4.5em; }
body.is-court-board .bo-timer.is-call { font-size: 2.2em; }
body.is-court-board .bo-court-free { font-size: 2.4em; }
body.is-court-board .bo-team .odds { font-size: .5em; }

/* Overview only: the running score of a court being scored live takes the
   odds' slot, a little larger and bolder. A court board never shows it,
   because a scored match there takes the whole screen. */
.bo-team .odds.bo-live-score { font-size: .8em; line-height: 1; font-weight: 700; color: var(--good); }

/* ── board link: court chips (docs/court-boards.md) ── */
.board-courts { margin: 4px 0 12px; }
.board-courts .dlg-hint { margin-top: 8px; }

/* ═══ help START ═══ */
/* Owner: help (assets/help.js, route #/help). A static how-to. Mockups are
   drawn with the app's own tokens, so they follow the theme; they are
   pictures, so they are aria-hidden and take no taps. */
/* help.html, the static copy (tools/build-help.js): logo and a way into the app. */
/* Static pages (help.html, courts/<id>/): header and main share one width,
   so the logo and the button sit on the content's edges. */
.kb-static .wrap { max-width: calc(var(--w-page) + 2 * var(--gutter)); }
.kb-static-bar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); padding-block: var(--sp-4); }
.kb-static-bar .btn { text-decoration: none; }
.kb-static #main { padding-bottom: var(--sp-7); }
/* Terms and Privacy (Set 6): static pages in the help page's frame */
.legal { display: grid; gap: var(--sp-4); max-width: var(--w-read); }
.legal ul { margin: 0; padding-left: 1.2em; display: grid; gap: var(--sp-2); }
.legal p { margin: 0; }
.legal-links { font-size: var(--t-meta); color: var(--ink-2); }
.kit-sheet .overlay-sub a { color: var(--ink); text-underline-offset: 2px; }
.si-legal { margin: var(--sp-3) 0 0; text-align: center; }
.me-data .legal-links, .me-account .legal-links { margin: 0; }
.legal-links a, .legal-body a { color: var(--ink); text-underline-offset: 2px; }
/* One block of text: the sections are headings inside a single card, not cards. */
.legal-body { gap: 26px; font-size: var(--t-body); line-height: 1.55; color: var(--ink-2); }
.legal-body > section { display: grid; gap: var(--sp-2); }
.legal-body h2 { margin: 0; font-size: var(--t-card); font-weight: 600; line-height: 1.3; color: var(--ink); }
.legal-body b { color: var(--ink); font-weight: 600; }
/* ── How it works: a sticky table of contents beside one reading column ──
   help.js html(). Phone: the topics are a rail pinned under the masthead
   (--kb-top, measured by help.js), the one in view marked and the rail
   scrolled to it. From an 880px view: a sidebar on the left, grouped by
   stage (Start, Book a court … Account), sticky and scrolling on its own.
   Links are plain anchors, so the static help.html works without script. */
.kb-doc { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); align-items: start; }
.kb-body { min-width: 0; display: flex; flex-direction: column; gap: var(--stack-sec); }
.kb-nav {
  position: sticky; top: var(--kb-top, calc(var(--safe-t) + 64px)); z-index: var(--z-sticky);
  margin-inline: calc(-1 * var(--gutter)); padding: var(--sp-2) 0;
  background: var(--ground);
}
.kb-static .kb-nav { --kb-top: 0px; }
.kb-nav-h, .kb-nav-group { display: none; }
.kb-nav-in {
  display: flex; gap: var(--sp-2); overflow-x: auto; overscroll-behavior-x: contain;
  padding-inline: var(--gutter); scroll-padding-inline: var(--gutter); scrollbar-width: none;
}
.kb-nav-in::-webkit-scrollbar { display: none; }
/* A rail link has the chip's look; the one in view is the pressed chip. */
.kb-nav-link {
  flex: none; display: inline-flex; align-items: center; min-height: 34px; padding: 0 14px;
  border-radius: var(--r-pill); background: var(--surface-2); color: var(--ink);
  font-size: 13.5px; font-weight: 500; line-height: 1.2; white-space: nowrap; text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out);
}
.kb-nav-link:hover { background: color-mix(in srgb, var(--ink) 9%, var(--surface-2)); }
.kb-nav-link[aria-current="true"] { background: var(--ink); color: var(--surface); font-weight: 600; }
.kb-nav-link:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* Under the pinned rail, a jump lands with its heading in view. */
.kb-sec { container: kbsec / inline-size; }
.kb-sec, .kb-topic { scroll-margin-top: calc(var(--kb-top, calc(var(--safe-t) + 64px)) + 66px); }
.kb-static :is(.kb-sec, .kb-topic) { scroll-margin-top: 66px; }
.kb-next { margin: var(--sp-5) 0 0; }
/* Pop's "From Help" link: the topic glows for a moment where it lands. */
.kb-topic.is-found { animation: kb-found 1.6s var(--ease-out); border-radius: var(--r-inner); }
@keyframes kb-found { 0%, 30% { background: var(--lime-tint); } 100% { background: transparent; } }
@media (prefers-reduced-motion: reduce) { .kb-topic.is-found { animation: none; } }

@container view (min-width: 880px) {
  .kb-doc { grid-template-columns: 220px minmax(0, 1fr); gap: var(--sp-7); }
  .kb-nav {
    top: var(--sticky-top); margin: 0; padding: 0 4px 0 0; background: none; box-shadow: none;
    max-height: calc(100svh - var(--sticky-top) - var(--sp-5)); overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
  }
  .kb-nav-h { display: block; margin: 0 0 var(--sp-2); padding-inline: 12px; font-size: var(--t-card); font-weight: 600; color: var(--ink); }
  .kb-nav-in { flex-direction: column; gap: 2px; overflow: visible; padding: 0; }
  .kb-nav-group {
    display: block; margin: var(--sp-4) 0 4px; padding-inline: 12px;
    font-size: 12px; font-weight: 600; letter-spacing: .02em; color: var(--ink-3);
  }
  .kb-nav-h + .kb-nav-in > .kb-nav-group:first-child { margin-top: 0; }
  /* Sidebar links: the shell's tabs, at reading size. */
  .kb-nav-link {
    min-height: 36px; padding: 0 12px; border-radius: var(--r-field); background: none; color: var(--ink-2);
    font-size: 14px; white-space: normal;
  }
  .kb-nav-link:hover { background: var(--surface-2); color: var(--ink); }
  .kb-nav-link[aria-current="true"] { background: var(--surface); color: var(--ink); box-shadow: var(--sh-nav); }
  .kb-sec, .kb-topic { scroll-margin-top: var(--sticky-top); }
}

.kb-sec > h2 { margin: 0; font-size: var(--fs-lg); font-weight: 700; letter-spacing: -.01em; }
.kb-sec > .kb-lede { margin: 4px 0 var(--sp-4); color: var(--ink-2); max-width: var(--measure); }
.kb-topic {
  display: grid; gap: var(--sp-4); grid-template-columns: minmax(0, 1fr);
  padding: var(--sp-5) 0;
}
.kb-topic:first-of-type { border-top: 0; padding-top: 0; }
/* Text above its mock in a narrow section; beside it once the section is 600 wide. */
.kb-topic .kb-mock { max-width: var(--w-pop); }
@container kbsec (min-width: 600px) {
  .kb-topic { grid-template-columns: minmax(0, 1fr) 240px; align-items: start; gap: var(--sp-6); }
  .kb-topic.no-mock { grid-template-columns: minmax(0, 1fr); }
  .kb-topic .kb-mock { max-width: none; }
}
.kb-topic p { margin: 0 0 var(--sp-2); color: var(--ink-2); }
.kb-topic code { font-family: var(--font-mono); font-size: 12px; background: var(--surface-2); padding: 1px 5px; border-radius: var(--r-field); overflow-wrap: anywhere; }
.kb-steps { list-style: none; counter-reset: kb; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.kb-steps > li { counter-increment: kb; display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 10px; align-items: start; }
.kb-steps > li::before {
  content: counter(kb); width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface-2); color: var(--ink); font-size: var(--fs-xs); font-weight: 700;
}
.kb-notes { margin: var(--sp-3) 0 0; padding-left: 1.1em; color: var(--ink-2); font-size: var(--fs-sm); display: flex; flex-direction: column; gap: 4px; }
.kb-ui { font-weight: 600; color: var(--ink); white-space: nowrap; }
.kb-who { margin-left: 6px; vertical-align: 2px; }

/* The mockup frame and its parts. */
.kb-mock {
  background: var(--surface-2); border-radius: var(--r-card); padding: var(--sp-4);
  display: flex; flex-direction: column; gap: 10px; pointer-events: none; user-select: none;
  font-size: var(--fs-sm); min-width: 0;
}
.kb-scr { border: 0; border-radius: var(--r-inner); padding: 12px; display: flex; flex-direction: column; gap: 8px; min-width: 0; background: var(--surface-3); }
.kb-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.kb-row > .kb-grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kb-t { font-weight: 700; color: var(--ink); }
.kb-m { color: var(--ink-2); font-size: var(--fs-xs); }
.kb-mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.kb-dot { width: 26px; height: 26px; border-radius: 50%; flex: none; background: var(--line-2); display: grid; place-items: center; font-size: 11px; font-weight: 700; color: var(--ink); }
.kb-dot.is-me { box-shadow: 0 0 0 2px var(--hi-strong); }
.kb-mock .btn, .kb-mock .btn--quiet { min-height: 34px; padding: 0 12px; font-size: 12.5px; }
.kb-mock .chip { min-height: 30px; padding: 0 10px; font-size: 12px; }
.kb-mock .tag { flex: none; }
.kb-field { border: 0; border-radius: var(--r-inner); padding: 7px 10px; color: var(--ink-3); background: var(--surface-2); }
.kb-hl { box-shadow: 0 0 0 2px var(--hi-strong); border-radius: var(--r-inner); }
.kb-ring { box-shadow: 0 0 0 2px var(--hi-strong); }
.kb-inv { background: var(--ink); color: var(--ground); }   /* a court waiting for Start */
.kb-inv .kb-t { color: var(--ground); }
.kb-inv .kb-m { color: var(--inv-ink-2); }
/* A one-court score panel, like the board's halves. */
.kb-score { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.kb-half { border-radius: var(--r-inner); border: 0; padding: 10px; text-align: center; background: var(--surface-3); }
.kb-half.is-serving { background: var(--lime-tint); }
.kb-big { font-family: var(--font-mono); font-size: 34px; font-weight: 700; line-height: 1; color: var(--ink); }
.kb-tap { display: grid; place-items: center; min-height: 64px; border-radius: var(--r-inner); background: var(--accent); color: var(--accent-ink); font-weight: 700; }
.kb-tap.is-b { background: var(--surface-2); color: var(--ink); }
.kb-tabbar { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; border: 0; border-radius: var(--r-inner); padding: 6px; background: var(--surface-3); }
.kb-tabbar span { text-align: center; padding: 6px 0; border-radius: var(--r-pill); font-weight: 600; font-size: 12px; color: var(--ink-2); }
.kb-tabbar span.is-on { background: var(--accent); color: var(--accent-ink); }
.kb-tools { display: flex; gap: 6px; justify-content: flex-end; }
.kb-tools span { width: 30px; height: 30px; border-radius: 50%; border: 0; display: grid; place-items: center; color: var(--ink-2); background: var(--surface-2); }
.kb-tools svg { width: 14px; height: 14px; }
/* ═══ help END ═══ */

/* ═══ court pages START ═══ */
/* Owner: tools/build-court-pages.js (courts/<id>/index.html). One static page
   per venue for search; the in-app court sheet's pieces (.court-detail, .rates,
   .cd-sec, .crew-say) laid out as a page. */
.cp { min-width: 0; }
.cp-crumb ol { list-style: none; display: flex; flex-wrap: wrap; gap: 4px; margin: 0; padding: 0; font-size: var(--fs-xs); color: var(--ink-3); }
.cp-crumb li + li::before { content: '›'; margin-right: 4px; }
.cp-crumb a { color: inherit; }
.cp-crumb [aria-current] { color: var(--ink-2); }
.cp .cp-photo, .cp .cp-art { margin: 0; }
.cp-photo img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--r-card, 16px); background: var(--line); }
.cp-know p { margin: 0; font-size: 13.5px; color: var(--ink-2); max-width: var(--measure); }
.cp-know-acts { display: flex; flex-wrap: wrap; gap: 8px; }
.cp-know-acts .btn--soft { text-decoration: none; }
.cp-art { display: block; width: 100%; height: auto; aspect-ratio: 1200 / 630; border-radius: var(--r-card, 16px); }
/* The head breathes: title, then the place and facts rows with room between. */
.cp-head .page-head-text { gap: var(--sp-3); }
.cp-head .court-meta-rows { gap: var(--sp-2); }
/* Rates lead the aside, right above how to book, with prices easy to read. */
.cp-sum { line-height: 1.65; }
.cp-rates .rates, .cp-players .rates { gap: 0; margin: 0; }
.cp-rates .rate { align-items: baseline; font-size: var(--fs-base); padding: 10px 0; }
.cp-rates .rate:first-child { border-top: 0; padding-top: 0; }
.cp-rates .rate-amt { font-size: var(--fs-md); font-weight: 600; }
.cp-head .court-loc, .cp-head .court-facts { font-size: var(--fs-base); }
/* Sections, as in the in-app court sheet: a hairline above each, no cards.
   On a wide view the aside's first section starts its column clean. */
.cp .cd-sec { gap: var(--sp-3); padding-top: var(--sp-5); }
@container view (min-width: 880px) { .cp .split-side > .cd-sec:first-child { padding-top: 0; border-top: 0; } }
.cp-faq dl, .cp-near ul { margin: 0; }
.cp-sum { margin: 0; color: var(--ink-2); max-width: var(--measure); }
.cp .verified { margin: 0; }
.cp .cp-crew { gap: var(--sp-2); }
.cp-crew p { margin: 0; font-size: 13.5px; color: var(--ink-2); max-width: var(--measure); }
.cp-crew .btn--soft { align-self: flex-start; text-decoration: none; }
.cp-faq dl { margin: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.cp-faq dt { font-weight: 600; font-size: 14px; }
.cp-faq dd { margin: 2px 0 0; font-size: 13.5px; color: var(--ink-2); max-width: var(--measure); }
.cp-near ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.cp-near li { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 10px 0; }
.cp-near li:first-child { border-top: 0; padding-top: 2px; }
.cp-near a { display: flex; flex-direction: column; min-width: 0; color: inherit; text-decoration: none; }
.cp-n { font-weight: 600; font-size: 14px; }
.cp-near a:hover .cp-n { text-decoration: underline; text-underline-offset: 3px; }
.cp-m { font-size: 12.5px; color: var(--ink-3); }
.cp-p { font-size: 12.5px; color: var(--ink-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* Hub pages (/courts/ and one per city): one readable column of the same list. */
.cp-hub { display: flex; flex-direction: column; gap: var(--sp-5); max-width: 720px; }
.cp-hub .cp-sum { font-size: var(--fs-base); }
.cp-more { margin: 0; font-size: 13.5px; color: var(--ink-2); }
.cp-more a { color: inherit; text-underline-offset: 3px; }
/* ═══ court pages END ═══ */

/* ═══ crew START ═══
   The Dink Crew (docs/design/crew-bible.md). PAC_UI.say() builds every line
   and it reads as a §0 .crew-line: a small character and one line beside it,
   names 600 ink. No speech bubble, no tail, no box. A page voice (.is-block)
   is the same line one size up; a stack (.is-stack) puts the character above. */
.crew-say { display: flex; align-items: center; gap: 10px; min-width: 0; }
.crew-say-who { flex: none; line-height: 0; }
/* The bubble: a soft fill and a filled tail pointing at whoever talks. No
   outline (v2): the fill separates it, on the panel and inside a card alike. */
.crew-say-line {
  position: relative; margin: 0; min-width: 0; max-width: var(--w-bubble);
  padding: 9px 14px; border-radius: var(--r-tile); background: var(--surface-2);
  color: var(--ink); font-size: 14px; font-weight: 500; line-height: 1.4;
  overflow-wrap: anywhere;
}
.crew-say-line::before {
  content: ''; position: absolute; left: -4px; top: 50%; width: 10px; height: 10px; border-radius: 2px;
  background: inherit; transform: translateY(-50%) rotate(45deg);
}
.crew-say-line b, .crew-say-line strong { font-weight: 600; }
/* Each friend's bubble is a faint wash of their own colour, so who is talking
   reads at a glance. Dink's lime stays fainter: lime tint means "selected"
   and Volley's filter tokens elsewhere. Ink text stays AA on every wash. */
[data-crew] { --crew-c: var(--surface-2); --crew-mix: 16%; }
[data-crew="dink"] { --crew-c: var(--dink); --crew-mix: 11%; }
[data-crew="lob"] { --crew-c: var(--lob); }
[data-crew="smash"] { --crew-c: var(--smash); --crew-mix: 13%; }
[data-crew="erne"] { --crew-c: var(--erne); }
[data-crew="volley"] { --crew-c: var(--volley); }
[data-crew="pop"] { --crew-c: var(--pop); }
.crew-say .crew-say-line, .crew-line > p { background: color-mix(in srgb, var(--crew-c) var(--crew-mix), var(--surface-2)); }
.crew-say.is-stack { flex-direction: column; text-align: center; gap: 8px; }
.crew-say.is-stack .crew-say-line::before { left: 50%; top: -4px; transform: translateX(-50%) rotate(45deg); }
.scorer-mascot .crew-say { margin: 0 auto; }

/* Home's one crew slot. It stands down whenever the view already shows a
   Dink system state (an empty state or the offline bar): one character per screen. */
.crew-slot:empty { display: none; }
.crew-slot { margin: 0; }
.gview:has(.empty-mascot, .offline-bar) :is(.crew-slot, .page-crew) { display: none; }

/* A group's Today: the page voice (noGames / restDay / inviteGuests) at the top
   of the body. Spacing lives in "hierarchy sweep E" (.today-voice). */
.crew-today { padding: 0; }

/* Erne's tip: dismissible, one per session. A page voice like any other (no
   box); the ✕ is a quiet icon button beside the bubble ("hierarchy sweep E"). */
.crew-tip { display: flex; align-items: center; gap: var(--sp-1); min-width: 0; }
.crew-tip .crew-say { flex: 0 1 auto; min-width: 0; }
.crew-tip-x { flex: none; }
/* The crew's follow-up on Home: the whole line is the button into the chat. */
.crew-follow { cursor: pointer; border-radius: var(--r-inner); -webkit-tap-highlight-color: transparent; }
.crew-follow:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
.crew-follow:active .crew-say-line { transform: scale(.98); }
/* A tip with a button row (the crew quiz): the row wraps under the line. */
.crew-tip.has-acts { flex-wrap: wrap; }
.crew-tip-acts { flex-basis: 100%; display: flex; gap: var(--sp-1); padding-left: 74px; }
.crew-tip-x svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }

/* Me › You: the mute switch (a row in the first card; see "hierarchy sweep C"). */
.crew-toggle {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  min-height: var(--tap); cursor: pointer;
}
.crew-toggle span { font-weight: 600; }
.crew-toggle input { width: 22px; height: 22px; margin: 0; accent-color: var(--ink); flex: none; }
/* ═══ crew END ═══ */

/* ═══ sign-in crew START ═══
   #/signin (groups.js renderSignIn, siCrew). The crew peeks over the form
   card: the row is cut at chin height (77% of each 128-unit box) and the
   card, one layer up, hides the rest, so a bounce reads as peeking. The
   band above holds one speech bubble at a time. All of it is decorative
   (aria-hidden); #siMsg says what matters. Pupils are aimed from JS
   (el.look in dink-mascot.js and dink-friends.js). */
.si-crew {
  --si-band: 36px;
  position: relative; padding-top: var(--si-band);
  pointer-events: none; user-select: none;
}
.si-row { display: flex; justify-content: center; height: calc(var(--si-px) * .77); }
/* Each one a little narrower than its box, so the six stand shoulder to
   shoulder; the drawing overflows both sides evenly. */
.si-mate { flex: none; width: calc(var(--si-px) * .86); display: flex; justify-content: center; line-height: 0; }
.si-mate > * { flex: none; }
.si-card.has-crew { position: relative; z-index: 1; }

/* The bubble: over whoever talks (JS sets left and --tail), a wash of
   their colour like .crew-say-line, and no layout of its own. */
.si-say {
  position: absolute; left: 0; bottom: calc(var(--si-px) * .77 + 2px);
  width: max-content; max-width: min(100%, var(--w-bubble));
  margin: 0; padding: 7px 12px; border-radius: 14px;
  background: color-mix(in srgb, var(--crew-c, var(--dink)) 22%, var(--surface));
  color: var(--ink); font-size: 13.5px; font-weight: 500; line-height: 1.35;
  box-shadow: var(--sh-card);
  opacity: 0; visibility: hidden; transform: translateY(4px) scale(.96); transform-origin: var(--tail, 50%) 100%;
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-spring), visibility 0s linear var(--dur-2);
}
.si-say.is-on { opacity: 1; visibility: visible; transform: none; transition-delay: 0s; }
.si-say::after {
  content: ''; position: absolute; left: var(--tail, 50%); bottom: -4px;
  width: 10px; height: 10px; margin-left: -5px; border-radius: 2px;
  background: inherit; transform: rotate(45deg);
}

/* The wave: once a session they rise from behind the card, one by one. */
.si-crew.is-hello .si-mate > * { animation: si-rise .55s var(--ease-spring) both; animation-delay: calc(var(--i) * 55ms); }
@keyframes si-rise { from { transform: translateY(75%); } to { transform: none; } }

/* A wrong code, each in character (on top of their loss state): Smash
   fumes and shakes, Erne shakes his head, Volley winces, Lob droops, Pop
   flinches. Dink's own loss state is enough. */
.si-mate.is-oops { animation: .5s var(--ease-out) both; }
.si-mate.is-oops[data-crew="smash"] { animation-name: si-fume; animation-duration: .6s; }
.si-mate.is-oops[data-crew="erne"] { animation-name: si-no; animation-duration: .9s; transform-origin: 50% 100%; }
.si-mate.is-oops[data-crew="volley"] { animation-name: si-wince; transform-origin: 50% 100%; }
.si-mate.is-oops[data-crew="lob"] { animation-name: si-droop; animation-duration: 2.4s; }
.si-mate.is-oops[data-crew="pop"] { animation-name: si-flinch; animation-duration: .6s; }
@keyframes si-fume { 0%, 100% { transform: none; } 15%, 45%, 75% { transform: translateX(-3px); } 30%, 60%, 90% { transform: translateX(3px); } }
@keyframes si-no { 0%, 100% { transform: none; } 20% { transform: rotate(-7deg); } 45% { transform: rotate(6deg); } 70% { transform: rotate(-4deg); } }
@keyframes si-wince { 0%, 100% { transform: none; } 25%, 60% { transform: scale(1.07, .88); } }
@keyframes si-droop { 0%, 100% { transform: none; } 20%, 75% { transform: translateY(5px); } }
@keyframes si-flinch { 0%, 100% { transform: none; } 30% { transform: translate(-5px, -12px) rotate(-8deg); } 60% { transform: translate(-2px, 0) rotate(-3deg); } }

/* The code: one real input laid over six drawn boxes (it keeps paste,
   autofill and one-time-code). The input is invisible but on top, so a tap
   anywhere on the boxes focuses it; the box that takes the next digit shows
   focus. */
.si-code { position: relative; }
.si-boxes { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--sp-2); }
.si-box {
  height: var(--tap-lg); display: grid; place-items: center; min-width: 0;
  border-radius: var(--r-field); background: var(--surface-2);
  font-family: var(--font-mono); font-size: 24px; font-weight: 700; color: var(--ink);
  transition: background-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.si-box.is-full { background: var(--surface); }
.si-code:focus-within .si-box.is-at { background: var(--surface); box-shadow: 0 0 0 2px var(--ink); }
.si-code:focus-within .si-box.is-at:not(.is-full)::after { content: ''; width: 2px; height: 24px; border-radius: 1px; background: var(--ink); animation: si-caret 1s steps(1) infinite; }
@keyframes si-caret { 50% { opacity: 0; } }
.si-card .si-code .si-code-input,
.si-card .si-code .si-code-input:is(:hover, :focus) {
  position: absolute; inset: 0; width: 100%; height: 100%; min-height: 0; padding: 0 var(--sp-3);
  font-size: 16px; letter-spacing: 1.6em; color: transparent; caret-color: transparent;
  background: transparent; box-shadow: none; outline: 0; cursor: text;
}
.si-code-input::placeholder { color: transparent; }
.si-code-input::selection { background: transparent; color: transparent; }
.si-code-input:-webkit-autofill { -webkit-text-fill-color: transparent; transition: background-color 0s 600000s; }
.si-code.is-bad .si-boxes { animation: si-shake .4s var(--ease-out); }
.si-code.is-bad .si-box.is-full { background: var(--loss-tint); color: var(--loss); }
.si-code.is-ok .si-box, .si-code.is-ok:focus-within .si-box.is-at { background: var(--lime); color: var(--on-lime); box-shadow: none; animation: si-pop .4s var(--ease-spring) both; }
.si-code.is-ok .si-box:nth-child(2) { animation-delay: 40ms; }
.si-code.is-ok .si-box:nth-child(3) { animation-delay: 80ms; }
.si-code.is-ok .si-box:nth-child(4) { animation-delay: 120ms; }
.si-code.is-ok .si-box:nth-child(5) { animation-delay: 160ms; }
.si-code.is-ok .si-box:nth-child(6) { animation-delay: 200ms; }
@keyframes si-shake { 20%, 60% { transform: translateX(-6px); } 40%, 80% { transform: translateX(6px); } }
@keyframes si-pop { 50% { transform: translateY(-6px) scale(1.06); } }
@media (max-width: 380px) { .si-box { font-size: 21px; } .si-boxes { gap: 6px; } }

@media (prefers-reduced-motion: reduce) {
  .si-crew *, .si-code * { animation: none !important; transition: none !important; }
  .si-say { transform: none; }
}
/* ═══ sign-in crew END ═══ */

/* ═══ courtside START ═══
   Courtside, the crew's home (docs/courtside.md): the hub's 2×3 grid of
   still crew tiles, and the stage (header, canvas, ready/over card).
   Tokens only, so light and dark follow the theme. Lime is only the one
   Play / Go again button (.btn). */

/* ══ Sidequests (#/quests, assets/sidequests.js, docs/sidequests.md) ══ */
/* Sidequests (feed template, the split): one card per quest in the lead,
   its steps while active, its stickers as §0 list rows with the art in a
   tile; the aside holds what's next, the next unlock and your scouting. */
.sq-art { flex: none; width: 52px; height: 52px; display: grid; place-items: center; line-height: 0; }
.sq-art svg { width: 40px; height: 40px; display: block; }
/* "How it works": a §0 quiet button that folds an active quest's steps. */
.sq-how { display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start; }
/* The quiet button's 14px inline padding, pulled back so the label lines up with the card's text. */
.sq-how > summary { list-style: none; width: fit-content; margin-inline-start: -14px; }
.sq-how-chev { flex: none; transition: transform var(--dur-2) var(--ease-out); }
.sq-how[open] .sq-how-chev { transform: rotate(180deg); }
.sq-how > summary::-webkit-details-marker { display: none; }
.sq-signin { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; font-size: 13.5px; color: var(--ink-2); }
.sq-steps { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 4px; font-size: 13.5px; color: var(--ink-2); }
/* A locked batch (Set 8): what to finish first, with that quest's bar. */
.sq-lock-row { display: flex; flex-direction: column; gap: 6px; }
/* Hub (Feed template): the game of the week on an ink card, the art on
   its right; the other games as cards with the art on top; high scores in
   the rail. The sticker sits on a hatch of the crew member's colour. */
.cs-feature { display: grid; grid-template-columns: minmax(0, 1fr); padding: 0; overflow: hidden; gap: 0; }
.cs-feature-text { display: flex; flex-direction: column; gap: 6px; padding: 24px; }
.cs-feature-go { display: flex; align-items: center; gap: 12px; margin-top: 12px; }
.cs-feature-art { display: grid; place-items: center; min-height: 160px; padding: 16px; line-height: 0;
  background: var(--inv-line); }
.cs-feature-art .cs-stk { width: 100%; max-width: 240px; height: auto; aspect-ratio: 3 / 2; }
@media (min-width: 768px) { .cs-feature { grid-template-columns: minmax(0, 1fr) 300px; } }
/* Phone: art on top, like the smaller game cards below it. The sticker gets a
   fixed height: a percentage of the grid row didn't resolve and it spilled
   out of the bottom of the strip, cut off. */
@media (max-width: 767.98px) {
  .cs-feature-art { order: -1; min-height: 0; height: 140px; padding: 14px 16px; }
  .cs-feature-art .cs-stk { width: auto; max-width: 100%; height: 112px; }
  .cs-feature-text { padding: 20px 20px 22px; }
}
/* On one column the high scores follow the games (the feed's rail-first rule is for Home). */
@media (max-width: 1279.98px) { .cs-split .split-side > :first-child { order: 4; } }
.cs-games { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
@media (min-width: 1024px) { .cs-games { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.cs-game { padding: 0; gap: 0; overflow: hidden; color: inherit; text-decoration: none; transition: transform var(--dur-1) var(--ease-out); }
.cs-game:active { transform: scale(.985); }
.cs-game-art { display: grid; place-items: center; height: 110px; padding: 10px; line-height: 0;
  background: color-mix(in srgb, var(--cs-tint, var(--ink-4)) 16%, var(--surface-3)); }
.cs-game-art .cs-stk { width: auto; height: 100%; aspect-ratio: 3 / 2; }
.cs-game-text { display: flex; flex-direction: column; gap: 2px; padding: 14px 16px 16px; min-width: 0; }
.cs-stk { display: block; overflow: visible; }
.cs-game.is-locked { cursor: default; }
.cs-game.is-locked :is(.cs-game-art .cs-stk, .card-title) { opacity: .45; }
.cs-game-art { position: relative; }
.cs-lock { position: absolute; top: 10px; right: 10px; display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%;
  background: var(--surface); color: var(--ink-2); box-shadow: var(--sh-card); }
.cs-lock svg { width: 15px; height: 15px; }
/* Guests: one line on the five locked games, above them. */
.cs-more { flex-direction: row; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px 16px; }
.cs-more-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1 1 220px; }
.cs-stk .sh { display: none; }
.cs-gs { fill: var(--ink); opacity: .1; }
.cs-face { display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%; overflow: hidden; line-height: 0; }
.cs-face svg { width: 30px; height: 30px; }

/* The stage: back, title, score; then the canvas at the size the engine sets. */
.gview:has(> .cs-stage) { padding-top: var(--sp-2); padding-bottom: 0; }
.cs-stage { display: flex; flex-direction: column; align-items: center; }
.cs-head { display: flex; align-items: center; gap: var(--sp-2); width: 100%; max-width: var(--w-card); margin-bottom: var(--sp-2); }
.cs-head .icon-btn { flex: none; }
.cs-title { flex: 1; min-width: 0; margin: 0; font-size: var(--fs-lg); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-score { display: flex; align-items: center; gap: var(--sp-2); margin: 0; flex: none; font-size: var(--fs-sm); font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.cs-best:empty { display: none; }
.cs-play {
  position: relative; max-width: 100%; overflow: hidden;
  border-radius: var(--r-card);
  background: var(--surface-2);
}
.cs-canvas { display: block; touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; outline: none; }
.cs-overlay {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  padding: var(--sp-4);
  background: color-mix(in srgb, var(--ground) 82%, transparent);
  animation: cs-in var(--dur-2) var(--ease-out);
}
.cs-overlay[hidden] { display: none; }
.cs-card {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-4);
  width: 100%; max-width: var(--w-card-sm); padding: var(--sp-5) var(--sp-4);
  border-radius: var(--r-card); background: var(--surface); border: 0;
  box-shadow: var(--shadow-sm); text-align: center; box-shadow: var(--sh-card); }
.cs-say:empty { display: none; }
.cs-say .crew-say { margin: 0 auto; }
.cs-msg { margin: 0; color: var(--ink-2); font-size: var(--fs-sm); }
.cs-card .btn { width: 100%; }
.cs-level { width: 100%; }
@keyframes cs-in { from { opacity: 0; } to { opacity: 1; } }

/* Orb Zuma (assets/courtside-orb.js), Crew AI's game. The hub features it
   on the ink card: the real <crew-orb> on the left, Play under the title. */
.cs-orb-feature { grid-template-columns: 96px minmax(0, 1fr); align-items: center; gap: 20px; padding: 24px; }
.cs-orb-feature .cs-feature-text { padding: 0; }
@media (min-width: 768px) { .cs-orb-feature { grid-template-columns: 96px minmax(0, 1fr); } }
@media (max-width: 767.98px) { .cs-orb-feature { padding: 20px; gap: 16px; } .cs-orb-feature .cs-feature-text { padding: 0; } }
.cs-orb-feature .cs-feature-go .btn { min-width: 140px; }
.cs-orb-art { display: grid; place-items: center; line-height: 0; }
.cs-face--orb { background: none; overflow: visible; }

/* The stage: the 390×640 field (scaled by the engine), the world drawn on
   .oz-bg, the game on the canvas, the orb as a real <crew-orb> on top. */
.oz-play { touch-action: none; }
.oz-bg, .oz-play .cs-canvas { position: absolute; inset: 0; }
.oz-play .cs-canvas { cursor: crosshair; }
.oz-orb { position: absolute; pointer-events: none; line-height: 0; }
/* The glass takes the loaded colour; the eyes follow the aim. */
.oz-orb[data-oz-tint] .co-tint { background-color: var(--oz-tint); opacity: .62; transition: opacity .3s, background-color .3s; }
.oz-orb .co-ball { transition: box-shadow .3s; }
.oz-orb .co-eye i { animation: none; translate: var(--oz-look, 0 0); }
.oz-orb .co-mouth { display: none; }
.oz-orb[data-oz-mood="grin"] .co-conic, .oz-orb[data-oz-mood="hmm"] .co-conic { animation-duration: 2.2s; }
.oz-mouth { position: absolute; left: 50%; top: 63%; width: calc(var(--s) * .3); height: calc(var(--s) * .16); transform: translate(-50%, -50%); overflow: visible; }
.oz-mouth path { stroke: var(--ink); }
.oz-mouth > * { display: none; transform-box: view-box; transform-origin: 50% 30%; }
.oz-orb[data-oz-mood="idle"] .oz-m-idle, .oz-orb[data-oz-mood="grin"] .oz-m-grin, .oz-orb[data-oz-mood="hmm"] .oz-m-hmm,
.oz-orb[data-oz-mood="shoot"] .oz-m-shoot, .oz-orb[data-oz-mood="sad"] .oz-m-sad { display: inline; }
.oz-m-grin { animation: oz-grin .5s ease-in-out infinite; }
.oz-m-hmm { animation: oz-hmm 2.4s ease-in-out infinite; }
@keyframes oz-grin { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.12, 1.2); } }
@keyframes oz-hmm { 0%, 100% { transform: translateX(8%) rotate(-6deg); } 40%, 55% { transform: translateX(-12%) rotate(5deg); } 75% { transform: translateX(10%) rotate(-8deg); } }

/* Pick a track: three worlds of four tiles, over the field. */
.oz-levels { position: absolute; inset: 0; z-index: 2; overflow-y: auto; display: flex; flex-direction: column; gap: var(--sp-4);
  padding: var(--sp-4); background: var(--ground); }
.oz-levels[hidden] { display: none; }
.oz-levels-head, .oz-world-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-2); }
.oz-levels-head b { color: var(--ink); font-variant-numeric: tabular-nums; }
.oz-world { display: flex; flex-direction: column; gap: var(--sp-2); }
.oz-world h3 { margin: 0; font-size: var(--t-body); font-weight: 700; }
.oz-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
.oz-tile { position: relative; display: flex; flex-direction: column; align-items: center; gap: 3px; min-height: 44px; padding: 8px 4px 9px;
  border: 0; border-radius: var(--r-tile); background: var(--surface); color: var(--ink); font: inherit; cursor: pointer;
  box-shadow: var(--sh-card); -webkit-tap-highlight-color: transparent; transition: transform var(--dur-1) var(--ease-out); }
.oz-tile:active { transform: scale(.96); }
.oz-tile.is-next { box-shadow: inset 0 0 0 2.5px var(--lime); }
.oz-tile canvas { width: 60px; height: 70px; }
.oz-tile.is-locked { cursor: default; }
.oz-tile.is-locked canvas { opacity: .22; }
.oz-tile-name { font-size: 13px; font-weight: 700; line-height: 1.1; text-align: center; }
.oz-tile-meta { font-size: 11px; font-weight: 600; color: var(--ink-2); line-height: 1.1; font-variant-numeric: tabular-nums; }
.oz-tile-lock { position: absolute; left: 50%; top: 28px; width: 30px; height: 30px; margin-left: -15px; border-radius: 50%;
  display: grid; place-items: center; background: var(--ink); color: var(--surface); }
.oz-tile-lock svg { width: 14px; height: 14px; }

/* Ready / over: the Courtside card, at the foot of the field so the track shows. */
.oz-overlay { align-items: flex-end; background: none; z-index: 3; }
.oz-card { align-items: stretch; text-align: left; max-width: var(--w-card); }
.oz-card-text { display: flex; flex-direction: column; gap: 6px; }
.oz-card-top { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-3); }
.oz-card-score { font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; }
.oz-cols { display: flex; align-items: center; gap: 10px; }
.oz-dots { display: flex; gap: 4px; }
.oz-dots i { width: 16px; height: 16px; border-radius: 50%; box-shadow: inset 0 -2px 3px color-mix(in srgb, var(--ink) 18%, transparent); }
.oz-btns { display: flex; gap: 8px; }
.oz-card .oz-btns .btn { width: auto; flex: 1; }
.oz-card .oz-btns .btn--primary { flex: 1.4; }
.oz-foot { margin: var(--sp-2) 0 0; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); text-align: center; min-height: 1.4em; }

@media (prefers-reduced-motion: reduce) {
  .oz-m-grin, .oz-m-hmm { animation: none; }
  .oz-tile, .oz-tile:active { transition: none; transform: none; }
}
/* ═══ courtside END ═══ */

/* ── Rivalry nudge (Rematch, Phase 1) ──
   Every rivalry line is a card aside (.crew-say.is-aside, 32px, no bubble; the
   look is in "hierarchy sweep E"). Only placement is set here. */
.rcard-say { margin: var(--sp-2) 0 var(--sp-3); }
.rcard-say .crew-say { max-width: var(--w-bubble); }
.feed-rivalry { margin: var(--sp-1) 0; }
.profile-rivalry { margin: 0 0 var(--sp-2); }
/* Group Insights: Volley's read over the tiles, his nudge on Regulars at risk (Host pack). */
.gi-say { margin: 0 0 var(--sp-3); }
.gi-say .crew-say { max-width: var(--w-bubble); }
.profile-rel-n .tag--dot { margin-left: var(--sp-1); }
/* Group Today: games that never started (organizers). */
.today-past .date-tile { flex: none; }

/* ── Kit + Clubhouse (docs/groups.md, Phase 2). Standard v2 tokens only. ──
   One drawing idea runs through every surface: a locker door is a calm
   surface panel with a recessed inner frame, three vent slits up top and a
   handle on the right. Stickers sit straight on it, each at its own small
   tilt, the way people actually slap them on. */

/* Clubhouse (layout only; the door is §0 .door): doors in a wrapping grid,
   four across beside a 300px rail on a wide view, two across on a phone. */
.club-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 24px; align-items: start; }
@media (min-width: 1024px) { .club-grid { grid-template-columns: minmax(0, 1fr) 300px; } }
.club-rail { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.door-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
@media (min-width: 768px) { .door-grid { grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 12px; } }
/* The same eight tilts on every locker, so a sticker keeps its angle wherever it shows. */
.lk-spot:nth-child(8n+1) { --tilt: -6deg; }
.lk-spot:nth-child(8n+2) { --tilt: 4deg; }
.lk-spot:nth-child(8n+3) { --tilt: -2deg; }
.lk-spot:nth-child(8n+4) { --tilt: 7deg; }
.lk-spot:nth-child(8n+5) { --tilt: 3deg; }
.lk-spot:nth-child(8n+6) { --tilt: -5deg; }
.lk-spot:nth-child(8n+7) { --tilt: 5deg; }
.lk-spot:nth-child(8n+8) { --tilt: -3deg; }

/* Profile hero: the whole dressed player, standing in front of an open locker. */
/* Me › Profile: the same stage, beside the name. */


/* The door on a profile. Under the hero on a phone; beside it on a wide screen. */
.lk-door {
  position: relative; padding: var(--sp-3) 26px var(--sp-4) var(--sp-4);
  background: var(--surface); border: 0; border-radius: var(--r-card);
  box-shadow: inset 0 0 0 6px var(--surface), inset 0 0 0 7px var(--line); box-shadow: var(--sh-card); }
.lk-door-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); min-height: 28px; padding: 2px 0 var(--sp-2) 4px; }
.lk-spots { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-2); }
.lk-spot {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  aspect-ratio: 1; min-width: 0; padding: 0; border: 0; border-radius: var(--r-inner);
  background: transparent; color: var(--ink); font: inherit;
}
button.lk-spot { cursor: pointer; transition: transform var(--dur-1) var(--ease-out), background-color var(--dur-2) var(--ease-out); }
button.lk-spot:hover { background: var(--surface-2); }
button.lk-spot:active { transform: scale(.97); }
button.lk-spot:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.lk-spot-art { display: block; width: 84%; max-width: 76px; aspect-ratio: 1; transform: rotate(var(--tilt, 0deg)); }
.lk-spot-art svg { width: 100%; height: 100%; display: block; }
.lk-spot:has(.lk-from) .lk-spot-art { width: 70%; }
/* An empty spot: a faint sticker-sized mark. The owner's also shows a plus. */
.lk-spot-empty { display: grid; place-items: center; width: 8px; height: 8px; border-radius: 50%; background: var(--line); }
button.lk-spot .lk-spot-empty { width: 14px; height: 14px; border-radius: 0; background: none; }
button.lk-spot .lk-spot-empty::after {
  content: ""; width: 14px; height: 14px; opacity: .55;
  background: linear-gradient(var(--ink-3), var(--ink-3)) center / 2px 100% no-repeat, linear-gradient(var(--ink-3), var(--ink-3)) center / 100% 2px no-repeat;
}
.lk-from {
  max-width: 100%; font-size: 11px; font-weight: 600; line-height: 1.1; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lk-door-sm { padding: var(--sp-3) 26px var(--sp-3) var(--sp-3); }
.lk-spots.is-pick .lk-spot { background: transparent; }
.lk-spots.is-pick .lk-spot:hover { background: var(--surface-2); }

/* The "new" pile: gifts waiting for a free spot. */
.lk-pending { margin-top: var(--sp-3); }
.lk-pending-t { display: flex; align-items: center; gap: var(--sp-2); margin: 0 0 var(--sp-2); font-size: var(--fs-sm); font-weight: 600; }
.lk-pile { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.lk-pile-item {
  display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--tap); max-width: 100%; min-width: 0;
  padding: 4px var(--sp-4) 4px 4px; border-radius: var(--r-pill); border: 0; color: var(--ink); font: inherit; text-align: left; cursor: pointer;
  transition: transform var(--dur-1) var(--ease-out), border-color var(--dur-2) var(--ease-out); background: var(--surface-3); }
.lk-pile-item:hover { background: var(--surface-2); }
.lk-pile-item:active { transform: scale(.97); }
.lk-pile-art { flex: none; display: inline-flex; width: 40px; height: 40px; transform: rotate(-8deg); }
.lk-pile-item:nth-child(even) .lk-pile-art { transform: rotate(6deg); }
.lk-pile-art svg { width: 100%; height: 100%; }
.lk-pile-t { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.lk-pile-t b { font-size: var(--fs-sm); }
.lk-pile-t span { font-size: var(--fs-xs); color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lk-dot { flex: none; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--danger); }

/* Locker shelves */
.lk-bar { display: block; height: 6px; border-radius: var(--r-pill); background: var(--surface-2); overflow: hidden; }
.lk-bar i { display: block; height: 100%; background: var(--accent); border-radius: var(--r-pill); }
.lk-hint { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }

/* Shelf switcher and the sheets' part switchers: the Standard segmented track. */

.lk-shelf { display: flex; flex-direction: column; gap: var(--sp-5); }
.lk-group { display: flex; flex-direction: column; gap: var(--sp-2); }
.lk-group .sub-h { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); }
.lk-group-n { font-size: var(--fs-xs); font-weight: 600; font-variant-numeric: tabular-nums; }
.lk-art { display: block; width: 64px; height: 64px; margin-bottom: 2px; }
.lk-art svg { width: 100%; height: 100%; display: block; }
.lk-name { font-size: var(--fs-sm); font-weight: 700; line-height: 1.2; overflow-wrap: anywhere; }
.lk-how { font-size: 11px; line-height: 1.25; color: var(--ink-2); }

/* Pickers in sheets: tiles (art over name), never long chip lists. */
.kit-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: var(--sp-2); }
.kit-tiles-gift { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.kit-cell {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-width: 0; min-height: 96px; padding: var(--sp-3) 6px var(--sp-2); border-radius: var(--r-inner);
  border: 0; color: var(--ink); font: inherit; cursor: pointer;
  transition: transform var(--dur-1) var(--ease-out), border-color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out); background: var(--surface-3); }
.kit-cell:hover { background: var(--surface-2); }
.kit-cell:active { transform: scale(.97); }
.kit-cell:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.kit-cell[aria-checked="true"], .kit-cell[aria-pressed="true"] { background: var(--lime-tint); }
.kit-tile-art { display: grid; place-items: center; width: 48px; height: 48px; }
.kit-tile-art svg { width: 100%; height: 100%; display: block; }
.kit-cell:nth-child(odd) .kit-tile-art svg { transform: rotate(-4deg); }
.kit-cell:nth-child(even) .kit-tile-art svg { transform: rotate(3deg); }
.kit-tile-n { max-width: 100%; font-size: var(--fs-xs); font-weight: 600; line-height: 1.2; overflow-wrap: anywhere; }
.kit-tile-s { font-size: 11px; color: var(--ink-2); }
.kit-tile-dot { position: absolute; top: 8px; right: 8px; }
.kit-none {
  width: 30px; height: 30px; border-radius: 50%; border: 0; background: var(--surface-2); }
.kit-swatches { display: grid; grid-template-columns: repeat(7, minmax(0, var(--tap))); justify-content: space-between; gap: var(--sp-2); }
.kit-swatch {
  width: 100%; aspect-ratio: 1; padding: 0; border-radius: 50%; border: 0; background: var(--kc); cursor: pointer;
  transition: transform var(--dur-1) var(--ease-out);
}
.kit-swatch:active { transform: scale(.96); }
.kit-swatch[aria-checked="true"] { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--ink); }
.kit-swatch:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* Sheets */
.kit-sheet .overlay-title:focus { outline: none; }
.kit-sheet .btn { text-decoration: none; display: inline-flex; align-items: center; justify-content: center; }
.spot-now { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) var(--sp-3); border-radius: var(--r-inner); background: var(--surface-2); }
.spot-now p { display: flex; flex-direction: column; margin: 0; min-width: 0; line-height: 1.25; }
.spot-now b { font-size: var(--fs-base); }
.spot-now span { font-size: var(--fs-sm); color: var(--ink-2); }
.spot-now-art { flex: none; display: inline-flex; width: 56px; height: 56px; transform: rotate(-5deg); }
.spot-now-art svg { width: 100%; height: 100%; }
.spot-gift { flex-direction: column; text-align: center; padding: var(--sp-4); background: var(--accent-wash); }
.spot-gift .spot-now-art { width: 72px; height: 72px; }

/* Unlock reveal: the new things land like stickers being slapped on. */
.kit-reveal-title { text-align: center; }
/* Centred, so the room for the close button goes on both sides, or the
   title sits left of the stickers. */
.overlay:has(> .overlay-close) .kit-reveal-title { padding-inline: calc(var(--tap) - var(--sp-2)); }
.kit-reveal { display: flex; justify-content: center; flex-wrap: wrap; gap: var(--sp-4); padding: var(--sp-2) 0; }
.kit-reveal-item { flex: 0 1 96px; display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 0; text-align: center; }
.kit-reveal-item .lk-art { position: relative; width: 76px; height: 76px; margin: var(--sp-2) 0; isolation: isolate; }
.kit-reveal-item .lk-art::before {
  content: ""; position: absolute; inset: -8px; z-index: -1; border-radius: 50%; background: var(--accent-wash);
}
.kit-reveal-item:nth-child(1) .lk-art svg { transform: rotate(-6deg); }
.kit-reveal-item:nth-child(2) .lk-art svg { transform: rotate(5deg); }
.kit-reveal-item:nth-child(3) .lk-art svg { transform: rotate(-3deg); }
.kit-reveal-more { text-align: center; }

/* Gift entry on a finished Open Play game: quiet, below the results. */
.rcard-gift { display: flex; justify-content: flex-start; margin-top: var(--sp-1); }
.rcard-gift:empty { display: none; }
.gift-open { display: inline-flex; align-items: center; gap: var(--sp-2); }
.gift-open-art { display: inline-flex; width: 24px; height: 24px; transform: rotate(-8deg); }
.gift-open-art svg, .gift-done-art svg { width: 100%; height: 100%; }
.gift-done { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--tap); margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }
.gift-done-art { display: inline-flex; width: 28px; height: 28px; transform: rotate(-6deg); }
.gift-who { gap: var(--sp-2); padding-left: 6px; }
.gift-who .avatar { --av: 30px; }

@media (prefers-reduced-motion: no-preference) {
  .kit-reveal:not(.is-still) .kit-reveal-item { animation: kit-slap var(--dur-3) var(--ease-out) both; }
  .kit-reveal:not(.is-still) .kit-reveal-item:nth-child(2) { animation-delay: .08s; }
  .kit-reveal:not(.is-still) .kit-reveal-item:nth-child(3) { animation-delay: .16s; }
  .door:hover .door-stickers > * { transform: rotate(0deg); transition: transform var(--dur-2) var(--ease-out); }
}
@keyframes kit-slap { from { opacity: 0; transform: translateY(-10px) scale(1.25); } to { opacity: 1; transform: none; } }
@media (max-width: 380px) {
  .lk-door { padding-right: 22px; }
}

/* ── hierarchy sweep D: folded milestone notes ── */
.result-notes { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--sp-2); font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); }
.result-note { white-space: nowrap; }
.result-note-sep { color: var(--ink-3, var(--ink-2)); }
.feed-gift-slot:empty { display: none; }

/* ── hierarchy sweep C: Me › You ── */
/* Card 1: Open my profile, then the crew switch under a hairline (no card of its own). */
.me-locker .form-actions { margin-top: var(--sp-2); }
.me-crew { margin-top: var(--sp-4); padding-top: var(--sp-3); }
.me-crew-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.me-crew-text small { font-size: 13px; font-weight: 500; color: var(--ink-3); line-height: 1.35; }
/* The crew card: who greets you (pref homeCrew) and What the crew knows. */
.me-home-crew { margin-top: var(--sp-3); padding-top: var(--sp-3); }
.me-home-crew .me-crew-text { margin: 0; }
.me-home-crew .me-crew-text > span { font-weight: 600; }
.me-home-crew-opts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: var(--sp-2); }
.me-home-crew-opts .chip { gap: 6px; }
.me-notes-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); width: 100%;
  min-height: var(--tap); margin-top: var(--sp-3); padding: var(--sp-3) 0 0; border: 0; border-top: 1px solid var(--line);
  background: none; color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.me-notes-row > .me-crew-text { font-weight: 600; }
.me-notes-n { flex: none; min-width: 28px; padding: 2px 10px; border-radius: var(--r-pill); background: var(--surface-2); font-weight: 600; text-align: center; font-variant-numeric: tabular-nums; }
.me-notes-n:empty { display: none; }
.me-notes-row::after { content: "›"; flex: none; color: var(--ink-3); font-size: 20px; line-height: 1; }
/* The sheet: Lob hosts, a line per note with its delete button. */
.cn-host { margin-top: var(--sp-3); }
.cn-list { list-style: none; margin: var(--sp-3) 0 0; padding: 0; }
.cn-note { display: flex; align-items: center; gap: var(--sp-2); padding: 6px 0; }
.cn-note:first-child { border-top: 0; }
.cn-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; overflow-wrap: anywhere; }
.cn-text small { font-size: 12.5px; color: var(--ink-3); }
.cn-del { flex: none; color: var(--ink-3); }
.cn-del svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.cn-empty { padding: var(--sp-2) 0; color: var(--ink-3); font-size: 14px; }
.cn-switch { margin-top: var(--sp-3); padding-top: var(--sp-3); }
/* Me › Settings › Your data (Set 6): switch rows, then the two account actions. */
.me-quiet { gap: 12px; }
.me-quiet .form-msg:empty { display: none; }
/* Me › Groups: Paste an invite link, one row like Home's zero-group form. */
.me-paste-form { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }
.me-paste-form input { flex: 1 1 0; min-width: 0; min-height: var(--tap); }
/* Me › Settings › Profile (formats and times are chips, the audience a
   .seg), and the 18+ switch on sign-up. Layout only. */
.me-field { border: 0; margin: 0; padding: 0; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.me-field > legend { padding: 0; margin-bottom: 8px; font-size: 14px; font-weight: 600; color: var(--ink); }
.me-pick { display: flex; flex-wrap: wrap; gap: 8px; min-width: 0; }
.me-times-row { display: grid; grid-template-columns: 84px minmax(0, 1fr); align-items: center; gap: 12px; }
.me-times-d { font-size: 13px; font-weight: 500; color: var(--ink-3); }
.me-prof-form { gap: 18px; }
@media (max-width: 767.98px) { .me-times-row { grid-template-columns: minmax(0, 1fr); gap: 6px; } }
.me-paste-form .btn--soft { flex: none; }

/* ── hierarchy sweep B: Home strip, rank line ── */
/* Your groups strip (Home below 1280; the rail lists groups above it). One
   line of 44px pill chips that scrolls sideways, bleeding to the screen edge
   while the first chip lines up with the page gutter (main.wrap's padding). */
#homeStripHost { --gstrip-gut: var(--gutter); min-width: 0; }
.gstrip {
  display: flex; gap: var(--sp-2); overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; scrollbar-width: none;
  margin: 0 calc(-1 * var(--gstrip-gut)) var(--sp-3);
  padding: 2px var(--gstrip-gut); scroll-padding-inline: var(--gstrip-gut);
}
.gstrip::-webkit-scrollbar { display: none; }
.gstrip-item {
  flex: none; scroll-snap-align: start; justify-content: flex-start;
  min-height: var(--tap); max-width: 220px; padding-left: 8px;
}
.gstrip-item .gtile { box-shadow: none; }
.gstrip-text { display: flex; flex-direction: column; min-width: 0; }
.gstrip-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gstrip-status { font-size: var(--fs-xs); font-weight: 600; color: var(--ink-2); white-space: nowrap; }
.gstrip-item.is-live .gstrip-status { color: var(--hi-strong); }
.gstrip-item.is-live .gstrip-status::before {
  content: ""; display: inline-block; vertical-align: middle; margin: -2px 5px 0 0;
  width: 6px; height: 6px; border-radius: 50%; background: currentColor;
}
.gstrip-new { padding: 0 16px; gap: 4px; }
.gstrip-new[aria-expanded="true"] { background: var(--ink); color: var(--surface); }
/* The create form opens right under the strip. */
#homeStripHost > .feed-create .gform { margin-bottom: var(--sp-3); }

/* Rank line: one quiet row to the leaderboard, no card. */
#homeRankHost:empty { display: none; }
.home-rank {
  display: flex; align-items: center; gap: var(--sp-3); min-width: 0;
  min-height: var(--tap); margin: 0 0 var(--sp-3); padding: 0 var(--sp-2);
  margin-inline: calc(-1 * var(--sp-2));
  border-radius: var(--r-inner); color: var(--ink); text-decoration: none; font-weight: 600;
  transition: background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.home-rank:hover { background: var(--surface-2); }
.home-rank:active { transform: scale(.985); background: var(--surface-2); }
.home-rank-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.home-rank-text b { font-weight: 700; font-variant-numeric: tabular-nums; }
.home-rank-chev { flex: none; color: var(--ink-3); }
@media (prefers-reduced-motion: reduce) { .gstrip-item:active, .home-rank:active { transform: none; } }

/* ── hierarchy sweep E: crew presence, results actions ──
   Crew presence: every line is a crew line (crew region); a card aside is the
   inline size with the card as its frame. Celebrations are untouched. */

/* Erne's tip: the ✕ sits right beside the bubble, quiet until hovered. */
.crew-tip .crew-tip-x { align-self: center; color: var(--ink-3); }
.crew-tip .crew-tip-x:hover, .crew-tip .crew-tip-x:focus-visible { color: var(--ink); }

/* A group's Today: the page voice is the first thing in the body, above New game. */


/* ── hierarchy sweep A: group header, settings, invite sheet ── */
/* Compact group header: one row (tile · name + sub · Invite · ⚙) on every
   group tab and on Settings. Replaces the cover band there; .cover-band
   stays for the invite landing (renderJoin). */
.gtile-md { --gt: 44px; }
.ghead-invite { flex: none; padding: 0 14px; }
.ghead-gear { flex: none; }
.ghead-gear svg { width: 20px; height: 20px; }

/* Settings: a row with a title + sub on the left and one button on the right
   (Invite people, Leave group). Wraps under the text when it can't fit. */
.set-row { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); }
.set-row-text { flex: 1 1 180px; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.set-row-text > * { margin: 0; }
.set-row > button { flex: none; }
.set-row + .form-msg:not(:empty) { margin-top: var(--sp-2); }

/* Members & roles: rows like the leaderboard (hairline between, no boxes).
   Name, the role as quiet text, then one ⋯ that opens the role / remove sheet. */
.set-mems { display: flex; flex-direction: column; }
.set-mem {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 6px 0;
}
.set-mem:first-child { border-top: 0; }
.set-mem-who {
  flex: 1 1 auto; min-width: 0; min-height: var(--tap); display: flex; align-items: center; gap: 10px;
  color: inherit; text-decoration: none; border-radius: var(--r-inner);
  transition: transform var(--dur-1) var(--ease-out);
}
.gsec .set-mem-who { text-decoration: none; }
.set-mem-who:active { transform: scale(.985); }
.set-mem-who .avatar, .set-mem-who .tag { flex: none; }
.set-mem-name { min-width: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.set-mem-role { flex: none; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); }
.set-mem-more { flex: none; color: var(--ink-2); }
.set-mem-more:hover { color: var(--ink); background: var(--surface-2); }
.set-mem-more-gap { flex: none; width: 44px; }
.set-role-chips { margin-bottom: var(--sp-4); }
.set-mem-remove { margin-top: var(--sp-2); }

/* Look › Group name: inline field + Save (was a prompt()). */
.set-rename { display: flex; flex-direction: column; gap: 6px; margin: var(--sp-2) 0 var(--sp-3); }
.set-rename-row { display: flex; gap: var(--sp-2); }
.set-rename-row input { flex: 1 1 auto; min-width: 0; }
.set-rename-row .btn--soft { flex: none; }

/* Clubhouse foot: Leave group for members, quiet and last. */
.club-leave { display: flex; flex-direction: column; align-items: center; gap: 4px; margin: var(--sp-5) 0 var(--sp-2); }
.club-leave .form-msg { text-align: center; }

/* Invite sheet (#inviteDlg). */
.invite-sheet #inviteUrl { width: 100%; min-width: 0; text-overflow: ellipsis; }
.invite-acts { display: flex; gap: var(--sp-2); }
.invite-acts > * { flex: 1 1 0; min-width: 0; }
.invite-sheet .overlay-foot.invite-foot { justify-content: space-between; }
.invite-sheet .invite-foot > * { flex: 0 1 auto; }
.invite-sheet .overlay-title:focus { outline: none; }
.invite-sheet .invite-foot { align-items: flex-start; }
.invite-sheet .invite-resets { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
/* Show the crew: a real switch (track + knob), not a checkbox. */
.crew-toggle input[role="switch"] {
  appearance: none; -webkit-appearance: none; position: relative;
  width: 44px; height: 26px; border-radius: var(--r-pill); background: var(--line-2);
  cursor: pointer; transition: background var(--dur-2) var(--ease-out);
}
.crew-toggle input[role="switch"]::after {
  content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%;
  background: var(--surface); box-shadow: var(--shadow-sm); transition: transform var(--dur-2) var(--ease-out);
}
.crew-toggle input[role="switch"]:checked { background: var(--accent); }
.crew-toggle input[role="switch"]:checked::after { transform: translateX(18px); }

/* ── courts map-first (oct14) ──
   One toolbar for cards, list and map (#courtsBar): search · Near me · view
   switch, then filter pills (Time, City, Court type, Booking) + sort, Clear
   all, the result count and Share. Each pill opens a .fpop popover. Below
   900px City/Type/Booking/Sort live in the Filters sheet (#sidebar), whose
   desktop box is display:contents so its groups sit in the pill row. In map
   view .map-wrap is fixed from the toolbar's bottom (--cm-top, measured by
   sizeMap) to the viewport bottom / phone tab bar (--cm-bottom). Tiles are
   grayscale. Overrides the older .controls/.at-bar/.dir-head/.catalog rules. */
.ctool { position: relative; display: flex; flex-direction: column; gap: 10px; padding: 20px 0 16px; }
.ctool-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ctool-main .field.search { flex: 1 1 auto; min-width: 0; max-width: var(--w-card); }
.ctool-main .view-seg { margin-left: auto; }
.ctool-filters { flex-wrap: wrap; }
.ctool-end { margin-left: auto; display: flex; align-items: center; gap: 8px; min-width: 0; }
#view-courts .ctool-end .result-line { margin: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctool-clear { min-height: var(--tap); }
.ctool-clear[hidden] { display: none; }

.fgroup { position: relative; }
/* The filter chips are §0 .chip (open = ink, set by Volley = --token);
   only the label clamp and the chevron turn live here. */
.chip-label { max-width: 160px; overflow: hidden; text-overflow: ellipsis; }
.chip-chev { width: 14px; height: 14px; flex: none; transition: transform var(--dur-2) var(--ease-out); }
.chip-pop[aria-expanded="true"] .chip-chev { transform: rotate(180deg); }

.fpop {
  display: none; position: absolute; top: calc(100% + 8px); left: 0; z-index: 35;
  transform: translateX(var(--fp-shift, 0px));
  min-width: 248px; padding: 10px;
  background: var(--surface); border: 0; border-radius: var(--r-card); box-shadow: var(--shadow-lg); box-shadow: var(--sh-float); }
.fpop.is-open { display: block; animation: fp-in var(--dur-2) var(--ease-out); }
@keyframes fp-in { from { opacity: 0; transform: translate(var(--fp-shift, 0px), -4px); } to { opacity: 1; transform: translateX(var(--fp-shift, 0px)); } }
.fpop.facet { padding: 10px; border: 0; }
.fpop .facet-title { font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); margin: 4px 10px 6px; }
.fpop-time { width: 380px; padding: 12px 14px; }
.fpop-time .at-head { margin-bottom: 8px; }
.fpop-time .at-note { margin: 8px 2px 0; }

/* Sort is a pill-shaped select in the row; its label is for screen readers. */
.fsort { display: flex; align-items: center; padding: 0; border: 0; }
.fsort .field select { min-height: var(--tap); width: auto; font-size: 13px; font-weight: 600; }

/* The quiet facts line under the cards and the list. */
.dir-foot {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px 16px;
  margin-top: 20px; padding-top: 14px;
}
.dir-foot[hidden] { display: none; }
#view-courts .dir-foot .statbar { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 0; }
#view-courts .dir-foot .stat-item { font-size: 12px; white-space: nowrap; }
#view-courts .dir-foot .stat-item b { font-size: 13px; font-weight: 600; }
#view-courts .dir-foot .stat-item + .stat-item { margin-left: 0; padding-left: 0; border-left: 0; }
#view-courts .dir-foot .sync-act #syncLabel { position: static; width: auto; height: auto; overflow: visible; clip: auto; }
#view-courts .dir-foot .sync-act { width: auto; padding: 0 14px; }

@media (min-width: 1280px) {
  #sidebar.fsheet { display: contents; }
  #sidebar .sidebar-head, #sidebar .sidebar-apply, #sidebarBackdrop { display: none; }
  .fsort .facet-title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; margin: 0; }
}

@media (max-width: 1279px) {
  /* Phone grid: search full width; Filters · Time · Clear · Near me; views ·
     count · Share. The rows are display:contents so one grid places all. */
  .ctool {
    display: grid; gap: 8px; padding: 12px 0 10px;
    grid-template-columns: auto auto minmax(0, 1fr) auto;
    grid-template-areas:
      "search search search search"
      "filters time clear near"
      "views views result share";
    align-items: center;
  }
  .ctool-row, .ctool-end { display: contents; }
  .ctool-main .field.search { grid-area: search; max-width: none; }
  #view-courts .ctool .near-me { grid-area: near; justify-self: end; }
  .ctool-main .view-seg { grid-area: views; margin: 0; justify-self: start; }
  #filtersBtn { grid-area: filters; }
  .fgroup-time { grid-area: time; position: static; }
  .ctool-clear { grid-area: clear; justify-self: start; }
  /* Live now (live notes): its own row, so it never jumps when Clear appears. */
  #fpLive { grid-column: 1 / -1; justify-self: start; }
  #view-courts .ctool-end .result-line { grid-area: result; justify-self: start; max-width: 100%; }
  #view-courts:not(.is-map) .ctool-end .result-line { white-space: normal; font-size: 12px; line-height: 1.3; }
  #shareBtn { grid-area: share; }
  /* The Time popover spans the toolbar under its last row. */
  .fpop-time { left: 0; right: 0; width: auto; top: calc(100% + 4px); transform: none; }
  .fpop-time.is-open { animation: none; }
  /* Inside the sheet every popover is a plain section and the pills go. */
  #sidebar .chip-pop { display: none; }
  #sidebar .fgroup { position: static; }
  #sidebar .fpop {
    display: block; position: static; transform: none; min-width: 0; animation: none;
    padding: 14px 0; border: 0; border-radius: 0; box-shadow: none; background: none;
  }
  #sidebar .fpop .facet-title { font-size: 13px; font-weight: 700; color: var(--ink); margin: 0 0 8px; }
  #sidebar .fsort { display: block; padding: 14px 0; }
  #sidebar .fsort .field select { width: 100%; box-shadow: none; }
  #sidebar .fsort .facet-title { font-size: 13px; font-weight: 700; color: var(--ink); margin: 0 0 8px; }
}
@media (max-width: 767px) {
  #view-courts .ctool .near-me { width: var(--tap); padding: 0; justify-content: center; }
}

/* Map view: the map owns the screen beside the shell sidebar. */
#view-courts.is-map .map-wrap:not([hidden]) {
  position: fixed; top: var(--cm-top, 180px); bottom: var(--cm-bottom, 0px); left: 0; right: 0;
  border: 0; border-radius: 0;
}
#view-courts.is-map #map { height: 100%; min-height: 0; }
#view-courts.is-map .map-wrap:not([hidden]) { border-top: 0; }
/* The toolbar floats over the map's top edge. The bar itself is click-through
   so the map stays draggable between controls; every control gets a surface
   and a shadow so it reads on the tiles. Its z-index (36) sits above the
   phone tab bar, so the Filters sheet inside it still covers the bar. */
#view-courts.is-map:not([hidden]) .ctool {
  position: fixed; z-index: 36; top: calc(var(--cm-top, 0px) + 14px); left: var(--gutter); right: var(--gutter);
  padding: 0; pointer-events: none;
}
#view-courts.is-map .ctool :is(.field, .near-me, .view-seg, .chip-pop, .filters-open, .ctool-clear, .result-line, .share-act, .fpop, #sidebar) { pointer-events: auto; }
#view-courts.is-map .ctool :is(.near-me, .view-seg, .filters-open, .fsort select, .share-act) { box-shadow: var(--sh-float); }
#view-courts.is-map .ctool .view-seg { background: var(--surface-2); }
#view-courts.is-map .ctool .share-act { background: var(--surface); }
#view-courts.is-map .ctool .ctool-clear { background: var(--surface); box-shadow: var(--shadow-md); border-radius: var(--r-pill); }
#view-courts.is-map .ctool-end .result-line {
  display: inline-flex; align-items: center; min-height: 32px; padding: 0 12px;
  background: var(--surface); color: var(--ink-2); border-radius: var(--r-pill); box-shadow: var(--shadow-sm);
}
@media (min-width: 1280px) {
  #view-courts.is-map .map-wrap:not([hidden]) { left: var(--sidebar-w); }
  #view-courts.is-map:not([hidden]) .ctool { left: calc(var(--sidebar-w) + var(--gutter)); }
}
@media (max-width: 1279px) {
  #view-courts.is-map:not([hidden]) .ctool { top: calc(var(--cm-top, 0px) + 10px); left: 12px; right: 12px; }
}
/* Beside the rail, not over its logo and + New. */
@media (min-width: 768px) and (max-width: 1023.98px) {
  #view-courts.is-map:not([hidden]) .ctool { left: calc(var(--rail-w) + var(--safe-l) + 12px); }
}
html:has(#view-courts.is-map:not([hidden])) { overflow: hidden; }
body:has(#view-courts.is-map:not([hidden])) #pageFoot { display: none; }

/* "You are here" (Near me): a small Ink dot with a surface ring and a soft
   halo. Not a pin, never interactive, sits under the court popups. It's a
   MapLibre HTML marker (anchor centre, placed by transform), so the 18px box
   is the span plus its margin and stays upright on the tilted map. */
.map-me { pointer-events: none; width: 18px; height: 18px; }
.map-me span {
  display: block; width: 12px; height: 12px; margin: 3px; border-radius: 50%;
  background: var(--ink); box-shadow: 0 0 0 3px var(--surface), 0 0 0 9px color-mix(in srgb, var(--ink) 14%, transparent);
}

/* Checked is a fine line under the name and address, not a column or a
   footer, so the booking CTA ends every card and row. */
.court-check { margin: 2px 0 0; line-height: 1.3; }
/* Popular with DinkedIn players / paid (Set 8): one quiet line on the card. */
.court-pop { margin: 0; font-size: 12.5px; color: var(--ink-2); line-height: 1.35; }
/* "About this info" under From DinkedIn players: one quiet footer that opens
   to where each value comes from and when the facts were checked. */
.cd-about { font-size: var(--fs-xs); color: var(--ink-3); }
.cd-about > summary { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; min-height: var(--tap); cursor: pointer; list-style: none; }
.cd-about > summary::-webkit-details-marker { display: none; }
.cd-about-more { color: var(--ink-2); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.cd-about-more::after { content: ""; display: inline-block; width: 6px; height: 6px; margin-left: 6px; border: solid currentColor; border-width: 0 1.6px 1.6px 0; transform: translateY(-2px) rotate(45deg); transition: transform var(--dur-2) var(--ease-out); }
.cd-about[open] .cd-about-more::after { transform: translateY(1px) rotate(-135deg); }
.cd-about-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.cd-about-list li { display: grid; grid-template-columns: 6.5em 1fr; gap: 8px; }
.cd-about-k { color: var(--ink-2); font-weight: 600; font-variant-numeric: tabular-nums; }
.cd-about-note { margin: 8px 0 0; }
.courts-table .t-check { display: block; margin-top: 2px; white-space: normal; line-height: 1.3; text-align: left; justify-self: auto; grid-area: auto; }

/* One CTA shape everywhere: the booking/contact button fills a fixed slot,
   the quiet "I booked this" / "Log" sits in a fixed slot beside it, so every
   card and row has the same two widths. One line, centred, ellipsis. */
.court .court-foot {
  display: grid; grid-template-columns: minmax(0, 1fr) 7.5rem; align-items: center; gap: 8px;
}
:is(.court-foot, .t-acts) > :is(.book-link, .cta-log) {
  display: block; width: 100%; min-width: 0; margin: 0;
  line-height: calc(var(--tap) - 2px); text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Centred on the capitals, not sat on the baseline: half a cap height
   (~.35em) up from the baseline, minus half the 18px glyph. */
:is(.court-foot, .t-acts) > .book-link svg { vertical-align: calc(.35em - 9px); margin-right: 6px; }
.cta-none {
  display: flex; align-items: center; justify-content: center; min-height: var(--tap); margin: 0;
  font-size: var(--fs-xs); color: var(--ink-3); white-space: nowrap;
}
.courts-table td.t-book { width: 1%; }
.t-acts { display: grid; grid-template-columns: 13rem 7.5rem; align-items: center; gap: 8px; }
/* Side by side needs ~340px (the longest booking label at 15/700 plus the
   log slot); narrower cards stack the pair instead of cutting the label. */
@container court (max-width: 343.98px) {
  .court .court-foot { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .court .court-foot > .cta-log { width: auto; justify-self: start; margin-left: -10px; }
  .court .cta-none { justify-content: flex-start; }
}
@media (max-width: 767px) {
  /* Phone rows: the CTA pair spans the bottom line, CTA taking the room. */
  .courts-table td.t-book { grid-area: foot; justify-self: stretch; width: auto; }
  .t-acts { grid-template-columns: minmax(0, 1fr) 7.5rem; }
}
/* Narrow phones: the row is under ~340px wide, so the pair stacks like a
   narrow card instead of cutting the booking label. */
@media (max-width: 419.98px) {
  .t-acts { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .t-acts > .cta-log { width: auto; justify-self: start; margin-left: -10px; }
}
/* ── end courts map-first (oct14) ── */

/* ── Home rhythm (Val, 2026-10-14): one gap between every block ──
   The view is a column with a single --sp-3 gap; the hosts carry no outer
   margins of their own, and an empty host takes no slot. */
#view-home:has(> #feedHost) { display: flex; flex-direction: column; gap: var(--sp-3); }
#view-home:has(> #feedHost) > :is(#homeOffline, #homeLiveHost, #storiesHost, #homeNextHost, #homeRankHost):empty { display: none; }
#view-home:has(> #feedHost) > .page-title { margin: 0; }
#view-home .crew-slot { margin: 0; }
#view-home .gstrip { margin-block: 0; }
#view-home .stories-rail { margin-block: 0; }
#view-home .home-rank { margin-block: 0; }
#homeStripHost { display: flex; flex-direction: column; gap: var(--stack); }
#homeStripHost > .feed-create .gform { margin: 0; }
#homeStripHost > .feed-create:not(:has(.gform:not([hidden]))) { display: none; }
#homeNextHost, #homeLiveHost .live-stack { display: flex; flex-direction: column; gap: var(--stack); margin: 0; }
#homeNextHost > * { margin: 0; }

/* Settings cards (Val, 2026-10-14): every block inside a card gets the same
   breathing room, so a note never sits flush under its chips. */
.gsettings > .card.gsec > .form-msg:empty { display: none; }
.gsettings .set-rename { margin: 0; }

/* ── profile gallery + story fit (oct14) ── */
/* Profile Photos: uniform square tiles, 3 across in a phone column, 4 then 5 as the
   profile column widens (container query, so it follows the column, not the window). */
#profilePhotos { container-type: inline-size; }
.photo-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px;
  border-radius: 0; overflow: visible;
}
@container (min-width: 520px) { .photo-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@container (min-width: 760px) { .photo-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
.photo-tile {
  position: relative; display: block; width: 100%; aspect-ratio: 1; min-width: 0;
  margin: 0; padding: 0; border: 0; border-radius: var(--r-inner); overflow: hidden;
  background: var(--surface-2); cursor: zoom-in;
  transition: transform var(--dur-1) var(--ease-out);
}
.photo-tile img {
  position: absolute; inset: 0; display: block; width: 100%; height: 100%;
  object-fit: cover; background: none; transition: filter var(--dur-2) var(--ease-out);
}
/* A hairline so a bright photo still reads as a tile on Paper. */
@media (hover: hover) { .photo-tile:hover img { filter: brightness(.9); } }
.photo-tile:active { transform: scale(.97); }
.photo-tile:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: var(--r-inner); }
@media (prefers-reduced-motion: reduce) { .photo-tile:active { transform: none; } }
#profilePhotos .form-actions { margin-top: var(--sp-3); }

/* Story viewer: one frame, everything else lines up with it.
   Phone: the frame is the screen (full-bleed). Wider: a centred 9:16 frame on black,
   as tall as the window allows. Progress + head float over the frame's top, caption +
   foot over its bottom; the photo is always shown whole (contain) unless it is a
   portrait already within 10% of the frame's shape (.is-fill, set in storyShow). */
dialog.story-viewer {
  --sv-h: 100dvh; --sv-w: 100vw;
  display: grid; grid-template-columns: var(--sv-w); grid-template-rows: var(--sv-h);
  justify-content: center; align-content: center; padding: 0;
}
.story-viewer > * { grid-area: 1 / 1; min-width: 0; min-height: 0; }
.story-viewer .story-media { position: relative; z-index: 0; display: block; overflow: hidden; background: var(--ink); }
.story-viewer .story-media img {
  position: absolute; inset: 0; width: 100%; height: 100%; max-width: none; max-height: none;
  object-fit: contain; border-radius: 0;
}
.story-viewer .story-media img.is-fill { object-fit: cover; }
.story-viewer .story-top, .story-viewer .story-bottom {
  position: relative; z-index: 1; display: flex; flex-direction: column;
  padding-inline: var(--sp-3); pointer-events: none;
}
.story-viewer .story-top :is(button, a), .story-viewer .story-bottom :is(button, a) { pointer-events: auto; }
.story-viewer .story-top {
  align-self: start; padding-top: calc(var(--sp-2) + var(--safe-t)); padding-bottom: var(--sp-6, 32px);
  background: color-mix(in srgb, var(--ink) 55%, transparent);
}
.story-viewer .story-head { padding: var(--sp-2) 0 0; }
.story-viewer .story-bottom {
  align-self: end; padding-top: var(--sp-6, 32px); padding-bottom: calc(var(--sp-3) + var(--safe-b));
  background: color-mix(in srgb, var(--ink) 60%, transparent);
}
.story-viewer .story-caption { margin: 0; max-height: 30dvh; overflow-y: auto; pointer-events: auto; }
.story-viewer .story-foot:empty { display: none; }
.story-viewer .story-tap { z-index: 0; }
.story-viewer .story-tap.is-end { cursor: default; }
.story-viewer :is(button, a):focus-visible { outline-color: var(--white); }
@media (min-width: 768px) {
  dialog.story-viewer {
    --sv-h: calc(100dvh - 2 * var(--sp-4));
    --sv-w: min(calc(100vw - 2 * var(--sp-4)), calc(var(--sv-h) * 9 / 16));
    background: var(--ink);
  }
  dialog.story-viewer::backdrop { background: var(--ink); }
  .story-viewer .story-media { border-radius: var(--r-card); }
  .story-viewer .story-top { border-radius: var(--r-card) var(--r-card) 0 0; padding-top: var(--sp-3); }
  .story-viewer .story-bottom { border-radius: 0 0 var(--r-card) var(--r-card); padding-bottom: var(--sp-3); }
}
/* Mouse users get a visible arrow on the half they are over. */
@media (hover: hover) {
  .story-viewer .story-tap::after {
    content: ""; position: absolute; top: 50%; width: 40px; height: 40px; margin-top: -20px;
    border-radius: 50%; background: color-mix(in srgb, var(--white) 90%, transparent) center / 16px no-repeat;
    opacity: 0; transition: opacity var(--dur-2) var(--ease-out);
  }
  .story-viewer .story-tap.is-prev::after { left: var(--sp-3); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%231D2233' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 3L5 8l5 5'/%3E%3C/svg%3E"); }
  .story-viewer .story-tap.is-next::after { right: var(--sp-3); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%231D2233' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 3l5 5-5 5'/%3E%3C/svg%3E"); }
  .story-viewer .story-tap:is(:hover, :focus-visible)::after { opacity: 1; }
  .story-viewer .story-tap.is-end::after { display: none; }
}

/* ═══ welcome START ═══
   The welcome deck (assets/welcome.js, docs/design/welcome-deck.md): six
   swipe cards after sign-up, each a crew host, a looping mockup, a title and
   a line. Every selector lives under .welcome; keyframes are wl-*.
   Mockup rule: the RESTING style of every moving part is its loop's END
   frame, so the global reduced-motion block (which drops all animation)
   leaves each card on its finished picture. The loops (6s each, a hold on
   the end frame, then a short fade back to the start) are exempt from the
   three-duration rule, like the skeleton loops. Cards off screen are paused
   on their first frame (.wl-card:not(.is-on)); welcome.js rewinds them. */
.welcome {
  --wl-t: 6s;
  --wl-pb: 16px;                   /* mockup bottom padding (more with a host) */
  --wl-water: color-mix(in srgb, var(--line-2) 72%, var(--surface-2));
  border: 0; padding: 0; color: var(--ink); background: var(--surface);
  width: 100%; max-width: 100%;
  height: calc(100dvh - var(--safe-t) - 8px); max-height: none;
  margin: auto 0 0; border-radius: var(--r-card) var(--r-card) 0 0;
  box-shadow: var(--shadow-lg); overflow: hidden;
}
.welcome::backdrop { background: var(--scrim); }
.welcome .wl-in { display: flex; flex-direction: column; height: 100%; min-height: 0; }

/* Top row: where you are (dots) and a quiet way out. */
.welcome .wl-top {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  padding: 8px 8px 0 10px;
}
.welcome .wl-dots { display: flex; align-items: center; }
.welcome .wl-dot {
  width: 22px; height: var(--tap); padding: 0; border: 0; background: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.welcome .wl-dot span {
  display: block; width: 8px; height: 8px; border-radius: var(--r-pill); background: var(--line-2);
  transition: width var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out);
}
.welcome .wl-dot[aria-current] { width: 32px; }
.welcome .wl-dot[aria-current] span { width: 20px; background: var(--ink); }
/* The ring goes round the dot itself, not the 44px tap box around it. */
.welcome .wl-dot:focus-visible { outline: none; }
.welcome .wl-dot:focus-visible span { outline: 2px solid var(--ink); outline-offset: 3px; }
.welcome .wl-skip.is-gone { visibility: hidden; }

/* The deck: a row of full-width cards the track slides across. */
.welcome .wl-view { flex: 1 1 auto; min-height: 0; overflow: hidden; touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.welcome .wl-track { display: flex; height: 100%; transition: transform var(--dur-3) var(--ease-out); }
.welcome .wl-track.is-dragging { transition: none; }
.welcome .wl-card {
  flex: 0 0 100%; min-width: 0; height: 100%;
  display: flex; flex-direction: column; justify-content: center;
  padding: 8px 16px 0;
  overflow-y: auto; overscroll-behavior: contain;   /* last resort on very short screens */
}
.welcome .wl-card.has-host { --wl-pb: 16px; }
.welcome .wl-stage { flex: 1 1 auto; min-height: 196px; max-height: 400px; display: flex; }
.welcome .wl-copy { flex: none; margin-top: var(--sp-5); }
.welcome .wl-title { margin: 0; font-size: var(--fs-lg); font-weight: 700; letter-spacing: -.01em; line-height: 1.2; }
.welcome .wl-body { margin: 6px 0 0; font-size: var(--fs-base); color: var(--ink-2); line-height: 1.45; text-wrap: pretty; }
.welcome .wl-foot { flex: none; display: flex; flex-direction: column; gap: 4px; padding: var(--sp-4) 16px calc(16px + var(--safe-b)); }
.welcome .wl-later[hidden] { display: none; }

/* The host: 64px character, its floating paddle and the bubble (the look of
   .crew-say-line, built here because say() stands down on the sign-in route). */
.welcome .wl-host { flex: none; display: flex; align-items: flex-end; gap: var(--sp-3); margin-top: var(--sp-3); padding-left: 4px; min-width: 0; }
.welcome .wl-host-who { position: relative; flex: none; width: 64px; height: 64px; line-height: 0; }
.welcome .wl-prop { position: absolute; left: 44px; top: -34px; width: 26px; height: 44px; line-height: 0; pointer-events: none; }
.welcome .wl-paddle {
  display: block; transform-origin: 50% 82%; transform: rotate(32deg);
  animation: wl-float 2.4s ease-in-out infinite;
}
.welcome .wl-ball { position: absolute; left: 16px; top: -12px; display: block; animation: wl-dink 1.2s ease-in-out infinite; }
.welcome .wl-bubble {
  position: relative; margin: 0 0 6px; min-width: 0; max-width: var(--w-bubble);
  padding: 8px 12px; border-radius: var(--r-inner);
  background: var(--surface); border: 0;
  color: var(--ink); font-size: var(--fs-sm); font-weight: 600; line-height: 1.35;
  box-shadow: var(--sh-card);
}
.welcome .wl-bubble::before { content: none; display: none; }

/* The first card: pick your crew. Three questions, one tap each (chips),
   then who you got; the host turns into them. */
.welcome .wl-card-quiz { justify-content: flex-start; padding-top: var(--sp-4); }
.welcome .wl-card-quiz .wl-copy { margin-top: 0; }
.welcome .wl-quiz { flex: none; display: flex; flex-direction: column; gap: var(--sp-4); margin-top: var(--sp-5); user-select: none; }
.welcome .wl-q-t { margin: 0 0 8px; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); }
.welcome .wl-q-opts { display: flex; flex-wrap: wrap; gap: 8px; }
.welcome .wl-match { flex: none; min-height: 1.4em; margin: var(--sp-4) 0 0; font-size: var(--fs-sm); color: var(--ink-2); }
.welcome .wl-card-quiz .wl-host { margin-top: auto; padding-top: var(--sp-3); }

/* ── mockup parts (the real app in miniature) ── */
.welcome .wl-mock {
  position: relative; flex: 1; min-width: 0; overflow: hidden;
  background: var(--surface-2); border-radius: var(--r-card);
  font-size: 13px; line-height: 1.3; color: var(--ink); pointer-events: none;
}
.welcome .wl-scene {
  position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; gap: 10px;
  padding: 16px 16px var(--wl-pb);
  animation: wl-scene var(--wl-t) linear infinite;
}
.welcome .wl-btn {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  height: 34px; padding: 0 14px; border-radius: var(--r-inner);
  background: var(--accent); color: var(--accent-ink);
  font-size: 13px; font-weight: 600; white-space: nowrap;
}
.welcome .wl-pill {
  display: inline-flex; align-items: center; height: 22px; padding: 0 9px; border-radius: var(--r-pill);
  background: var(--surface-2); color: var(--ink-2); font-size: 11.5px; font-weight: 600; white-space: nowrap;
}
.welcome .wl-pill.is-good { background: var(--hi-wash); color: var(--hi-strong); }

/* 1 · Courts map. Grayscale like #map's tiles; pins are its lime circle markers. */
.welcome .wl-map .wl-scene { padding: 0; justify-content: flex-end; }
.welcome .wl-map-art { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.welcome .wl-water { fill: var(--wl-water); }
.welcome .wl-land { fill: var(--surface-2); }
.welcome .wl-road path { fill: none; stroke: var(--surface); stroke-width: 5; stroke-linecap: round; }
.welcome .wl-road-sm path { stroke-width: 2.5; }
.welcome .wl-pin-dot { fill: var(--accent); stroke: var(--ink); stroke-width: 2; }
.welcome .wl-pin-in, .welcome .wl-pin-dot, .welcome .wl-ring { transform-box: fill-box; transform-origin: 50% 50%; }
.welcome .wl-pin-in { transform-origin: 50% 100%; }
.welcome .wl-pin3 .wl-pin-dot { transform: scale(1.3); }
.welcome .wl-ring { fill: none; stroke: var(--hi-strong); stroke-width: 2; opacity: 0; }
.welcome .wl-court {
  position: relative; margin: 0 12px 12px; display: flex; align-items: center; gap: 12px;
  padding: 10px 10px 10px 12px; background: var(--surface); border: 0;
  border-radius: var(--r-inner); box-shadow: var(--shadow-md); box-shadow: var(--sh-card); }
.welcome .wl-court-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.welcome .wl-bar { display: block; width: 62%; height: 10px; border-radius: var(--r-pill); background: var(--ink); opacity: .82; }
.welcome .wl-pills { display: flex; gap: 6px; flex-wrap: wrap; }
.welcome .wl-book { transform-origin: 50% 50%; }

/* 2 · A game card, then the up-next banner (--hi-strong edge, --hi-wash fill). */
.welcome .wl-gcard {
  display: flex; flex-direction: column; gap: 12px; padding: 12px;
  background: var(--surface); border: 0; border-radius: var(--r-inner);
  box-shadow: var(--sh-card);
}
.welcome .wl-grow { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; }
.welcome .wl-when { font-size: 15px; font-weight: 700; white-space: nowrap; }
.welcome .wl-count { display: inline-grid; justify-items: end; font-size: 12px; font-weight: 600; color: var(--ink-2); white-space: nowrap; }
.welcome .wl-count > span { grid-area: 1 / 1; }
.welcome .wl-c3 { opacity: 0; }
.welcome .wl-seats { display: flex; gap: 6px; }
.welcome .wl-seat { width: 32px; height: 32px; border-radius: 50%; flex: none; position: relative; }
.welcome .wl-seat.is-open { background: var(--surface-2); }
.welcome .wl-face {
  position: absolute; inset: 0; border-radius: 50%; display: grid; place-items: center;
  font-size: 12px; font-weight: 700; color: var(--ink); background: var(--line-2);
}
.welcome .wl-face[data-t="1"] { background: var(--volley); }
.welcome .wl-face[data-t="2"] { background: var(--smash); }
.welcome .wl-face[data-t="3"] { background: var(--pop); }
.welcome .wl-face.is-me { background: var(--ink); color: var(--surface); font-size: 10px; box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--hi-strong); }
.welcome .wl-gcard .wl-btn { display: grid; width: 100%; }
.welcome .wl-gcard .wl-btn > span { grid-area: 1 / 1; text-align: center; }
.welcome .wl-imin-a { opacity: 0; }
.welcome .wl-next {
  display: flex; align-items: center; gap: 8px; padding: 10px 12px;
  background: var(--hi-wash); border: 0; border-radius: var(--r-inner);
  font-size: 13px; color: var(--ink);
  box-shadow: var(--sh-card);
}
.welcome .wl-next b { font-weight: 700; }
.welcome .wl-next-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--hi-strong); }

/* 3 · Score ticks to a win; You climbs the 3-row board (.lb-row in miniature). */
.welcome .wl-score {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: end; gap: 8px;
  padding: 12px 14px; background: var(--surface); border: 0; border-radius: var(--r-inner);
  box-shadow: var(--sh-card);
}
.welcome .wl-side { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.welcome .wl-side.is-them { align-items: flex-end; }
.welcome .wl-team { font-size: 12px; font-weight: 600; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.welcome .wl-numrow { display: flex; align-items: center; gap: 8px; }
.welcome .wl-num {
  display: block; height: 1em; font-variant-numeric: tabular-nums;
  font-size: 34px; font-weight: 700; line-height: 1; letter-spacing: -.02em;
}
.welcome .is-them .wl-num { color: var(--ink-2); }
.welcome .wl-roll { overflow: hidden; min-width: 2ch; }
.welcome .wl-roll-in { display: block; transform: translateY(-3em); }
.welcome .wl-roll-in > span { display: block; height: 1em; }
.welcome .wl-dash { font-size: 22px; font-weight: 700; color: var(--ink-2); padding-bottom: 6px; }
.welcome .wl-win {
  display: inline-flex; align-items: center; height: 22px; padding: 0 9px; border-radius: var(--r-pill);
  background: var(--accent); color: var(--accent-ink);
  font-size: 11.5px; font-weight: 700;
}
.welcome .wl-lb { --wl-rh: 40px; position: relative; height: calc(3 * var(--wl-rh)); flex: none; }
.welcome .wl-row {
  position: absolute; left: 0; right: 0; top: calc(var(--slot) * var(--wl-rh)); height: var(--wl-rh);
  display: grid; grid-template-columns: 26px minmax(0, 1fr) auto; align-items: center; gap: 10px;
  padding: 0 4px;
}
.welcome .wl-row.is-first { border-top-color: transparent; }
.welcome .wl-row.is-me { z-index: 1; background: var(--hi-wash); border-top-color: transparent; border-radius: var(--r-inner); margin-inline: -4px; padding-inline: 8px; }
.welcome .wl-rank { font-size: 13px; font-weight: 700; color: var(--ink-2); text-align: center; line-height: 1; }
.welcome .wl-name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.welcome .wl-row.is-me .wl-name { font-weight: 700; }
.welcome .wl-wl { font-variant-numeric: tabular-nums; font-size: 12.5px; color: var(--ink-2); }
.welcome .wl-swap { display: inline-grid; justify-items: center; }
.welcome .wl-wl.wl-swap { justify-items: end; }
.welcome .wl-swap > span { grid-area: 1 / 1; }
.welcome .wl-old { opacity: 0; }

/* 4 · The locker door: 8 spots, stickers on four, the gift's note below. */
.welcome .wl-locker .wl-scene { align-items: center; }
.welcome .wl-door {
  width: min(100%, 236px); padding: 10px 12px 14px; border-radius: var(--r-inner);
  background: var(--surface); box-shadow: var(--sh-card);
}
.welcome .wl-vents { display: flex; flex-direction: column; gap: 4px; align-items: center; margin-bottom: 10px; }
.welcome .wl-vents span { display: block; width: 46%; height: 3px; border-radius: var(--r-pill); background: var(--line-2); }
.welcome .wl-spots { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.welcome .wl-spot { aspect-ratio: 1; border-radius: var(--r-inner); background: var(--surface-2); display: grid; place-items: center; }
.welcome .wl-spot.is-open { background: var(--sand); opacity: .6; }
.welcome .wl-st { display: block; width: 88%; line-height: 0; }
.welcome .wl-st svg { width: 100%; height: auto; display: block; }
.welcome .wl-dotsticker { display: block; aspect-ratio: 1; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px var(--white); }
.welcome .wl-locker .wl-note { width: min(100%, 236px); box-sizing: border-box; }

/* 5 · Courtside: six game tiles, the picked one ringed, its best below. */
.welcome .wl-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.welcome .wl-tile {
  display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 8px 4px 7px; min-width: 0;
  background: var(--surface); border-radius: var(--r-inner); box-shadow: var(--sh-card);
}
.welcome .wl-tile-face { width: 28px; height: 28px; line-height: 0; display: grid; place-items: center; }
.welcome .wl-tile-name { font-size: 11px; font-weight: 600; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.welcome .wl-tile-pick { box-shadow: 0 0 0 2px var(--hi-strong); }
.welcome .wl-tile-pick .wl-tile-name { color: var(--ink); }
.welcome .wl-best {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 10px 14px; background: var(--surface); border-radius: var(--r-inner); box-shadow: var(--sh-card);
}
.welcome .wl-best .wl-num { font-size: 28px; }

/* 6 · A home screen with DinkedIn dropping into the last slot. */
.welcome .wl-home .wl-scene { align-items: center; }
.welcome .wl-phone {
  width: 172px; padding: 18px 14px; border-radius: var(--r-card);
  background: var(--surface);
}
.welcome .wl-apps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px 8px; justify-items: center; }
.welcome .wl-app { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 100%; }
.welcome .wl-app-ico { display: block; width: 30px; height: 30px; border-radius: var(--r-field); background: var(--line-2); }
.welcome .wl-app-lbl { display: block; width: 70%; height: 4px; border-radius: var(--r-pill); background: var(--line-2); opacity: .7; }
.welcome .wl-app-drop { position: relative; display: block; line-height: 0; }
.welcome .wl-app-me .wl-app-ico { background: var(--surface-2); }
.welcome .wl-app-name { font-size: 8.5px; font-weight: 700; line-height: 1; white-space: nowrap; }
.welcome .wl-tap { position: absolute; inset: -4px; border-radius: var(--r-field); border: 2px solid var(--hi-strong); opacity: 0; }
.welcome .wl-home .wl-note { width: min(100%, 236px); box-sizing: border-box; }

/* ── the loops ── (start frame → end frame, hold, fade, back to start) */
.welcome .wl-pin1 .wl-pin-in { animation: wl-pin1 var(--wl-t) linear infinite; }
.welcome .wl-pin2 .wl-pin-in { animation: wl-pin2 var(--wl-t) linear infinite; }
.welcome .wl-pin3 .wl-pin-in { animation: wl-pin3 var(--wl-t) linear infinite; }
.welcome .wl-pin4 .wl-pin-in { animation: wl-pin4 var(--wl-t) linear infinite; }
.welcome .wl-pin5 .wl-pin-in { animation: wl-pin5 var(--wl-t) linear infinite; }
.welcome .wl-pin3 .wl-pin-dot { animation: wl-pick var(--wl-t) var(--ease-out) infinite; }
.welcome .wl-ring { animation: wl-ring var(--wl-t) ease-out infinite; }
.welcome .wl-court { animation: wl-court var(--wl-t) linear infinite; }
.welcome .wl-book { animation: wl-press-book var(--wl-t) linear infinite; }

.welcome .wl-f1 { animation: wl-f1 var(--wl-t) linear infinite; }
.welcome .wl-f2 { animation: wl-f2 var(--wl-t) linear infinite; }
.welcome .wl-f3 { animation: wl-f3 var(--wl-t) linear infinite; }
.welcome .wl-f4 { animation: wl-f4 var(--wl-t) linear infinite; }
.welcome .wl-c3 { animation: wl-c3 var(--wl-t) linear infinite; }
.welcome .wl-c4 { animation: wl-c4 var(--wl-t) linear infinite; }
.welcome .wl-gcard .wl-btn { animation: wl-press-in var(--wl-t) linear infinite; }
.welcome .wl-imin-a { animation: wl-imin-a var(--wl-t) linear infinite; }
.welcome .wl-imin-b { animation: wl-imin-b var(--wl-t) linear infinite; }
.welcome .wl-next { animation: wl-next var(--wl-t) linear infinite; }
.welcome .wl-next-dot { animation: wl-beat 1.2s ease-in-out infinite; }

.welcome .wl-roll-in { animation: wl-roll var(--wl-t) linear infinite; }
.welcome .wl-win { animation: wl-win var(--wl-t) linear infinite; }
.welcome .wl-row-you { animation: wl-up var(--wl-t) linear infinite; }
.welcome .wl-row-ben { animation: wl-down var(--wl-t) linear infinite; }
.welcome .wl-old { animation: wl-old var(--wl-t) linear infinite; }
.welcome .wl-new { animation: wl-new var(--wl-t) linear infinite; }

.welcome .wl-st1 { animation: wl-f1 var(--wl-t) linear infinite; }
.welcome .wl-st2 { animation: wl-f2 var(--wl-t) linear infinite; }
.welcome .wl-st3 { animation: wl-f3 var(--wl-t) linear infinite; }
.welcome .wl-st4 { animation: wl-slap var(--wl-t) linear infinite; }

.welcome .wl-tile-pick { animation: wl-tile var(--wl-t) linear infinite; }

.welcome .wl-app-drop { animation: wl-f4 var(--wl-t) linear infinite; }
.welcome .wl-app-name { animation: wl-c4 var(--wl-t) linear infinite; }
.welcome .wl-tap { animation: wl-tap var(--wl-t) ease-out infinite; }

/* Off screen: hold the first frame. Coming back: welcome.js rewinds. */
.welcome .wl-card:not(.is-on) :is(.wl-stage, .wl-host) * { animation-play-state: paused; }
.welcome .wl-card.is-rewind * { animation: none !important; }

/* Short phones (an iPhone SE in Safari shows ~550px): the stage gives way first. */
@media (max-height: 640px) {
  .welcome .wl-stage { min-height: 132px; }
  .welcome .wl-copy { margin-top: var(--sp-3); }
  .welcome .wl-foot { padding-top: var(--sp-3); }
}

/* Desktop: a centred dialog. */
@media (min-width: 768px) {
  .welcome {
    width: 420px; height: fit-content; max-height: calc(100dvh - 48px);
    margin: auto; border-radius: var(--r-card); border: 0;
  box-shadow: var(--sh-card);
}
  .welcome .wl-in { height: auto; }
  .welcome .wl-top { padding: 12px 12px 0 14px; }
  .welcome .wl-card { padding: 8px 24px 0; justify-content: flex-start; }
  .welcome .wl-stage { flex: none; height: clamp(132px, 100dvh - 400px, 300px); max-height: none; }
  .welcome .wl-foot { padding: var(--sp-5) 24px 24px; }
}

/* Opening and closing: a sheet from the bottom on a phone, a dialog's
   scale-in on desktop. (The global reduced-motion block drops these too.) */
@media (prefers-reduced-motion: no-preference) {
  .welcome[open] { animation: wl-sheet-in var(--dur-3) var(--ease-out); }
  .welcome[open].is-closing { animation: wl-sheet-out var(--dur-2) var(--ease-in) forwards; }
  .welcome[open]::backdrop { animation: wl-fade-in var(--dur-2) var(--ease-out); }
  .welcome[open].is-closing::backdrop { animation: wl-fade-out var(--dur-2) var(--ease-in) forwards; }
  @media (min-width: 768px) {
    .welcome[open] { animation-name: wl-dlg-in; animation-duration: var(--dur-2); }
    .welcome[open].is-closing { animation-name: wl-dlg-out; }
  }
}

@keyframes wl-sheet-in { from { transform: translateY(100%); } to { transform: none; } }
@keyframes wl-sheet-out { from { transform: none; } to { transform: translateY(100%); } }
@keyframes wl-dlg-in { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: none; } }
@keyframes wl-dlg-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(.96); } }
@keyframes wl-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes wl-fade-out { from { opacity: 1; } to { opacity: 0; } }

/* The paddle bobs where the host is looking; Pop's ball dinks on it. */
@keyframes wl-float { 0%, 100% { transform: rotate(32deg) translateY(0); } 50% { transform: rotate(26deg) translateY(-4px); } }
@keyframes wl-dink {
  0%, 100% { transform: translateY(0); animation-timing-function: cubic-bezier(.3, 0, .7, 1); }
  50% { transform: translateY(-14px); animation-timing-function: cubic-bezier(.3, 0, .7, 1); }
}

/* Every mockup fades out at the end of its hold and back in on frame 0. */
@keyframes wl-scene { 0%, 91% { opacity: 1; } 94%, 97% { opacity: 0; } 100% { opacity: 1; } }

/* Map: five pins drop in (6–22%), the third is picked and pulses (30–60%),
   its card rises (34–40%), Book presses (56–60%), hold to 91%. */
@keyframes wl-pin1 { 0%, 6% { opacity: 0; transform: translateY(-16px); } 9% { opacity: 1; transform: translateY(0) scale(1.2, .8); } 11%, 94% { opacity: 1; transform: none; } 95%, 100% { opacity: 0; transform: translateY(-16px); } }
@keyframes wl-pin2 { 0%, 10% { opacity: 0; transform: translateY(-16px); } 13% { opacity: 1; transform: translateY(0) scale(1.2, .8); } 15%, 94% { opacity: 1; transform: none; } 95%, 100% { opacity: 0; transform: translateY(-16px); } }
@keyframes wl-pin3 { 0%, 14% { opacity: 0; transform: translateY(-16px); } 17% { opacity: 1; transform: translateY(0) scale(1.2, .8); } 19%, 94% { opacity: 1; transform: none; } 95%, 100% { opacity: 0; transform: translateY(-16px); } }
@keyframes wl-pin4 { 0%, 18% { opacity: 0; transform: translateY(-16px); } 21% { opacity: 1; transform: translateY(0) scale(1.2, .8); } 23%, 94% { opacity: 1; transform: none; } 95%, 100% { opacity: 0; transform: translateY(-16px); } }
@keyframes wl-pin5 { 0%, 22% { opacity: 0; transform: translateY(-16px); } 25% { opacity: 1; transform: translateY(0) scale(1.2, .8); } 27%, 94% { opacity: 1; transform: none; } 95%, 100% { opacity: 0; transform: translateY(-16px); } }
@keyframes wl-pick { 0%, 30% { transform: scale(1); } 33% { transform: scale(1.45); } 35%, 94% { transform: scale(1.3); } 95%, 100% { transform: scale(1); } }
@keyframes wl-ring {
  0%, 32% { opacity: 0; transform: scale(1); }
  33% { opacity: .9; transform: scale(1.2); }
  45% { opacity: 0; transform: scale(2.8); }
  46% { opacity: .9; transform: scale(1.2); }
  58%, 100% { opacity: 0; transform: scale(2.8); }
}
@keyframes wl-court {
  0%, 34% { opacity: 0; transform: translateY(calc(100% + 16px)); }
  40% { opacity: 1; transform: translateY(-3px); }
  42%, 94% { opacity: 1; transform: none; }
  95%, 100% { opacity: 0; transform: translateY(calc(100% + 16px)); }
}
@keyframes wl-press-book {
  0%, 55% { transform: none; background-color: var(--accent); }
  57% { transform: scale(.92); background-color: var(--accent-2); }
  60%, 100% { transform: none; background-color: var(--accent); }
}

/* Game: three faces (6–16%), "3 in, need 1", I'm in presses (30–34%), your
   face drops into the last seat (37–42%), "4 in", the banner (52–58%). */
@keyframes wl-f1 { 0%, 6% { opacity: 0; transform: scale(.4); } 9% { opacity: 1; transform: scale(1.12); } 11%, 94% { opacity: 1; transform: none; } 95%, 100% { opacity: 0; transform: scale(.4); } }
@keyframes wl-f2 { 0%, 11% { opacity: 0; transform: scale(.4); } 14% { opacity: 1; transform: scale(1.12); } 16%, 94% { opacity: 1; transform: none; } 95%, 100% { opacity: 0; transform: scale(.4); } }
@keyframes wl-f3 { 0%, 16% { opacity: 0; transform: scale(.4); } 19% { opacity: 1; transform: scale(1.12); } 21%, 94% { opacity: 1; transform: none; } 95%, 100% { opacity: 0; transform: scale(.4); } }
@keyframes wl-f4 {
  0%, 37% { opacity: 0; transform: translateY(-26px); }
  40% { opacity: 1; transform: translateY(0) scale(1.14, .86); }
  42%, 94% { opacity: 1; transform: none; }
  95%, 100% { opacity: 0; transform: translateY(-26px); }
}
@keyframes wl-c3 { 0%, 18% { opacity: 0; } 21%, 40% { opacity: 1; } 42%, 100% { opacity: 0; } }
@keyframes wl-c4 { 0%, 41% { opacity: 0; transform: translateY(4px); } 44%, 94% { opacity: 1; transform: none; } 95%, 100% { opacity: 0; } }
@keyframes wl-press-in {
  0%, 30% { transform: none; background-color: var(--accent); }
  32% { transform: scale(.95); background-color: var(--accent-2); }
  35%, 100% { transform: none; background-color: var(--accent); }
}
@keyframes wl-imin-a { 0%, 33% { opacity: 1; } 35%, 94% { opacity: 0; } 95%, 100% { opacity: 1; } }
@keyframes wl-imin-b { 0%, 33% { opacity: 0; } 35%, 94% { opacity: 1; } 95%, 100% { opacity: 0; } }
@keyframes wl-next {
  0%, 52% { opacity: 0; transform: translateY(12px); }
  57% { opacity: 1; transform: translateY(-2px); }
  59%, 94% { opacity: 1; transform: none; }
  95%, 100% { opacity: 0; transform: translateY(12px); }
}
@keyframes wl-beat { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(.6); opacity: .5; } }

/* Board: 8 → 9 → 10 → 11 (10–41%), Win (44%), You and Ben trade places
   (54–62%), the marks change as they pass (58–61%). */
@keyframes wl-roll {
  0%, 10% { transform: translateY(0); }
  13%, 24% { transform: translateY(-1em); }
  27%, 38% { transform: translateY(-2em); }
  41%, 94% { transform: translateY(-3em); }
  95%, 100% { transform: translateY(0); }
}
@keyframes wl-win { 0%, 43% { opacity: 0; transform: scale(.6); } 46% { opacity: 1; transform: scale(1.1); } 48%, 94% { opacity: 1; transform: none; } 95%, 100% { opacity: 0; transform: scale(.6); } }
@keyframes wl-up { 0%, 54% { transform: translateY(var(--wl-rh)); } 61% { transform: translateY(-3px); } 63%, 94% { transform: none; } 95%, 100% { transform: translateY(var(--wl-rh)); } }
@keyframes wl-down { 0%, 54% { transform: translateY(calc(-1 * var(--wl-rh))); } 62%, 94% { transform: none; } 95%, 100% { transform: translateY(calc(-1 * var(--wl-rh))); } }
@keyframes wl-old { 0%, 58% { opacity: 1; } 61%, 94% { opacity: 0; } 95%, 100% { opacity: 1; } }
@keyframes wl-new { 0%, 58% { opacity: 0; } 61%, 94% { opacity: 1; } 95%, 100% { opacity: 0; } }

/* Locker: stickers 1–3 pop on (6–21%, the faces' loops), the gift slaps on
   (36–42%), its note rises (52–59%, wl-next). */
@keyframes wl-slap {
  0%, 36% { opacity: 0; transform: scale(1.7) rotate(-14deg); }
  39% { opacity: 1; transform: scale(.92) rotate(4deg); }
  42%, 94% { opacity: 1; transform: none; }
  95%, 100% { opacity: 0; transform: scale(1.7) rotate(-14deg); }
}

/* Courtside: the tile is tapped and ringed (0–8%), the best rolls 39 → 42
   (wl-roll, 10–41%), New best (wl-win, 44%). */
@keyframes wl-tile {
  0%, 4% { transform: none; box-shadow: var(--sh-card); }
  6% { transform: scale(.94); box-shadow: 0 0 0 2px var(--hi-strong); }
  9%, 94% { transform: none; box-shadow: 0 0 0 2px var(--hi-strong); }
  95%, 100% { transform: none; box-shadow: var(--sh-card); }
}

/* Home screen: the icon drops in (wl-f4, 37–42%) with its name (wl-c4), a
   tap pulses on it (46–58%), the note rises (wl-next, 52–59%). */
@keyframes wl-tap {
  0%, 46% { opacity: 0; transform: scale(1); }
  48% { opacity: 1; transform: scale(1); }
  58%, 100% { opacity: 0; transform: scale(1.5); }
}
/* ═══ welcome END ═══ */

/* ── live-notes ──────────────────────────────────────────────
   Live notes (docs/live-notes-plan.md). Owner: design. Three parts:
   1. Map overlay, built by assets/live-crew.js inside a 0×0 MapLibre HTML
      marker (.ln-marker, upright on the tilted map) at the court pin's centre. Everything is placed around
      (0,0) by custom properties live-crew.js sets: --ln-size (character,
      42px), --ln-r (ring radius), --x/--y per character, --bx/--by per crew
      bubble, --ln-up / --ln-down for the note stack and the foot row.
      The root is click-through; only characters, bubbles and tags take taps,
      so the court dot underneath stays tappable.
   2. The court popup's "Live now" section (.ln-pop*).
   3. The Home Live card's note composer and end time (.ln-compose, .ln-ends).
      The Live card is dark in both themes, so those parts read local
      --ln-* colours that the card overrides. */

/* 1. Map ─────────────────────────────────────────────────── */
.ln-marker { width: 0 !important; height: 0 !important; background: none; border: 0; overflow: visible; }
.ln-venue {
  position: absolute; left: 0; top: 0; width: 0; height: 0;
  pointer-events: none; font-family: var(--font-body); line-height: 1.2;
  -webkit-tap-highlight-color: transparent;
}
.ln-venue :is(.ln-char, .ln-bubble, .ln-live, .ln-more) { pointer-events: auto; }

/* The ring: a soft lime wash behind the crew so a cluster reads as one
   place on the grey tiles. Lime as a fill only, no outline stripe. */
.ln-crew { position: absolute; left: 0; top: 0; }
.ln-venue.has-crew .ln-crew::before {
  content: ""; position: absolute; left: 0; top: 0;
  width: calc(2 * var(--ln-r) + var(--ln-size) + 8px); aspect-ratio: 1;
  translate: -50% -50%; border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 24%, transparent);
}

.ln-char {
  position: absolute; left: 0; top: 0; width: var(--ln-size); height: var(--ln-size);
  translate: calc(var(--x, 0px) - 50%) calc(var(--y, 0px) - 50%);
  padding: 0; margin: 0; border: 0; background: none; cursor: pointer; color: inherit;
  border-radius: 50%;
}
.ln-fig { display: block; width: 100%; height: 100%; line-height: 0; transition: transform var(--dur-1) var(--ease-out); }
.ln-fig svg { display: block; width: 100%; height: 100%; overflow: visible; }
.ln-char:active .ln-fig { transform: scale(.96); }
.ln-dot { display: block; width: 62%; height: 62%; margin: 19%; border-radius: 50%; box-shadow: inset 0 0 0 2px var(--ink); }
/* Still (zoomed out, off screen) = the rest pose. The crew's own keyframes
   live in each SVG's <style>; this only switches them off, no re-render. */
:is(.ln-venue.is-still, .ln-char.is-still) .ln-fig * { animation: none !important; }

.ln-you {
  position: absolute; left: 50%; top: calc(100% - 9px); translate: -50% 0; z-index: 1;
  height: 16px; line-height: 16px; padding: 0 6px; border-radius: var(--r-pill);
  font-size: 11px; font-weight: 700; white-space: nowrap; pointer-events: none;
  color: var(--ground); background: var(--ink);
  box-shadow: 0 0 0 2px var(--surface);
}
/* Tap a character: its name, for ~2s. Ink pill above it, on top of everything. */
.ln-name {
  position: absolute; left: 50%; bottom: calc(100% - 4px); translate: -50% 0; z-index: 5;
  max-width: var(--w-pin); height: 24px; line-height: 24px; padding: 0 10px; border-radius: var(--r-pill);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; pointer-events: none;
  font-size: var(--fs-xs); font-weight: 700; color: var(--ground); background: var(--ink);
  box-shadow: var(--shadow-md);
  animation: ln-in var(--dur-2) var(--ease-out);
}
@keyframes ln-in { from { opacity: 0; transform: translateY(4px) scale(.96); } }

/* Bubbles: one line, ellipsis, surface fill, hairline ring so they hold on
   both the light tiles and the inverted dark ones. Height 24 (live-crew.js
   stacks on that). */
.ln-bubble {
  position: absolute; display: block; box-sizing: border-box;
  height: 24px; line-height: 24px; padding: 0 10px; border-radius: var(--r-pill);
  font-size: var(--fs-xs); font-weight: 600; color: var(--ink); white-space: nowrap;
  background: var(--surface);
  /* Outline and shadow come from drop-shadow, which traces the bubble AND
     its tail as one shape. box-shadow only traces the box, so the tail used
     to sit outside the outline and read as a separate piece (iPhone). */
  filter: drop-shadow(0 0 .6px var(--ln-edge)) drop-shadow(0 0 .6px var(--ln-edge));
  cursor: pointer;
}
.ln-venue { --ln-edge: color-mix(in srgb, var(--ink) 42%, transparent); }
.ln-btext { position: relative; z-index: 1; display: block; overflow: hidden; text-overflow: ellipsis; }
/* The tail: one filled triangle whose base sits 6px INSIDE the bubble's
   rounded end. A shallower base left its corners poking out past the curve
   as little steps; this deep, the tail's edges meet the curve in one clean
   corner, and drop-shadow outlines body + tail as a single silhouette. */
.ln-bubble::after { content: ""; position: absolute; pointer-events: none; background: var(--surface); }
/* Crew notes: a column beside the ring, on the character's side; the tail
   points in at the character. */
.ln-bubble.is-crew { left: var(--bx); top: var(--by); max-width: var(--w-pin); z-index: 3; }
.ln-bubble.is-crew.is-l { translate: -100% 0; }
.ln-bubble.is-crew::after { top: 6px; width: 13px; height: 12px; }
.ln-bubble.is-crew.is-r::after { left: -7px; clip-path: polygon(0 50%, 100% 0, 100% 100%); }
.ln-bubble.is-crew.is-l::after { right: -7px; clip-path: polygon(100% 50%, 0 0, 0 100%); }

/* Anonymous notes: stacked over the pin (or over the ring), newest nearest. */
.ln-notes {
  position: absolute; left: 0; bottom: var(--ln-up, 14px); translate: -50% 0; z-index: 4;
  display: flex; flex-direction: column-reverse; align-items: center; gap: 6px;
}
.ln-nrow { display: flex; align-items: center; gap: 4px; max-width: 200px; }
.ln-bubble.is-anon { position: relative; min-width: 0; max-width: var(--w-pin); }
.ln-bubble.is-anon:not(.has-tail)::after { display: none; }
.ln-bubble.is-anon.has-tail::after { left: calc(50% - 6px); bottom: -7px; width: 12px; height: 12px; clip-path: polygon(0 0, 100% 0, 50% 100%); }

/* Foot row under the pin / ring: the Live tag, then the crew "+N". */
.ln-foot {
  position: absolute; left: 0; top: var(--ln-down, 14px); translate: -50% 0; z-index: 2;
  display: flex; align-items: center; gap: 4px; white-space: nowrap;
}
.ln-live, .ln-more {
  display: inline-flex; align-items: center; gap: 5px; height: 22px; margin: 0; padding: 0 9px;
  border: 0; border-radius: var(--r-pill); cursor: pointer;
  font-family: var(--font-body); font-size: var(--fs-xs); font-weight: 700; line-height: 1;
  transition: transform var(--dur-1) var(--ease-out);
}
.ln-live { padding-left: 7px; color: var(--ground); background: var(--ink); }
.ln-live::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--accent); box-shadow: 0 0 0 1.5px var(--ground);
}
.ln-more { color: var(--ink); background: var(--surface); font-variant-numeric: tabular-nums; }
.ln-live:active, .ln-more:active, .ln-bubble.is-anon:active { transform: scale(.97); }

/* 2. Court popup: "Live now" ──────────────────────────────── */
.ln-pop { margin-top: 12px; padding-top: 12px; display: grid; gap: 2px; }
/* In the map popup the section follows the court card, outside its padding:
   it takes the card's insets so its text lines up with the card's. */
.pac-popup .ln-pop { margin: 0; padding: var(--sp-3) var(--sp-5) var(--sp-4); }
.ln-pop-title {
  display: flex; align-items: center; gap: 6px; margin: 0 0 4px;
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2);
}
.ln-pop-title::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--accent);
}
.ln-pop-row { display: flex; align-items: baseline; gap: 8px; min-width: 0; padding: 5px 0; }
.ln-pop-text { flex: 1; min-width: 0; font-size: 13px; color: var(--ink); line-height: 1.35; overflow-wrap: anywhere; }
.ln-pop-text b { font-weight: 700; }
.ln-pop-time { flex: none; font-size: var(--fs-xs); color: var(--ink-2); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* 3. Live card: note composer and end time ───────────────── */
:is(.ln-slot, .ln-ends) {
  --ln-fg: var(--ink); --ln-fg-2: var(--ink-2);
  --ln-line: var(--line-2); --ln-line-hi: var(--ink-3); --ln-fill: var(--surface-2);
}
.live-card :is(.ln-slot, .ln-ends) {
  --ln-fg: var(--white); --ln-fg-2: var(--inv-ink-2);
  --ln-line: var(--inv-line); --ln-line-hi: color-mix(in srgb, var(--white) 30%, var(--ink)); --ln-fill: var(--inv-line);
}
.ln-slot:empty { display: none; }
.ln-compose { display: grid; gap: 8px; min-width: 0; }

/* "Add a note": a chip with a plus, not a second primary. */
.ln-add, .ln-chip {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: var(--tap); padding: 0 14px; margin: 0; min-width: 0; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-body); font-size: 13px; font-weight: 600; line-height: 1.2;
  color: var(--ln-fg); border: 0; border-radius: var(--r-pill);
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out); background: var(--ln-line); }
.ln-add { justify-self: start; }
.ln-add::before { content: "+"; font-size: 17px; font-weight: 500; line-height: 1; margin-top: -1px; }
.ln-add:hover, .ln-chip:hover { background: var(--ln-line-hi); }
.ln-add:active, .ln-chip:active { transform: scale(.97); }

/* Input row: input | Post. The counter sits quietly inside the input's
   right edge (grid overlap, no wrapper needed). */
.ln-field { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px; min-width: 0; }
.ln-input {
  grid-area: 1 / 1; min-width: 0; min-height: var(--tap); padding-right: 52px;
  color: var(--ln-fg); background: var(--ln-line); }
.ln-input::placeholder { color: var(--ln-fg-2); }
.ln-input:hover { background: var(--ln-line-hi); }
.ln-input:focus { box-shadow: 0 0 0 3px var(--ln-line-hi); }
.ln-count {
  grid-area: 1 / 1; justify-self: end; margin-right: 14px; pointer-events: none;
  font-size: var(--fs-xs); font-weight: 600; color: var(--ln-fg-2); font-variant-numeric: tabular-nums;
}
.ln-post { grid-area: 1 / 2; }

.ln-suggest { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }

/* Your current note: one quiet row, text then ✕. */
.ln-mine {
  display: flex; align-items: center; gap: 4px; min-width: 0; min-height: var(--tap);
  padding: 0 0 0 14px; border-radius: var(--r-inner); background: var(--ln-fill);
}
.ln-mine::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; flex: none; margin-right: 4px;
  background: var(--accent);
}
.ln-text { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; color: var(--ln-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ln-clear {
  flex: none; display: inline-grid; place-items: center; width: var(--tap); height: var(--tap); margin: 0; padding: 0;
  font-size: 14px; line-height: 1; color: var(--ln-fg-2); background: none; border: 0; border-radius: 50%; cursor: pointer;
  transition: color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.ln-clear:hover { color: var(--ln-fg); }
.ln-clear:active { transform: scale(.97); }

.ln-hint, .ln-privacy { margin: 0; font-size: var(--fs-sm); line-height: 1.4; color: var(--ln-fg-2); }
.ln-privacy { font-size: var(--fs-xs); }

/* "Ends 9:30 PM" · +30m (organizers). Over time: the same calm line, the
   words in full colour and Extend as the one lime fill. Never a banner. */
.ln-ends {
  display: flex; align-items: center; gap: 8px; min-width: 0; margin: 0;
  font-size: var(--fs-sm); color: var(--ln-fg-2); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.ln-ends > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.ln-extend {
  position: relative; flex: none; display: inline-flex; align-items: center; height: 30px; padding: 0 12px; margin: 0;
  font-family: var(--font-body); font-size: var(--fs-xs); font-weight: 700; color: var(--ln-fg); border: 0; border-radius: var(--r-pill); cursor: pointer;
  transition: border-color var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out); background: var(--ln-line); }
.ln-extend::after { content: ""; position: absolute; inset: -7px -4px; }   /* 44px tap, 30px look */
.ln-extend:hover { background: var(--ln-line-hi); }
.ln-extend:active { transform: scale(.97); }
/* "See on map": a quiet text action at the row's end, not another pill. */
.ln-map {
  position: relative; flex: none; margin: 0 0 0 auto; padding: 0; border: 0; background: none; cursor: pointer;
  font-family: var(--font-body); font-size: var(--fs-sm); font-weight: 600; color: var(--ln-fg);
  text-decoration: underline; text-decoration-color: var(--ln-line-hi); text-underline-offset: 3px;
}
.ln-map::after { content: ""; position: absolute; inset: -12px -6px; }   /* 44px tap */
.ln-map:active { opacity: .7; }
.ln-ends.ln-over { color: var(--ln-fg); font-weight: 600; }
.ln-ends.ln-over .ln-extend { color: var(--accent-ink); background: var(--accent); border-color: transparent; }
/* The Live card is one big link that presses as a whole; pressing or typing
   in the composer must not shrink the card. */
.live-card:has(:is(.ln-slot, .ln-ends) :active) { transform: none; }

/* Hooks for the map wiring: .is-far on a venue (zoomed out) keeps only the
   Live tag so neighbouring venues don't pile up; the "Live now" filter pill
   (#fpLive) carries the same lime dot as the tag. */
.ln-venue.is-far :is(.ln-crew, .ln-bubble, .ln-notes, .ln-more) { display: none; }
.ln-venue.is-far .ln-foot { top: 14px; }
/* Up close the ring and bubbles already say "live": the tag is only for a
   venue showing nothing else, and at city zoom, where it's all there is. */
.ln-venue:not(.is-far):is(.has-crew, .has-notes) .ln-live { display: none; }
/* overflow: visible — .chip-label clips for its ellipsis, which cut the dot
   flat on its left edge; "Live now" never truncates. No ring: on the Ink
   pressed pill a Paper ring read as a stray rim. */
#fpLive .chip-label { display: inline-flex; align-items: center; gap: 7px; overflow: visible; }
#fpLive .chip-label::before {
  content: ""; width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: var(--accent);
}
/* ── live-notes END ── */

/* ── Touch inputs at 16px (GL-01) ──
   iOS Safari zooms any focused field under 16px and leaves the page zoomed.
   Coarse pointers only; the class rules are repeated so they don't win. */
@media (pointer: coarse) {
  input, select, textarea,
  .cp-search input[type="search"],
  .fsort .field select { font-size: 16px; }
}




/* While any sheet is open the page behind it doesn't scroll (app.js toggles it). */
html.is-sheet-open { overflow: hidden; }

/* CLS: the court list fills in after first paint; keep what sits below it
   (add prompt, footer credit) off screen until it does. */
#view-courts:not(.is-map) > .main-col { min-height: 100svh; }
/* Until the first court list is drawn, the lines under it stay invisible, so
   the list arriving can't visibly push them down (app.js sets .courts-ready). */
html:not(.courts-ready) body:has(#view-courts:not([hidden])) :is(#addPrompt, #dirFoot, #pageFoot) { visibility: hidden; }

/* admin review */
.rv-mount { display: flex; flex-direction: column; gap: var(--sp-3); }
.rv-mount:not(.dash-review) > .rv-item:first-of-type { border-top: 0; padding-top: 0; }
.rv-quiet { margin: 0; font-size: 14px; color: var(--ink-2); }

/* delete game */
.game-delete { margin-top: 24px; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }

/* ── connection chip ── one floating line on every screen (app.js netChip).
   Offline is a nap, not an alarm: Dink sleeps, the queue keeps working.
   Back online it says "Syncing…" until this phone's taps are sent, then
   "Synced" with a happy Dink, and leaves. */
.net-chip {
  position: fixed; z-index: 90; left: 50%; top: calc(env(safe-area-inset-top, 0px) + 10px);
  transform: translateX(-50%); display: inline-flex; align-items: center; gap: 6px;
  max-width: calc(100vw - 32px); padding: 6px 14px 6px 8px; border-radius: var(--r-pill);
  font-size: var(--fs-sm); font-weight: 600; white-space: nowrap;
  color: var(--white); background: var(--ink);
  animation: net-in var(--dur-2, .2s) var(--ease-out, ease-out);
}
.net-chip[hidden] { display: none; }
.net-chip dink-mascot { flex: none; margin: -4px 0; }
.net-chip .net-text { overflow: hidden; text-overflow: ellipsis; }
.net-chip[data-state="ok"] { background: var(--accent); color: var(--ink); }
@keyframes net-in { from { opacity: 0; transform: translate(-50%, -8px); } }
@media (prefers-reduced-motion: reduce) { .net-chip { animation: none; } }

/* No past sessions yet: no heading over nothing. */
.split-more:has(> #opHistory:empty) { display: none; }

/* ── map list ── Workspace template (ui-framework.md) for map view, 1024 and up:
   a 420 column (title, search, filters, the courts in the map's current area)
   docked beside the nav, and the map full-bleed in the rest. Phones and
   narrow tablets keep the floating toolbar over a full map. */
.map-list { display: none; }
@media (min-width: 1024px) {
  #view-courts { --ws-nav: calc(var(--rail-w) + var(--safe-l, 0px)); --ws-col: 420px; }
  #view-courts.is-map:not([hidden]) {
    /* --cm-top: 0 beside the rail or sidebar; under a guest's header (app.js sizeMap). */
    position: fixed; z-index: 30; top: var(--cm-top, 0px); bottom: 0; left: var(--ws-nav); width: var(--ws-col);
    display: flex; flex-direction: column; gap: 14px; padding: 28px 20px 0;
    background: var(--ground); overflow: hidden;
  }
  #view-courts.is-map .page-head { margin: 0 4px; }
  #view-courts.is-map > :not(.map-list) { flex: none; }
  #view-courts.is-map .page-meta { display: none; }
  #view-courts.is-map .map-wrap:not([hidden]) { top: var(--cm-top, 0px); bottom: 0; left: calc(var(--ws-nav) + var(--ws-col)); right: 0; }
  /* The toolbar sits in the column: no float, no shadows. */
  #view-courts.is-map:not([hidden]) .ctool { position: static; padding: 0 4px; pointer-events: auto; }
  #view-courts.is-map .ctool :is(.near-me, .view-seg, .filters-open, .fsort select, .share-act, .ctool-clear) { box-shadow: none; }
  #view-courts.is-map .ctool-main { flex-wrap: wrap; }
  #view-courts.is-map .ctool-main .search { flex: 1 0 100%; }
  #view-courts.is-map .ctool-filters { flex-wrap: wrap; row-gap: 8px; }
  #view-courts.is-map .ctool-end { display: none; }
  #view-courts.is-map .courts-results { display: contents; }
  #view-courts.is-map .map-list {
    display: flex; flex-direction: column; flex: 1; min-height: 0; margin: 0 -20px;
  }
  .map-list-head { margin: 0; padding: 12px 24px 4px; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-3); }
  .map-list-rows { list-style: none; margin: 0; padding: 4px 12px 24px; overflow-y: auto; overscroll-behavior: contain; flex: 1; }
  .map-list-rows > li > .list-row { width: 100%; margin-inline: 0; }
  .ml-more { padding: 10px 12px; font-size: var(--fs-sm); color: var(--ink-3); }
}
@media (min-width: 1280px) {
  #view-courts { --ws-nav: var(--sidebar-w); }
}


/* ── Crew AI (assets/crew-chat.js, design: Crew AI Chat.dc.html) ──
   One chat for the whole crew, opened from the orb (.crew-orb-btn). A
   full-screen sheet over the tab bar on phones; a panel down the right
   side on tablets and computers. Replies are plain text under the crew
   member's face, name and ground, in their colour (--cc). */
.cc-chat {
  position: fixed; z-index: calc(var(--z-sheet) + 1); display: flex; flex-direction: column;
  inset: 0; background: var(--ground); color: var(--ink);
  padding: var(--safe-t) var(--safe-r) 0 var(--safe-l);
}
.cc-chat[hidden] { display: none; }
.cc-chat:not([hidden]) { animation: cc-in var(--dur-2) var(--ease-out); }
@keyframes cc-in { from { opacity: 0; transform: translateY(16px); } }
/* A route change's snapshots are drawn over the whole page, the open panel
   too: its own name keeps it in its own layer, above the page, standing still. */
.cc-chat:not([hidden]) { view-transition-name: crew-chat; }
::view-transition-group(crew-chat), ::view-transition-new(crew-chat) { animation: none; }
::view-transition-old(crew-chat) { display: none; }
@media (min-width: 768px) {
  .cc-chat { inset: 0 0 0 auto; width: min(420px, 100vw); padding: 0; }
  @keyframes cc-in { from { opacity: 0; transform: translateX(24px); } }
}
.cc-head { flex: none; display: flex; align-items: center; gap: 8px; height: 56px; padding: 0 6px; }
.cc-icon {
  flex: none; display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border: 0; border-radius: 50%;
  background: none; color: var(--ink); cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.cc-icon:hover { background: var(--surface-2); }
.cc-icon svg { width: 22px; height: 22px; }
.cc-head-orb { flex: none; display: inline-flex; }
.cc-head-id { flex: 1 1 auto; min-width: 0; }
.cc-head-title { margin: 0; font-size: 16px; font-weight: 700; line-height: 1.2; }
.cc-status { margin: 0; display: flex; align-items: center; gap: 6px; font-size: 12.5px; line-height: 1.3; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cc-status-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--erne); transition: background-color .6s; }

.cc-scroll { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain; padding: 16px 16px 8px; }
.cc-empty { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 24px 0 8px; }
.cc-empty[hidden] { display: none; }
.cc-empty-title { margin: 18px 0 0; font-size: 22px; font-weight: 700; letter-spacing: -.01em; }
.cc-empty-sub { margin: 6px 0 22px; font-size: 15px; color: var(--ink-3); }
/* Your home crew member's hello (pref homeCrew), between the sub and the starters. */
.cc-empty-hello { display: flex; align-items: baseline; gap: 8px; max-width: 34ch; margin: -10px 0 20px; font-size: 14.5px; text-align: left; color: var(--ink-2); }
.cc-empty-hello .cc-dot { transform: translateY(1px); }
.cc-starters { display: flex; flex-direction: column; gap: 8px; width: 100%; max-width: 420px; }
.cc-starter {
  display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 0 16px; border: 0; border-radius: 14px;
  background: var(--surface-2); color: var(--ink); font: inherit; font-size: 15px; text-align: left; cursor: pointer;
  -webkit-tap-highlight-color: transparent; transition: transform var(--dur-1) var(--ease-out);
}
.cc-starter:active { transform: scale(.98); }
.cc-dot { flex: none; width: 10px; height: 10px; border-radius: 50%; background: var(--cc); }
.cc-starter-q { flex: 1 1 auto; min-width: 0; }
.cc-starter-who { flex: none; font-size: 13px; color: var(--ink-3); }

.cc-thread { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.cc-me {
  align-self: flex-end; max-width: 80%; padding: 10px 14px; border-radius: var(--r-tile) var(--r-tile) 6px var(--r-tile);
  background: var(--ink); color: var(--ground); font-size: 15px; line-height: 1.4; overflow-wrap: anywhere;
}
.cc-crew { align-self: flex-start; max-width: 92%; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.cc-who { display: flex; align-items: center; gap: 8px; min-height: 28px; margin: 0; font-size: 13px; color: var(--ink-3); }
.cc-who b { font-weight: 700; color: var(--ink); }
.cc-av { flex: none; display: inline-flex; width: 28px; height: 28px; border-radius: 50%; overflow: hidden; background: var(--cc); }
.cc-av svg { display: block; width: 100%; height: 100%; }
/* Plain greedy wrapping, not pretty: the answer types in a word at a time,
   and pretty re-balances the last lines on every word, so words hopped
   between lines. Greedy never moves a line already shown. */
.cc-line { margin: 0; font-size: 15px; line-height: 1.45; color: var(--ink); text-wrap: wrap; overflow-wrap: anywhere; }
.cc-help { color: var(--ink-2); font-size: 14px; }
.help-ask { margin: 0; }
.cc-src { margin: 0; font-size: 12.5px; color: var(--ink-3); }
.cc-acts, .cc-set { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.cc-pill {
  display: inline-flex; align-items: center; min-height: 36px; padding: 0 14px; border: 0; border-radius: var(--r-pill);
  background: var(--surface-2); color: var(--ink); font: inherit; font-size: 14px; font-weight: 600; text-decoration: none; cursor: pointer;
}
.cc-pill--lime { background: var(--accent); color: var(--accent-ink); }
.cc-court .cc-pill { background: var(--ground); }
.cc-wait { display: flex; align-items: center; gap: 10px; font-size: 14px; font-weight: 500; color: var(--ink-3); }
.cc-wait-t.is-new { animation: cc-say var(--dur-2) var(--ease-out); }
@keyframes cc-say { from { opacity: 0; transform: translateY(3px); } }
.cc-src a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
/* Volley's top court, inline: name, facts, the booking pill and the rest. */
.cc-court { display: flex; flex-direction: column; gap: 6px; width: 100%; min-width: 260px; padding: 12px 14px; border-radius: var(--r-tile); background: var(--surface-2); }
.cc-court-name { padding: 0; border: 0; background: none; color: var(--ink); font: inherit; font-size: 15px; font-weight: 700; text-align: left; cursor: pointer; }
.cc-court-meta { margin: 0; font-size: 13px; color: var(--ink-3); }

.cc-form { flex: none; padding: 8px 12px calc(12px + var(--safe-b)); transition: opacity var(--dur-1) var(--ease-out); }
.cc-form.is-busy { opacity: .6; }
.cc-field { display: flex; align-items: center; gap: 8px; height: 48px; padding: 0 5px 0 18px; border-radius: var(--r-pill); background: var(--surface-2); }
.cc-field:focus-within { box-shadow: var(--ring-field); }
.cc-field input {
  flex: 1 1 auto; min-width: 0; height: 100%; padding: 0; border: 0; border-radius: 0; background: none;
  color: var(--ink); font: inherit; font-size: 16px; outline: none;
}
.cc-field input:is(:hover, :focus, :focus-visible) { background: none; box-shadow: none; outline: none; }
.cc-field input::placeholder { color: var(--ink-3); }
.cc-send {
  flex: none; display: grid; place-items: center; width: 38px; height: 38px; padding: 0; border: 0; border-radius: 50%;
  background: var(--ink); color: var(--ground); cursor: pointer;
}
.cc-send:disabled { background: var(--line); color: var(--ink-3); cursor: default; }
.cc-send svg { width: 16px; height: 16px; }

/* <crew-orb> (assets/crew-orb.js): glass ball, six crew colours, a flat
   face. Every size is a fraction of --s. Layers, bottom to top: white,
   a turning conic of the crew colours, three counter-turning blobs, the
   answering crew's tint, gloss, rim, then the face. */
crew-orb { --s: 52px; display: inline-block; flex: none; width: var(--s); height: var(--s); line-height: 0; vertical-align: middle; }
.co-ball {
  position: relative; display: block; width: 100%; height: 100%; border-radius: 50%;
  overflow: hidden; isolation: isolate; background: var(--white);
  animation: co-breathe 4.2s ease-in-out infinite;
}
crew-orb[glow] .co-ball { box-shadow: 0 8px 17px -5px var(--glow); transition: box-shadow .6s; }
.co-conic, .co-blobs, .co-tint, .co-gloss, .co-rim { position: absolute; border-radius: 50%; pointer-events: none; }
.co-conic {
  inset: -25%; filter: blur(calc(var(--s) * .13));
  background: conic-gradient(var(--dink), var(--pop), var(--volley), var(--erne), var(--lob), var(--smash), var(--dink));
  animation: co-spin 10s linear infinite;
}
.co-blobs {
  inset: -10%; filter: blur(calc(var(--s) * .09)); opacity: .85;
  background:
    radial-gradient(circle at 26% 74%, var(--volley) 0 22%, transparent 46%),
    radial-gradient(circle at 76% 24%, var(--lob) 0 20%, transparent 44%),
    radial-gradient(circle at 78% 78%, var(--dink) 0 18%, transparent 42%);
  animation: co-spin 14s linear infinite reverse;
}
.co-tint { inset: 0; background-color: var(--tint); opacity: 0; transition: opacity .6s, background-color .6s; }
crew-orb[data-state="answering"] .co-tint { opacity: .8; }
/* At rest with a home crew member (pref homeCrew): a wash of their colour. */
crew-orb[data-home] .co-tint { opacity: .4; }
.co-gloss { inset: 0; background: radial-gradient(circle at 34% 24%, color-mix(in srgb, var(--white) 90%, transparent), transparent 44%); }
.co-rim { inset: 0; box-shadow: inset 0 calc(var(--s) * -.06) calc(var(--s) * .12) color-mix(in srgb, var(--ink) 12%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--ink) 6%, transparent); }
/* morph: the crew member's round face grows out of the swirl, with a spin. */
.co-av {
  position: absolute; inset: 0; z-index: 2; display: block; border-radius: 50%; overflow: hidden; background: var(--tint);
  transform: scale(.25) rotate(-120deg); opacity: 0;
  transition: transform .45s cubic-bezier(.3, 1.45, .5, 1), opacity .25s; /* MORPH in crew-chat.js */
}
.co-av svg { display: block; width: 100%; height: 100%; }
crew-orb[data-morph] .co-av { transform: none; opacity: 1; }
.co-face { transition: opacity .25s, transform .4s; }
crew-orb[data-morph] .co-face { opacity: 0; transform: scale(.6); }
crew-orb[data-state="thinking"] .co-conic { animation-duration: 2.2s; }
crew-orb[data-state="thinking"] .co-blobs { animation-duration: 3.4s; }

.co-face { position: absolute; inset: 0; z-index: 1; }
.co-eyes { position: absolute; left: 50%; top: 36%; display: flex; gap: calc(var(--s) * .1); transform: translate(-50%, -50%); }
.co-eye {
  position: relative; display: block; width: calc(var(--s) * .22); height: calc(var(--s) * .22); border-radius: 50%; background: var(--white);
  animation: co-blink 4.8s infinite;
}
.co-eye i {
  position: absolute; left: 50%; top: 50%; width: calc(var(--s) * .13); height: calc(var(--s) * .13); margin: calc(var(--s) * -.065) 0 0 calc(var(--s) * -.065);
  border-radius: 50%; background: var(--ink); animation: co-wander 7.5s ease-in-out infinite;
}
crew-orb[data-state="thinking"] .co-eye i { animation: co-hmm-eyes 2.4s ease-in-out infinite; }
crew-orb[data-state="answering"] .co-eye i { animation: none; transform: translate(0, 6%); }
.co-mouth { position: absolute; left: 50%; top: 63%; width: calc(var(--s) * .3); height: calc(var(--s) * .16); transform: translate(-50%, -50%); overflow: visible; }
.co-mouth path { stroke: var(--ink); }
.co-smile { transform-box: fill-box; transform-origin: center; animation: co-grin 6s ease-in-out infinite; }
.co-hmm, .co-talk { display: none; }
.co-hmm { transform-box: fill-box; transform-origin: center; animation: co-hmm 2.4s ease-in-out infinite; }
.co-d { fill: var(--ink); stroke: none !important; }
.co-tongue { fill: #ef5f7a; }
.co-talk { transform-box: fill-box; transform-origin: 50% 0; animation: co-talk 1.6s ease-in-out infinite; }
crew-orb[data-state="thinking"] .co-smile, crew-orb[data-state="answering"] .co-smile { display: none; }
crew-orb[data-state="thinking"] .co-hmm { display: inline; }
crew-orb[data-state="answering"] .co-talk { display: inline; }
@keyframes co-spin { to { transform: rotate(360deg); } }
@keyframes co-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.035); } }
@keyframes co-blink { 0%, 94%, 100% { transform: scaleY(1); } 96% { transform: scaleY(.12); } }
@keyframes co-wander {
  0%, 100% { transform: translate(0, 0); } 18% { transform: translate(22%, -10%); } 36% { transform: translate(-22%, -6%); }
  54% { transform: translate(-14%, 18%); } 72% { transform: translate(16%, 14%); }
}
@keyframes co-hmm-eyes { 0%, 100% { transform: translate(-22%, -28%); } 50% { transform: translate(22%, -30%); } }
@keyframes co-grin { 0%, 84%, 100% { transform: scale(1); } 90%, 95% { transform: scale(1.18, 1.45); } }
@keyframes co-hmm { 0%, 100% { transform: translateX(-6%) rotate(-6deg); } 50% { transform: translateX(6%) rotate(8deg); } }
@keyframes co-talk { 0%, 100% { transform: scaleY(1); } 25% { transform: scaleY(.55); } 50% { transform: scaleY(.9); } 75% { transform: scaleY(.6); } }
@media (prefers-reduced-motion: reduce) {
  .co-av, .co-face { transition: none; }
  .co-ball, .co-conic, .co-blobs, .co-eye i, crew-orb[data-state="thinking"] .co-eye i, .co-hmm, .co-talk, .co-smile { animation: none; }
  crew-orb[data-state="thinking"] .co-eye i { transform: translate(0, -28%); }
}

/* ── Crew 8 ball (assets/crew-8ball.js, design: Crew 8 Ball.dc.html) ──
   Three taps on the empty chat's orb. A layer over the sheet (below the
   status bar): the crew gradient floods out of the orb (clip-path), the
   orb's own layers grow into a black ball, an 8 rolls away to the window,
   and the answer floats up on a triangle in the crew member's colour
   (--tri). Phases: data-phase and is-open / is-win / is-back. */
#crewEmpty crew-orb { touch-action: manipulation; -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; }
.c8 {
  position: absolute; z-index: 5; inset: var(--safe-t) 0 0; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 28px;
}
.c8:focus { outline: none; }
.c8-flood {
  position: absolute; inset: 0; overflow: hidden; background: var(--white);
  clip-path: circle(0 at var(--cx) var(--cy)); transition: clip-path .9s cubic-bezier(.6, 0, .2, 1);
}
.c8.is-open .c8-flood { clip-path: circle(var(--r) at var(--cx) var(--cy)); }
.c8.is-closing .c8-flood { transition-duration: .7s; }
.c8-drift { position: absolute; inset: -30%; animation: c8-drift 9s ease-in-out infinite; }
.c8-conic, .c8-blobs, .c8-veil { position: absolute; inset: 0; }
.c8-conic {
  background: conic-gradient(var(--dink), var(--pop), var(--volley), var(--erne), var(--lob), var(--smash), var(--dink));
  filter: blur(70px); animation: co-spin 16s linear infinite;
}
.c8-blobs {
  inset: 10%; opacity: .8; filter: blur(50px); animation: co-spin 22s linear infinite reverse;
  background:
    radial-gradient(circle at 28% 72%, var(--volley) 0, transparent 46%),
    radial-gradient(circle at 76% 28%, var(--lob) 0, transparent 42%),
    radial-gradient(circle at 70% 80%, var(--dink) 0, transparent 36%);
}
.c8-veil { background: color-mix(in srgb, var(--white) 28%, transparent); }

.c8-ball {
  position: relative; z-index: 1; flex: none; width: 280px; height: 280px; padding: 0; border: 0; border-radius: 50%;
  background: none; cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.c8-ball:focus { outline: none; }
.c8-ball:focus-visible { outline: 3px solid var(--ink); outline-offset: 8px; }
.c8-move, .c8-float, .c8-orb { position: absolute; inset: 0; display: block; }
.c8-move { transition: transform .9s cubic-bezier(.34, 1.3, .5, 1); }
.c8.is-closing .c8-move { transition: transform .7s cubic-bezier(.6, 0, .2, 1); }
.c8:is([data-phase="ready"], [data-phase="answer"]) .c8-float { animation: c8-float 3.6s ease-in-out infinite; }
.c8[data-phase="rattle"] .c8-float { animation: c8-rattle .8s ease-in-out; }
.c8-orb {
  --s: 280px; border-radius: 50%; overflow: hidden; isolation: isolate; background: var(--white);
  box-shadow: 0 19px 41px -12px var(--glow); transition: box-shadow .9s;
}
.c8.is-open .c8-orb { box-shadow: 0 50px 70px -28px rgba(10, 10, 30, .6); }
.c8-orb .co-face { transition: opacity .3s, transform .5s; }
.c8.is-open .c8-orb .co-face { opacity: 0; transform: scale(.6); }
.c8-orb .co-gloss { transition: opacity .8s; }
.c8.is-open .c8-orb .co-gloss { opacity: .4; }
.c8-black {
  position: absolute; inset: 0; border-radius: 50%; opacity: 0; transition: opacity .7s ease;
  background: radial-gradient(circle at 36% 30%, #555 0, var(--ink) 38%, #050505 72%);
}
.c8.is-open .c8-black { opacity: 1; }
.c8-eight {
  position: absolute; left: 30%; top: 30%; width: 40%; height: 40%; display: grid; place-items: center; border-radius: 50%;
  background: var(--cream); box-shadow: inset 0 -6px 12px color-mix(in srgb, var(--ink) 18%, transparent);
  color: var(--ink); font: 800 75.6px/1 var(--font-body); /* 27% of the ball */
  opacity: 0; transform: scale(.3); transition: opacity .45s, transform .8s cubic-bezier(.34, 1.3, .5, 1);
}
.c8.is-open .c8-eight { opacity: 1; transform: none; }
.c8.is-win .c8-eight { opacity: 0; transform: translateY(-160%) scale(.6); }
.c8-win {
  position: absolute; left: 20%; top: 20%; width: 60%; height: 60%; border-radius: 50%; overflow: hidden;
  background: radial-gradient(circle at 50% 40%, #141b45 0, #070a1c 70%);
  box-shadow: inset 0 12px 24px color-mix(in srgb, var(--ink) 85%, transparent), 0 0 0 4px #2a2a2a;
  opacity: 0; transform: translateY(160%); transition: opacity .4s, transform .8s cubic-bezier(.34, 1.3, .5, 1);
}
.c8.is-win .c8-win { opacity: 1; transform: none; }
.c8-swirl {
  position: absolute; inset: -20%; opacity: 0; transition: opacity .3s;
  background: conic-gradient(transparent, rgba(110, 195, 255, .25), transparent 40%, rgba(179, 156, 245, .25), transparent 75%);
  animation: co-spin .7s linear infinite;
}
.c8[data-phase="rattle"] .c8-swirl { opacity: 1; }
.c8-prompt {
  position: absolute; inset: 0; display: grid; place-items: center; padding: 0 18%; text-align: center;
  font: 700 15px/1.25 var(--font-body); color: #e9ecff;
  opacity: 0; filter: blur(5px); transition: opacity .25s, filter .25s;
}
.c8[data-phase="ready"] .c8-prompt { opacity: 1; filter: none; transition-duration: .5s; }
.c8-tri {
  position: absolute; inset: 6%; display: flex; align-items: flex-end; justify-content: center; padding-bottom: 18%; box-sizing: border-box;
  clip-path: polygon(50% 6%, 97% 88%, 3% 88%); background: var(--tri, var(--erne));
  opacity: 0; filter: blur(6px); transform: rotate(-14deg) scale(.8); transition: opacity .25s, filter .25s, transform .25s;
}
.c8[data-phase="answer"] .c8-tri {
  opacity: 1; filter: none; transform: none;
  transition: opacity 1s .15s, filter 1.1s .15s, transform 1.4s .15s cubic-bezier(.2, .8, .3, 1);
}
.c8-tri span {
  width: 54%; text-align: center; color: var(--ink);
  font: 800 12px/1.12 var(--font-body); letter-spacing: .02em; text-transform: uppercase;
}
/* Always in the layout (so the ball's spot never moves), shown from the window on. */
.c8-back {
  position: relative; z-index: 1; min-height: 44px; padding: 0 16px; border: 0; background: none; cursor: pointer;
  color: var(--ink); font: 700 17px var(--font-body);
  text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 4px; -webkit-tap-highlight-color: transparent;
  opacity: 0; visibility: hidden; transform: translateY(8px);
  transition: opacity .4s ease-out, transform .4s ease-out, visibility 0s .4s;
}
.c8.is-back .c8-back { opacity: 1; visibility: visible; transform: none; transition: opacity .4s ease-out, transform .4s ease-out; }
@keyframes c8-drift { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(-6%, 4%) scale(1.12); } }
@keyframes c8-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
@keyframes c8-rattle {
  0%, 100% { transform: translate(0, 0) rotate(0); }
  12% { transform: translate(-16px, 5px) rotate(-9deg); }
  26% { transform: translate(14px, -7px) rotate(8deg); }
  40% { transform: translate(-11px, 6px) rotate(-6deg); }
  54% { transform: translate(9px, -3px) rotate(5deg); }
  68% { transform: translate(-6px, 2px) rotate(-3deg); }
  84% { transform: translate(3px, -1px) rotate(1deg); }
}
/* Reduced motion: the layer crossfades in with the window already up; no
   flood, drift, spin, float or rattle, and answers swap with a fade. */
@media (prefers-reduced-motion: reduce) {
  .c8 { opacity: 0; transition: opacity .3s; }
  .c8.is-open { opacity: 1; }
  .c8-flood, .c8.is-open .c8-flood { clip-path: none; transition: none; }
  .c8-drift, .c8-conic, .c8-blobs, .c8-swirl, .c8 .c8-float { animation: none !important; }
  .c8-move, .c8-orb, .c8-black, .c8-eight, .c8-win, .c8-orb .co-face, .c8-orb .co-gloss { transition: none !important; }
  .c8-prompt, .c8[data-phase="ready"] .c8-prompt, .c8-tri, .c8[data-phase="answer"] .c8-tri { filter: none; transform: none; transition: opacity .3s; }
}


/* The orb button: the lifted middle of the phone tab bar (below), a
   "Crew" row with a 28px orb on the tablet rail and desktop sidebar. */
.crew-orb-btn {
  display: flex; align-items: center; gap: 12px; min-height: 42px; padding: 0 12px; border: 0; border-radius: var(--r-field);
  background: none; color: var(--ink-2); font: inherit; font-size: 15px; font-weight: 500; text-align: left; cursor: pointer;
  -webkit-tap-highlight-color: transparent; transition: background-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-spring);
}
.crew-orb-btn:hover, .crew-orb-btn[aria-expanded="true"] { background: color-mix(in srgb, var(--panel) 60%, transparent); color: var(--ink); }
.crew-orb-btn:active { transform: scale(.96); }
.crew-orb-art { flex: none; display: inline-flex; }
.crew-orb-btn crew-orb { --s: 28px !important; }
@media (min-width: 768px) and (max-width: 1279.98px) {
  .crew-orb-btn { flex-direction: column; justify-content: center; gap: 4px; width: 64px; min-height: 58px; padding: 6px 2px; border-radius: var(--r-inner); font-size: 11px; }
}
@media (max-width: 767px) {
  /* The bar lets the orb rise out of it; folded, it clips again. */
  .tabs { overflow: visible; }
  .tabs.is-min { overflow: hidden; }
  .tabs .crew-orb-btn { position: relative; z-index: 2; justify-content: center; min-height: 54px; padding: 0; border-radius: var(--r-pill); background: none; }
  .tabs .crew-orb-btn:hover, .tabs .crew-orb-btn[aria-expanded="true"] { background: none; }
  /* 52px, its top 8px above the bar's top edge (the bar's 4px padding included). */
  .tabs .crew-orb-art { position: absolute; left: 50%; top: -12px; transform: translateX(-50%); }
  .tabs .crew-orb-btn crew-orb { --s: 52px !important; }
  .tabs .orb-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .tabs.is-min .crew-orb-btn { opacity: 0; pointer-events: none; }
  /* Signed out there is no Sidequests tab: Courtside takes the right half, so the orb stays centred. */
  .tabs:has(#tab-quests[hidden]):not(.is-min) { grid-template-columns: 1fr 1fr 1fr 2fr; }
  /* The open chat covers the bar and the page: the page behind it stays put. */
  html.is-crew-open .tabs { visibility: hidden; }
  html.is-crew-open, html.is-crew-open body { overflow: hidden; overscroll-behavior: none; }
}

/* The dock (768 and up, crew-chat.js mountDock): the crew's ask bar,
   floating at the foot of the page sheet, centred on it. Send opens the
   panel; it hides while the panel is open, so the orb row in the nav
   isn't needed there. */
.cc-dock { display: none; }
/* Crew AI is for signed-in players: no orb or ask bar until there's an account (html.has-acct). */
html:not(.has-acct) :is(.cc-dock, .crew-orb-btn, .tabs .crew-orb-btn) { display: none; }
@media (min-width: 768px) {
  body:has(> .masthead) { --dock-nav: calc(var(--rail-w) + var(--safe-l)); padding-bottom: 76px; }
  html:not(.has-acct) body:has(> .masthead) { padding-bottom: 0; }
  .tabs .crew-orb-btn { display: none; }
  .cc-dock {
    /* Over the Courts map view (z 30), under the sheets. */
    display: block; position: fixed; z-index: calc(var(--z-tabbar) + 1); bottom: calc(18px + var(--safe-b));
    /* % of the viewport without its scrollbar; the sheet has a 10px gutter on the right. */
    left: calc(var(--dock-nav, 0px) + (100% - var(--dock-nav, 0px) - 10px) / 2);
    width: min(560px, 100% - var(--dock-nav, 0px) - 58px); transform: translateX(-50%);
  }
  /* A hairline so it holds its edge over the cards in dark mode. */
  .cc-dock .cc-field { padding-left: 10px; background: var(--surface); }
  .cc-dock .cc-field:focus-within { box-shadow: var(--ring-field); }
  .cc-dock-orb { flex: none; display: inline-flex; cursor: pointer; }
  .cc-dock crew-orb { --s: 28px !important; }
  html.is-crew-open .cc-dock { display: none; }
}
@media (min-width: 1280px) {
  body:has(> .masthead) { --dock-nav: var(--sidebar-w); }
}
/* Map view from 1024: centred on the map, clear of the 420 list column. */
@media (min-width: 1024px) {
  body:has(> .masthead):has(#view-courts.is-map:not([hidden])) { --dock-nav: calc(var(--rail-w) + var(--safe-l) + 420px - 10px); }
}
@media (min-width: 1280px) {
  body:has(> .masthead):has(#view-courts.is-map:not([hidden])) { --dock-nav: calc(var(--sidebar-w) + 420px - 10px); }
}

/* After an AI-written crew line: sits in the text, on its x-height. */
.ai-mark { flex: none; }
.crew-say-line .ai-mark { display: inline-block; vertical-align: -3px; margin-left: 4px; }
/* Dink's "Your week": the Clubhouse page voice; empty (no line) takes no room. */
.club-dink:empty { display: none; }
.club-dink { margin-bottom: var(--sp-4); }
/* Erne on Add a court: a card aside under the duplicates list (app.js acRenderErne). */
.ac-erne:empty { display: none; }
.ac-erne { margin: var(--sp-2) 0; }
/* Sidequests, Your scouting (assets/sidequests.js): own submissions + Erne's read. */
.sq-mine-list { list-style: none; margin: 0; padding: 0; }
.sq-mine-row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 4px var(--sp-2); padding: 12px 0; }
.sq-mine-main { display: flex; flex-direction: column; min-width: 0; }
.sq-row-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sq-row-sub { font-size: 13px; font-weight: 500; color: var(--ink-3); }
.sq-erne { grid-column: 1 / -1; display: flex; align-items: flex-start; gap: 8px; margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }
.sq-erne .ai-mark { margin-top: 1px; }
/* Erne's flags in the admin review list: advice, never a decision. */
.rv-erne { display: flex; align-items: flex-start; gap: 8px; margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }
.rv-erne .ai-mark { margin-top: 1px; }
/* ── claims: "Is this you?" and the organizer's list (groups.js todayClaims) ──
   Cards of ui.listRow rows (--wrap), the actions in the value slot. Layout only. */
.claims-host { display: flex; flex-direction: column; gap: var(--stack); }
.claim-acts { display: flex; align-items: center; gap: 6px; }
.claim-row .list-meta { display: flex; flex-direction: column; gap: 2px; }
.claim-row-msg:empty { display: none; }
.claim-row-msg.err { color: var(--loss); }
.claim-guests { display: flex; flex-direction: column; gap: 14px; }
.claim-guests > summary { cursor: pointer; list-style: none; }
.claim-guests > summary::-webkit-details-marker { display: none; }
.claim-guests-chev { display: inline-block; vertical-align: middle; margin-inline-start: 8px; transition: transform var(--dur-2) var(--ease-out); }
.claim-guests[open] .claim-guests-chev { transform: rotate(180deg); }
.claim-guests:not([open]) { gap: 0; }
.claim-lede { margin: 0; color: var(--ink-2); text-align: center; }

/* ── open play here: public sessions (Set 10, court-meta.js openPlayHTML,
   assets/public-sessions.js) ── the court sheet, the static court pages and
   #/drop-in. Sessions run outside DinkedIn, never mixed with a group's Games.
   Layout only: buttons, tags and the rate rows are the shared components. */
.ps-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.ps-item { display: flex; flex-direction: column; gap: 8px; padding: 12px 0; }
.ps-list .ps-item:first-child { border-top: 0; padding-top: 4px; }
.ps-head { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ps-court, .ps-court-link { font-weight: 700; color: var(--ink); }
.ps-court-link { all: unset; cursor: pointer; font-weight: 700; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.ps-court-link:focus-visible { outline: 2px solid var(--hi-strong); outline-offset: 2px; border-radius: 4px; }
.ps-when { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.ps-title { font-size: 13px; color: var(--ink-2); overflow-wrap: anywhere; }
.ps-meta { font-size: 13px; color: var(--ink-2); }
.ps-first { display: flex; flex-direction: column; gap: 2px; }
.ps-first-h { font-size: var(--fs-xs); font-weight: 600; color: var(--ink-3); }
.rate-amt.is-unconfirmed { color: var(--ink-3); font-weight: 500; font-style: italic; }
.ps-note { margin: 0; font-size: var(--fs-xs); color: var(--ink-3); text-align: center; text-wrap: balance; }
.ps-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ps-times { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; }
.ps-times .tp-inline { grid-column: 1 / -1; }
.ps-check-row { display: flex; align-items: center; gap: 8px; font-size: var(--fs-sm); color: var(--ink); min-height: var(--tap); }
.ps-page { display: flex; flex-direction: column; gap: var(--sp-2); }
.ps-day { padding-top: var(--sp-2); }
.ps-mine-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 6px var(--sp-2); padding: 12px 0; }
.ps-mine-row > .ps-acts, .ps-mine-check { grid-column: 1 / -1; }
.ps-mine-check { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; }
#dropInPrompt { margin-top: 4px; }

/* ── Install DinkedIn (assets/install.js) ──
   Desktop: #installLine, a nav row at the foot of the sidebar, above the
   tool row. Phone and tablet: #installBtn in the tool row, and on phones a
   floating card above the tab bar until it is dismissed. */
.install-line { display: none; font-family: inherit; }
@media (min-width: 1280px) {
  .install-line:not([hidden]) { display: flex; margin-top: auto; }
  .install-line:not([hidden]) ~ .shell-tools { margin-top: 0; }
  #installBtn { display: none; }
}
.install-nudge {
  position: fixed; z-index: calc(var(--z-tabbar) + 1);
  left: calc(var(--gutter) + var(--safe-l)); right: calc(var(--gutter) + var(--safe-r));
  bottom: calc(var(--tabbar-h) + var(--safe-b) + 12px);
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; gap: 12px 14px;
  padding: 16px; border-radius: var(--r-card);
  background: var(--surface); box-shadow: var(--sh-float);
}
.install-nudge[hidden] { display: none; }
.install-nudge-go { grid-column: 1 / -1; }
.install-card-ico { width: 40px; height: 40px; border-radius: var(--r-field); }
.install-card-text { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.install-card-title { margin: 0; font-weight: 700; font-size: 15px; color: var(--ink); }
.install-card-sub { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.4; }
@media (min-width: 768px) { .install-nudge { display: none; } }
.install-steps { margin: 0; padding-left: 1.3em; display: flex; flex-direction: column; gap: 6px; }

/* Home for guests (groups.js guestHomeHtml): what an account adds. The hero
   is the Courtside feature card's shape: text left, Dink right on a hatch. */
.lp { display: flex; flex-direction: column; gap: var(--sp-4); }
.lp-hero { display: grid; grid-template-columns: minmax(0, 1fr); padding: 0; overflow: hidden; gap: 0; }
.lp-hero-text { display: flex; flex-direction: column; gap: 6px; padding: 24px; }
.lp-hero-go { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.lp-hero-art { display: grid; place-items: center; min-height: 160px; padding: 16px; line-height: 0;
  background: var(--inv-line); }
@media (min-width: 768px) { .lp-hero { grid-template-columns: minmax(0, 1fr) 260px; } }
@media (max-width: 767.98px) {
  .lp-hero-art { order: -1; min-height: 0; height: 140px; padding: 10px; }
  .lp-hero-text { padding: 20px 20px 22px; }
  .lp-hero-go > * { flex: 1 1 140px; }
}
.lp-perks { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
@media (min-width: 600px) { .lp-perks { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .lp-perks { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
/* Each perk: its sticker on top, like a Courtside game card (.cs-game-art). */
.lp-perk { padding: 0; gap: 0; overflow: hidden; }
/* A fixed height: the band's 100% doesn't resolve, and a wide card let the
   sticker grow to its width (the same fix as .cs-feature-art on a phone). */
.lp-perk .cs-game-art .cs-stk { width: auto; max-width: 100%; height: 90px; }
.lp-perk-text { display: flex; flex-direction: column; gap: 4px; padding: 14px 16px 16px; min-width: 0; }

/* ══ §13 Homepage ═══════════════════════════════════════════
   index.html, all of it (#front, assets/landing.js; design: Crew Homepage
   v5); the app is app.html. The motion is assets/hp-motion.js (Calm: one
   curve, --hp-calm); this file only holds what it rests on. html.is-front sits on index.html's <html>: it
   shows .hp and gives the page its ground. Compact < 768, Medium 768 to 1279, Wide 1280+.
   The court, the map and the dark cards are drawings: their colours are
   local (--hp-*), the map's from assets/map-style.js. */
.hp { display: none; }
html.is-front .hp { display: block; }
html.is-front body > :not(.hp):not(dialog) { display: none !important; }
html.is-front body { padding: 0; background: var(--ground); }
.hp {
  --hp-pad: 16px;
  --hp-dark: var(--ink); --hp-dark-ink: var(--white); --hp-dark-sub: var(--inv-ink-2); --hp-dark-kick: var(--inv-ink-2);
  --hp-stage: var(--ink); --hp-line: var(--inv-line);
  --hp-grass: #2e5e45; --hp-court: #22457a; --hp-chalk: var(--cream);
  --hp-land: #F1F1EE; --hp-park: #E5E9DB; --hp-water: #D8DCDE; --hp-road: var(--white); --hp-casing: #DEDED9; --hp-ring: var(--ink);
  --hp-numeral: var(--line-2);   /* How it works' big step numbers: a step off --surface-2 */
  --hp-calm: cubic-bezier(.16, 1, .3, 1);
  color: var(--ink);
}
@media (min-width: 768px) { .hp { --hp-pad: 32px; } }
@media (min-width: 1280px) { .hp { --hp-pad: 56px; } }
.hp :is(h1, h2, h3, p, ul) { margin: 0; }
/* Until hp-motion.js has its first frames on (index.html <head> sets .hp-mp,
   until DOMContentLoaded or 3s at most), the first screen waits unseen: no
   flash of the end state. */
html.hp-mp :is(.hp-head, .hp-hero) :is([data-m], [data-m-split]) { opacity: 0; }
/* A word mask (data-m-split): each word slides up inside its own box. The
   padding keeps ascenders and descenders in; the margin takes it back. */
.hp-wm { display: inline-block; vertical-align: top; overflow: hidden; padding: .12em .04em .18em; margin: -.12em -.04em -.18em; }
.hp-wi { display: inline-block; }
.hp-head, .hp-main, .hp-foot { max-width: 1440px; margin-inline: auto; padding-inline: calc(var(--hp-pad) + var(--safe-l)) calc(var(--hp-pad) + var(--safe-r)); }

/* Header: the wordmark, three links, Sign in and Join free. A phone keeps Sign in. */
.hp-head { display: flex; align-items: center; gap: 32px; min-height: 72px; padding-top: var(--safe-t); }
.hp-brand { display: block; line-height: 0; }
.hp-brand { flex: none; }
/* The rail and sidebar rules hide or size the masthead's .brand-logo; undo both here. */
.hp-brand .brand-logo { display: block; width: auto; }
.hp-brand .logo-img { width: auto; }
/* The "Dink!" bubble sits above the wordmark: lift the image so the wordmark, not the box, lines up with the nav. */
.hp-brand .brand-logo { transform: translateY(-16%); }
.hp-theme { flex: none; }
.hp-nav { position: relative; display: flex; gap: 24px; }
.hp-nav a { display: inline-flex; align-items: center; min-height: var(--tap); font-size: 15px; font-weight: 600; color: var(--ink-2); text-decoration: none; transition: color .45s var(--hp-calm); }
/* The nav ball (hp-motion.js): a pickleball dot that hops to the link under the pointer. */
.hp-nav-ball {
  position: absolute; left: 0; top: 0; width: 7px; height: 7px; border-radius: 50%; pointer-events: none;
  background: var(--lime); box-shadow: 0 0 0 1.5px var(--ink); opacity: 0; transform-origin: 50% 100%;
}
/* The pickleball drop (hp-motion.js, a fine pointer): the label steps aside
   for a ball. Everything moves by transform, so the button keeps its width. */
.hp .btn.hp-bb {
  position: relative; overflow: hidden;
  transition: background-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out), translate .4s var(--hp-calm);
}
.hp .btn.hp-bb:active { transform: none; }
.hp-lbl { display: inline-flex; align-items: center; gap: inherit; }
.hp-ball {
  position: absolute; right: 14px; top: 50%; width: 12px; height: 12px; margin-top: -6px; border-radius: 50%; opacity: 0; pointer-events: none;
  background:
    radial-gradient(circle at 32% 32%, var(--on-lime) 1.1px, transparent 1.5px),
    radial-gradient(circle at 70% 44%, var(--on-lime) 1.1px, transparent 1.5px),
    radial-gradient(circle at 42% 72%, var(--on-lime) 1.1px, transparent 1.5px),
    var(--lime);
}
.hp-bb--sm .hp-ball { right: 10px; }
/* On a light button (lime, or soft in light theme) the ball gets a ring. */
.hp-bb--ring .hp-ball { box-shadow: 0 0 0 1.5px var(--on-lime); }
.hp-nav a:hover { color: var(--ink); }
.hp-acts { margin-left: auto; display: flex; align-items: center; gap: 10px; }
@media (max-width: 767.98px) {
  .hp-head { min-height: 56px; gap: 12px; }
  .hp-nav { display: none; }
}

/* Guest shell: signed out, Courts is all of the app. The homepage's header
   (#guestHead) stands in for the tab bar, rail and sidebar, which are
   members' (html.has-acct: set in <head> from the kept session, kept by
   groups.js acctPaint). Their sizes go to 0, so every offset built on them
   (body padding, the map's left edge, the dock) collapses with them. */
.guest-head { display: none; }
html:not(.has-acct) .masthead { display: none; }
/* On the page's gutter, so the wordmark lines up with the page title. */
html:not(.has-acct) .guest-head {
  --hp-pad: var(--gutter);
  display: flex; position: sticky; top: 0; z-index: 10; max-width: none; background: var(--ground);
}
:root:not(.has-acct) { --sidebar-w: 0px; --rail-w: 0px; --tabbar-h: 0px; }
.hp-nav a[aria-current="page"] { color: var(--ink); }
.guest-credit { display: none; }
html:not(.has-acct) .guest-credit { display: flex; justify-content: center; }
/* From 768 the header is chrome, like the sidebar: on --canvas, with the
   page on its own sheet under it, inset on every side, not an L of one colour. */
@media (min-width: 768px) {
  html:not(.has-acct) .guest-head { --hp-pad: calc(var(--gutter) + 10px); background: var(--canvas); }
  html:not(.has-acct) body:has(> .masthead) > main.wrap { margin: 0 10px; min-height: calc(100svh - 82px); }
  html:not(.has-acct) .guest-credit { padding-block: var(--sp-3); }
}
/* The map workspace (1024 and up): the list column and the map are that sheet. */
@media (min-width: 1024px) {
  html:not(.has-acct) #view-courts.is-map:not([hidden]) {
    left: 10px; bottom: 10px; border-radius: var(--r-shell) 0 0 var(--r-shell);
  }
  html:not(.has-acct) #view-courts.is-map .map-wrap:not([hidden]) {
    left: calc(10px + var(--ws-col)); right: 10px; bottom: 10px;
    border-radius: 0 var(--r-shell) var(--r-shell) 0; overflow: hidden;
  }
}

/* Hero: the title, then the crew's selfie on the court. */
.hp-hero-text { display: flex; flex-direction: column; align-items: center; gap: 20px; padding-top: 40px; text-align: center; }
.hp-title { font-size: clamp(52px, 7vw, 96px); line-height: .92; font-weight: 800; letter-spacing: -.045em; }
.hp-lede { max-width: 620px; font-size: 20px; line-height: 1.45; color: var(--ink-2); text-wrap: pretty; }
@media (max-width: 767.98px) {
  .hp-hero-text { gap: 12px; padding-top: 12px; }
  .hp-title { line-height: .95; letter-spacing: -.04em; }
  .hp-lede { font-size: 16px; }
}
.hp-stage {
  position: relative; overflow: hidden; margin: 20px auto 0; width: 100%;
  aspect-ratio: 338 / 330; border-radius: var(--r-card); background: var(--hp-grass);
}
@media (min-width: 768px) { .hp-stage { margin-top: 36px; max-width: 1180px; aspect-ratio: 1180 / 600; border-radius: var(--r-lg) var(--r-lg) 0 0; } }
.hp-court { position: absolute; inset: 0; }
.hp-floor {
  position: absolute; left: -3.2%; width: 106.4%; bottom: -4.2%; height: 91%;
  --hp-persp: 420px;   /* hp-motion.js tips the floor in from 64deg on the same perspective */
  transform: perspective(var(--hp-persp)) rotateX(50deg); transform-origin: 50% 100%;
  background: var(--hp-court); box-shadow: inset 0 0 0 4px var(--hp-chalk);
  --hp-w: 2px;
}
.hp-floor i { position: absolute; display: block; background: var(--hp-chalk); }
.hp-net { left: -6%; right: -6%; top: 50%; height: 5px; margin-top: -3px; box-shadow: 0 -10px 0 color-mix(in srgb, var(--ink) 25%, transparent); }
.hp-line--a, .hp-line--b { left: 0; right: 0; height: var(--hp-w); }
.hp-line--a { top: 34%; }
.hp-line--b { top: 66%; }
.hp-line--c, .hp-line--d { left: 50%; width: var(--hp-w); height: 34%; }
.hp-line--c { top: 0; }
.hp-line--d { bottom: 0; }
@media (min-width: 768px) {
  .hp-floor { left: 7.6%; width: 84.8%; bottom: -6.7%; height: 130%; --hp-persp: 1100px; box-shadow: inset 0 0 0 11px var(--hp-chalk); --hp-w: 6px; }
  .hp-net { height: 14px; margin-top: -7px; box-shadow: 0 -27px 0 color-mix(in srgb, var(--ink) 25%, transparent); }
}
.hp-corner { display: none; position: absolute; top: 28px; width: 36px; height: 36px; border-top: 4px solid var(--hp-chalk); }
.hp-corner--l { left: 28px; border-left: 4px solid var(--hp-chalk); border-radius: 12px 0 0 0; }
.hp-corner--r { right: 28px; border-right: 4px solid var(--hp-chalk); border-radius: 0 12px 0 0; }
@media (min-width: 768px) { .hp-corner { display: block; } }
/* The caption: who you're on and their hello. A photo's colours, both themes. */
.hp-cap {
  position: absolute; z-index: 30; top: 16px; left: 16px; right: 16px; pointer-events: none;
  display: flex; flex-direction: column; gap: 2px; padding: 12px 14px; border-radius: var(--r-tile);
  background: var(--white); color: var(--ink); text-align: left;
}
.hp-cap-head { font-size: 12px; font-weight: 800; }
.hp-cap-job { font-weight: 600; color: var(--ink-3); }
.hp-cap-line { font-size: 15px; line-height: 1.35; font-weight: 600; text-wrap: pretty; }
@media (min-width: 768px) {
  .hp-cap { top: 28px; left: 50%; right: auto; width: min(440px, calc(100% - 200px)); transform: translateX(-50%); gap: 4px; padding: 14px 18px; border-radius: var(--r-tile); text-align: center; }
  .hp-cap-head { font-size: 13px; }
  .hp-cap-line { font-size: 17px; }
}
/* A crew member: a button the size of their drawing. Only the drawn shapes
   take the pointer, so a hover lands on who's under it, not a square. */
.hp-mate {
  --rot: var(--mr); --lift: 0px; --sc: 1;
  position: absolute; left: calc(var(--mx) * 1%); bottom: calc(var(--my) * 1%); width: calc(var(--ms) * 1%); aspect-ratio: 1;
  z-index: var(--mz); padding: 0; border: 0; background: none; cursor: pointer; pointer-events: none; border-radius: 50%;
  transform: translateY(var(--lift)) scale(var(--sc)) rotate(var(--rot));
  transition: transform .35s cubic-bezier(.3,1.6,.5,1), filter .25s;
}
.hp-mate .hp-pose svg > :not(style, defs) { pointer-events: auto; }
.hp-mate:focus-visible { outline-color: var(--white); }
@media (min-width: 768px) { .hp-mate { --rot: var(--r); left: calc(var(--x) * 1%); bottom: calc(var(--y) * 1%); width: calc(var(--s) * 1%); z-index: var(--z); } }
.hp-mate.is-on { --lift: -18px; --sc: 1.1; --rot: 0deg; z-index: 9; }
@media (min-width: 768px) { .hp-mate.is-on { --lift: -36px; --sc: 1.12; z-index: 10; } }
.has-hov .hp-mate:not(.is-on) { filter: saturate(.6) brightness(.96); }
.is-snap .hp-mate { --lift: -14px; --rot: calc(var(--mr) * -1); }
@media (min-width: 768px) { .is-snap .hp-mate { --rot: calc(var(--r) * -1); } }
.hp-art, .hp-pose, .hp-who, .hp-row-mate { display: block; line-height: 0; }
.hp-art, .hp-pose { width: 100%; height: 100%; }
.hp-pose svg, .hp-who svg, .hp-row-mate svg { display: block; width: 100%; height: 100%; }
.hp-pose--hype, .is-on .hp-pose--idle, .is-snap .hp-pose--idle, .is-hype .hp-pose--idle { display: none; }
.is-on .hp-pose--hype, .is-snap .hp-pose--hype, .is-hype .hp-pose--hype { display: block; }
.hp-flash { position: absolute; inset: 0; z-index: 20; background: var(--white); opacity: 0; pointer-events: none; transition: opacity .45s; }
.is-flash .hp-flash { opacity: .9; transition: none; }
.hp-shutter {
  position: absolute; z-index: 21; right: 12px; top: 104px; width: 48px; height: 48px; padding: 4px;
  border: 4px solid var(--white); border-radius: 50%; background: transparent; cursor: pointer;
}
.hp-shutter span { display: block; width: 100%; height: 100%; border-radius: 50%; background: var(--white); transition: transform var(--dur-1) var(--ease-out); }
.hp-shutter:active span { transform: scale(.88); }
.hp-shutter:focus-visible { outline-color: var(--white); outline-offset: 3px; }
@media (min-width: 768px) { .hp-shutter { right: 40px; top: 84px; width: 72px; height: 72px; padding: 5px; border-width: 5px; } }
.hp-go { display: flex; flex-direction: column; gap: 10px; padding: 16px 0 40px; }
.hp-go .btn { width: 100%; }
@media (min-width: 768px) {
  .hp-go { flex-direction: row; justify-content: center; padding: 28px 0 72px; }
  .hp-go .btn { width: auto; }
}

/* How it works: what a group is, Dink's line, three numbered steps (each a
   soft card: a big numeral behind, a small made-up screen, tilted, over the
   text) and Start a group. Steps stack under 1024, three across from 1024. */
.hp-how { display: flex; flex-direction: column; gap: 16px; padding-bottom: 40px; }
.hp-how-head { display: flex; flex-direction: column; gap: 16px; }
.hp-how-text { display: flex; flex-direction: column; gap: 8px; }
.hp-how-def { max-width: 620px; font-size: 17px; line-height: 1.45; color: var(--ink-2); text-wrap: pretty; }
.hp-how-def strong { color: var(--ink); font-weight: 700; }
.hp-how-dink { display: flex; align-items: flex-end; gap: 8px; }
.hp-how-face { flex: none; width: 84px; height: 84px; margin: -8px -6px -6px -10px; line-height: 0; }
.hp-how-face svg { width: 100%; height: 100%; }
.hp-how-dink .hp-say { margin-bottom: 12px; }
.hp-steps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
/* isolate: the numeral (z -1) sits on the card's fill, under the screen and the words. */
.hp-step {
  position: relative; overflow: hidden; isolation: isolate; display: flex; flex-direction: column; gap: 20px;
  min-width: 0; padding: 24px; border-radius: var(--r-card); background: var(--surface-2);
}
.hp-step-text { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.hp-step-n {
  position: absolute; z-index: -1; top: -22px; right: 16px; pointer-events: none;
  font-size: 120px; line-height: 1; font-weight: 900; letter-spacing: -.06em; color: var(--hp-numeral);
}
.hp-step-t { font-size: 24px; line-height: 1.05; font-weight: 800; letter-spacing: -.02em; }
.hp-step p { font-size: 15px; line-height: 1.5; color: var(--ink-2); text-wrap: pretty; }
.hp-step-view { display: flex; justify-content: center; align-items: center; padding-block: 8px; }
.hp-how-go { display: flex; flex-direction: column; align-items: stretch; gap: 12px; padding-top: 8px; text-align: center; }
.hp-how-note { font-size: 15px; color: var(--ink-2); }
.hp-how-note a { color: var(--ink); font-weight: 600; text-underline-offset: 3px; }
@media (min-width: 768px) {
  .hp-how { gap: 24px; padding-bottom: 72px; }
  .hp-how-head { flex-direction: row; align-items: flex-end; justify-content: space-between; gap: 24px; }
  .hp-how-def { font-size: 18px; }
  .hp-how-dink { flex: 0 1 380px; }
  .hp-how-face { width: 110px; height: 110px; }
  .hp-steps { gap: 16px; }
  .hp-step { padding: 32px; border-radius: var(--r-lg); }
  .hp-step-n { top: -22px; right: 20px; font-size: 150px; }
  /* One height for the three screens, so the titles line up. */
  .hp-step-view { height: 236px; padding-block: 0; }
  .hp-step-t { font-size: 30px; }
  .hp-step p { font-size: 16px; }
  .hp-how-go { flex-direction: row; align-items: center; justify-content: center; gap: 24px; }
}
@media (min-width: 1024px) { .hp-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
/* A step's screen: a white card in the app's words, never live data. */
.hp-mini {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px; width: 240px; max-width: 100%;
  padding: 18px; border-radius: var(--r-tile); background: var(--surface); font-size: 14px;
}
.hp-step:nth-child(1) .hp-mini { transform: rotate(-2.5deg); }
.hp-step:nth-child(2) .hp-mini { transform: rotate(2deg); }
.hp-step:nth-child(3) .hp-mini { transform: rotate(-1.5deg); }
.hp-mini--center { align-items: center; text-align: center; }
.hp-mini .gtile { margin-bottom: 4px; }
.hp-mini-t { font-size: 17px; font-weight: 800; letter-spacing: -.01em; color: var(--ink); }
.hp-mini-m { font-size: 13px; color: var(--ink-2); }
.hp-mini-btn {
  display: inline-flex; align-items: center; justify-content: center; align-self: stretch; min-height: 40px; margin-top: 6px;
  border-radius: var(--r-pill); background: var(--surface-2); color: var(--ink); font-size: 14px; font-weight: 700;
}
.hp-mini-btn.is-lime { background: var(--lime); color: var(--on-lime); }
.hp-mini-row { display: flex; align-items: center; gap: 8px; padding: 6px 0 2px; }
.hp-mini .avatar-stack .avatar { --av: 30px; }
.hp-mini-head { display: flex; justify-content: space-between; align-items: baseline; align-self: stretch; padding-bottom: 4px; }
.hp-mini-rank { display: flex; align-items: center; gap: 10px; align-self: stretch; padding: 6px 0; }
.hp-mini-rank .avatar { --av: 30px; }
.hp-mini-rank b { flex: 1; min-width: 0; font-weight: 700; color: var(--ink); }
.hp-mini-n { width: 14px; font-weight: 800; color: var(--ink-2); text-align: center; }
.hp-mini-rec { font: 600 13px/1 var(--font-mono); color: var(--ink-2); }

/* Questions: an accordion, the title beside the list from 1024 (the
   FAQPage in <head> says the same). Every answer is in the page: landing.js
   sets .is-js and folds all but the open one by its row's height, never
   hidden, so without JS they all show. Open turns the row over to ink. */
.hp-faq { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 24px 0 32px; }
.hp-faq .hp-h2 { font-size: 30px; font-weight: 900; }
.hp-faq-list { display: flex; flex-direction: column; gap: 10px; }
.hp-qa { border-radius: var(--r-tile); background: var(--surface-2); color: var(--ink); transition: background-color .35s var(--hp-calm); }
.hp-q { font-size: 17px; line-height: 1.3; font-weight: 800; letter-spacing: -.01em; }
.hp-q-btn {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; width: 100%; min-height: var(--tap-lg);
  margin: 0; padding: 14px 14px 14px 20px; border: 0; border-radius: inherit; background: none; color: inherit;
  font: inherit; letter-spacing: inherit; text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: color .35s var(--hp-calm);
}
.hp-q-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.hp-q-ico {
  position: relative; flex: none; width: 36px; height: 36px; border-radius: 50%; background: var(--surface);
  transition: transform .5s var(--hp-calm), background-color .35s var(--hp-calm);
}
.hp-q-ico::before, .hp-q-ico::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 14px; height: 2px; margin: -1px 0 0 -7px;
  border-radius: 2px; background: var(--ink); transition: background-color .35s var(--hp-calm);
}
.hp-q-ico::after { transform: rotate(90deg); }
.hp-a { display: grid; grid-template-rows: 1fr; transition: grid-template-rows .5s var(--hp-calm); }
.hp-a-in { min-height: 0; overflow: hidden; }
.hp-a p {
  padding: 0 20px 20px; font-size: 15px; line-height: 1.5; color: var(--ink-2); text-wrap: pretty;
  transition: opacity .35s var(--hp-calm), transform .5s var(--hp-calm), color .35s var(--hp-calm);
}
.hp-faq:not(.is-js) .hp-q-ico { display: none; }
.hp-faq:not(.is-js) .hp-q-btn { cursor: default; }
.hp-faq.is-js .hp-qa:not(.is-open) .hp-a { grid-template-rows: 0fr; }
.hp-faq.is-js .hp-qa:not(.is-open) .hp-a p { opacity: 0; transform: translateY(-10px); }
.hp-qa.is-open { background: var(--ink); }
.hp-qa.is-open .hp-q-btn { color: var(--ground); }
.hp-qa.is-open .hp-a p { color: color-mix(in srgb, var(--ground) 72%, var(--ink)); }
.hp-qa.is-open .hp-q-ico { background: var(--lime); transform: rotate(135deg); }
.hp-qa.is-open .hp-q-ico::before, .hp-qa.is-open .hp-q-ico::after { background: var(--on-lime); }
@media (min-width: 768px) {
  .hp-faq { gap: 24px; padding: 56px 0; }
  .hp-faq .hp-h2 { font-size: 64px; line-height: 1; letter-spacing: -.035em; }
  .hp-faq-list { gap: 12px; }
  .hp-qa { border-radius: var(--r-tile); }
  .hp-q { font-size: 20px; }
  .hp-q-btn { padding: 16px 16px 16px 28px; }
  .hp-q-ico { width: 40px; height: 40px; }
  .hp-a p { padding: 0 72px 26px 28px; font-size: 16px; }
}
@media (min-width: 1024px) { .hp-faq { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 48px; align-items: start; } }

/* The app, run by the crew: one card per crew member, the character big and
   cut off by the card's edge; hover (a tap on a phone) rises them into hype. */
.hp-main { display: flex; flex-direction: column; gap: 12px; }
@media (min-width: 768px) { .hp-main { gap: 16px; } }
.hp-sec-head { display: flex; flex-direction: column; gap: 4px; padding-bottom: 4px; }
.hp-h2 { font-size: 30px; line-height: 1.05; font-weight: 800; letter-spacing: -.02em; text-wrap: balance; }
.hp-sec-sub { font-size: 15px; color: var(--ink-2); }
@media (min-width: 768px) {
  .hp-sec-head { padding-bottom: 8px; }
  .hp-h2 { font-size: 44px; letter-spacing: -.025em; }
  .hp-sec-sub { font-size: 16px; }
}
@media (min-width: 1280px) { .hp-sec-head { flex-direction: row; align-items: baseline; justify-content: space-between; gap: 16px; } }
.hp-app { display: flex; flex-direction: column; gap: 12px; }
.hp-cards { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
@media (min-width: 768px) {
  .hp-app { padding-bottom: 40px; }
  .hp-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
  .hp-card[data-crew="volley"], .hp-card[data-crew="dink"] { grid-column: span 2; }
  .hp-card[data-crew="lob"] { order: 3; }
  .hp-card[data-crew="dink"] { order: 4; }
}
@media (min-width: 1280px) {
  .hp-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .hp-card[data-crew="lob"], .hp-card[data-crew="dink"] { order: 0; }
}
.hp-card {
  position: relative; overflow: hidden; min-width: 0; height: 560px; padding: 24px; border-radius: var(--r-card);
  background: var(--surface-2); color: var(--ink);
}
.hp-card[data-crew="smash"], .hp-card[data-crew="dink"] { height: 500px; }
.hp-card[data-crew="lob"] { height: 520px; }
.hp-card--dark { background: var(--hp-dark); color: var(--hp-dark-ink); }
@media (min-width: 768px) {
  .hp-card { padding: 36px; border-radius: var(--r-lg); }
  .hp-card[data-crew="smash"] { height: 560px; }
  .hp-card[data-crew="volley"] { height: 520px; }
  .hp-card[data-crew="dink"], .hp-card[data-crew="lob"] { height: 480px; }
  .hp-card[data-crew="lob"] { height: 560px; }
}
@media (min-width: 1280px) {
  .hp-card[data-crew="volley"] { height: 560px; }
  .hp-card[data-crew="lob"] { height: 480px; }
}
.hp-card-text { position: relative; z-index: 2; display: flex; flex-direction: column; gap: 12px; }
@media (min-width: 768px) { .hp-card-text { max-width: 380px; } }
.hp-kick { font: 600 12px/1.3 var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-2); }
.hp-card--dark .hp-kick, .hp-ai .hp-kick { color: var(--hp-dark-kick); }
.hp-h3 { font-size: 28px; line-height: 1.02; font-weight: 800; letter-spacing: -.025em; }
.hp-card-sub { font-size: 15px; line-height: 1.5; color: var(--ink-2); text-wrap: pretty; }
.hp-card--dark .hp-card-sub { color: var(--hp-dark-sub); }
@media (min-width: 768px) {
  .hp-h3 { font-size: 36px; }
  .hp-card-sub { font-size: 16px; }
}
/* The crew member's line: a chat bubble with its tail at the bottom left. */
.hp-say {
  align-self: flex-start; max-width: 300px; padding: 12px 16px; border-radius: var(--r-tile) var(--r-tile) var(--r-tile) 6px;
  background: var(--surface); color: var(--ink); font-size: 15px; line-height: 1.35; font-weight: 600;
}
.hp-card--dark .hp-say { background: var(--white); color: var(--ink); }
.hp-say:empty { display: none; }
.hp-who {
  position: absolute; z-index: 3; pointer-events: none; right: -60px; bottom: -80px; width: 220px; height: 220px;
  transition: transform .4s cubic-bezier(.3,1.5,.5,1);
}
.hp-card.is-hype .hp-who { transform: translateY(-40px) rotate(-4deg); }
.hp-card[data-crew="volley"] .hp-who { right: -70px; width: 230px; height: 230px; }
.hp-card[data-crew="dink"] .hp-who { right: -70px; bottom: -60px; width: 200px; height: 200px; }
@media (min-width: 768px) {
  .hp-card[data-crew="volley"] .hp-who { right: auto; left: -10px; bottom: -110px; width: 380px; height: 380px; }
  .hp-card[data-crew="volley"].is-hype .hp-who { transform: translateY(-50px) rotate(-4deg); }
  .hp-card[data-crew="smash"] .hp-who { bottom: -90px; width: 260px; height: 260px; }
  .hp-card[data-crew="dink"] .hp-who { right: auto; left: 140px; bottom: -120px; width: 300px; height: 300px; }
  .hp-card[data-crew="lob"] .hp-who { bottom: -100px; width: 260px; height: 260px; }
}
@media (min-width: 1280px) { .hp-card[data-crew="dink"] .hp-who { left: 180px; } }

/* Volley: a map of three courts with price pins, the app's map colours. */
.hp-map { position: absolute; left: 0; right: 0; bottom: 0; height: 280px; overflow: hidden; color: var(--ink); }
/* The land is the ground's, so hp-motion.js can wipe it in over the card. */
.hp-map-ground { position: absolute; inset: 0; background: var(--hp-land); }
@media (min-width: 768px) { .hp-map { left: 420px; top: 0; height: auto; } }
.hp-map-ground i { position: absolute; display: block; }
.hp-map-water { right: -80px; bottom: -80px; width: 320px; height: 320px; border-radius: 50%; background: var(--hp-water); }
.hp-map-park { left: 8%; top: 48%; width: 34%; height: 16%; border-radius: 10px; background: var(--hp-park); }
.hp-map-road { background: var(--hp-road); box-shadow: 0 0 0 1.5px var(--hp-casing); }
.hp-map-road--h { left: -10%; right: -10%; top: 36%; height: 12px; }
.hp-map-road--v { top: -10%; bottom: -10%; left: 58%; width: 10px; }
.hp-map-road--d { left: -20%; width: 150%; top: 70%; height: 8px; transform: rotate(-24deg); }
.hp-map-search {
  position: absolute; top: 20px; left: 20px; right: 20px; min-height: 44px; padding: 0 18px;
  display: flex; align-items: center; gap: 10px; border-radius: var(--r-pill);
  background: var(--surface); color: var(--ink-2); font-size: 14px;
}
.hp-map-search svg { width: 16px; height: 16px; flex: none; }
.hp-map-search span { display: inline-block; white-space: nowrap; }
.hp-pin { position: absolute; left: calc(var(--px) * 1%); top: calc(64px + (100% - 64px) * var(--py) / 100); display: flex; flex-direction: column; align-items: center; transform: translate(-50%, -100%); }
.hp-pin b {
  min-height: 32px; padding: 0 12px; display: inline-flex; align-items: center; border-radius: var(--r-pill);
  background: var(--surface); color: var(--ink); font-size: 14px; font-weight: 800; font-variant-numeric: tabular-nums;
}
.hp-pin.is-on b { background: var(--ink); color: var(--surface); }
.hp-pin i { width: 14px; height: 14px; margin-top: 4px; border-radius: 50%; background: var(--lime); box-shadow: 0 0 0 2px var(--hp-ring); }
.hp-map-card {
  position: absolute; left: 16px; bottom: 12px; width: 170px; padding: 10px 12px;
  display: flex; flex-direction: column; border-radius: var(--r-field); background: var(--surface);
}
@media (min-width: 768px) { .hp-map-card { left: 20px; bottom: auto; top: calc(36% + 10px); width: 180px; } }
.hp-map-card b { font-size: 12.5px; font-weight: 800; }
.hp-map-card span { font-size: 11px; color: var(--ink-2); }

/* Smash and Lob: a phone, cut off by the card's bottom edge. */
.hp-view--phone { position: absolute; left: 24px; bottom: -230px; width: 230px; height: 440px; }
.hp-card[data-crew="lob"] .hp-view--phone { bottom: -200px; height: 400px; }
@media (min-width: 768px) {
  .hp-view--phone { left: 36px; bottom: -250px; width: 250px; height: 500px; }
  .hp-card[data-crew="lob"] .hp-view--phone { bottom: -250px; height: 460px; }
}
.hp-phone { width: 100%; height: 100%; padding: 8px; border-radius: var(--r-lg); background: var(--ink); box-shadow: 0 0 0 2px var(--ink-2), 0 30px 60px -24px color-mix(in srgb, var(--ink) 40%, transparent); }
.hp-phone-screen {
  width: 100%; height: 100%; overflow: hidden; padding: 22px 14px; border-radius: calc(var(--r-lg) - 8px);
  display: flex; flex-direction: column; gap: 10px; background: var(--panel); color: var(--ink);
}
.hp-screen-head { display: flex; align-items: baseline; justify-content: space-between; }
.hp-screen-head b { font-size: 20px; font-weight: 800; letter-spacing: -.02em; }
.hp-screen-head span { font-size: 12px; font-weight: 700; color: var(--ink-2); }
.hp-score { display: flex; flex-direction: column; gap: 8px; padding: 14px; border-radius: var(--r-tile); background: var(--ink); color: var(--panel); }
.hp-score hr { width: 100%; height: 1px; margin: 0; border: 0; background: color-mix(in srgb, var(--panel) 18%, var(--ink)); }
.hp-score-row { display: flex; align-items: center; gap: 8px; }
.hp-score-row span { flex: 1; min-width: 0; font-size: 13px; font-weight: 700; opacity: .82; }
/* Two digits' room: the ticker (hp-motion.js) runs to 11 without moving a thing. */
.hp-score-row b { min-width: 2ch; text-align: right; font-size: 40px; line-height: 1; font-weight: 800; font-variant-numeric: tabular-nums; opacity: .6; }
.hp-score-row i {
  width: 34px; height: 34px; flex: none; display: grid; place-items: center; border-radius: 50%;
  font-size: 14px; font-weight: 800; font-style: normal; background: color-mix(in srgb, var(--panel) 18%, var(--ink));
}
.hp-score-row.is-lead span, .hp-score-row.is-lead b { opacity: 1; }
.hp-score-row.is-lead i { background: var(--panel); color: var(--ink); }
.hp-screen-label { padding-top: 4px; font-size: 12px; font-weight: 700; color: var(--ink-2); }
.hp-next { display: flex; justify-content: space-between; padding: 10px 12px; border-radius: 14px; background: var(--surface); font-size: 13px; font-weight: 600; }
.hp-next span + span { color: var(--ink-2); }
.hp-quest { display: flex; align-items: center; gap: 10px; padding: 12px; border-radius: var(--r-field); background: var(--surface); }
.hp-quest-art { flex: none; width: 44px; height: 44px; line-height: 0; color: var(--ink); }
.hp-quest-art svg { width: 100%; height: 100%; }
.hp-quest-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; font-size: 11.5px; line-height: 1.35; color: var(--ink-2); }
.hp-quest-text b { font-size: 13px; font-weight: 700; color: var(--ink); }
.hp-bar { display: block; height: 6px; border-radius: var(--r-pill); background: var(--surface-2); }
.hp-bar i { display: block; height: 100%; border-radius: var(--r-pill); background: var(--ink); }

/* Dink: three locker doors (the Clubhouse's own .door), cut off by the card. */
.hp-lockers { position: absolute; left: 24px; bottom: -20px; display: flex; gap: 10px; }
.hp-door { flex: none; width: 150px; }
@media (min-width: 768px) { .hp-lockers { left: auto; right: -40px; bottom: -4px; gap: 14px; } }
@media (min-width: 1280px) { .hp-lockers { left: 450px; right: auto; } }

/* Crew AI: the dark card. The chat's own orb plays three sample questions;
   the three privacy points sit beside it (under it on a phone). */
.hp-ai {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; padding: 24px 16px 16px; border-radius: var(--r-card);
  background: var(--hp-dark); color: var(--hp-dark-ink);
}
.hp-ai-head { display: flex; flex-direction: column; gap: 14px; padding: 0 8px; }
.hp-ai-lede { font-size: 15px; line-height: 1.45; color: var(--hp-dark-sub); max-width: 460px; }
.hp-trust { list-style: none; padding: 0 8px; display: flex; flex-direction: column; }
.hp-trust li { display: flex; flex-direction: column; gap: 2px; padding: 12px 0; }
.hp-trust-t { font-size: 15px; font-weight: 700; }
.hp-trust p { font-size: 14px; line-height: 1.45; color: var(--hp-dark-sub); text-wrap: pretty; }
@media (min-width: 768px) {
  .hp-ai { gap: 24px; padding: 40px; border-radius: var(--r-lg); }
  .hp-ai-head { gap: 20px; padding: 0; }
  .hp-ai-lede { font-size: 18px; line-height: 1.5; }
  .hp-trust { padding: 0; }
  .hp-trust li { gap: 4px; padding: 16px 0; }
  .hp-trust-t { font-size: 17px; }
  .hp-trust p { font-size: 15px; line-height: 1.5; }
}
@media (min-width: 1280px) {
  .hp-ai {
    grid-template-columns: minmax(0, 1fr) 620px; grid-template-rows: auto auto;
    grid-template-areas: "head stage" "trust stage"; column-gap: 56px; row-gap: 24px; padding: 48px;
  }
  .hp-ai-head { grid-area: head; align-self: end; }
  .hp-ai-stage { grid-area: stage; }
  .hp-trust { grid-area: trust; align-self: start; }
  .hp-ai .hp-h2 { font-size: 52px; line-height: 1; letter-spacing: -.03em; }
}
.hp-ai-stage { position: relative; overflow: hidden; height: 540px; border-radius: var(--r-card); background: var(--hp-stage); }
@media (min-width: 768px) { .hp-ai-stage { height: 600px; } }
@media (min-width: 1280px) { .hp-ai-stage { height: 660px; } .hp-ai-status { top: 486px; } }
.hp-ai-ask {
  position: absolute; top: 52px; right: 14px; max-width: 250px; padding: 12px 16px; border-radius: var(--r-tile) var(--r-tile) 6px var(--r-tile);
  background: var(--white); color: var(--ink); font-size: 14px; line-height: 1.35; font-weight: 600;
  opacity: 0; transform: translateY(8px) scale(.96); transform-origin: 100% 100%;
  transition: opacity .3s, transform .4s cubic-bezier(.3,1.5,.5,1);
}
.hp-ai-orb { position: absolute; left: 50%; top: 140px; transform: translateX(-50%); line-height: 0; }
.hp-ai-status {
  position: absolute; left: 0; right: 0; top: 360px; text-align: center;
  font: 700 12px/1.3 var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--hp-dark-kick); transition: color .4s;
}
.hp-ai-reply {
  position: absolute; left: 14px; right: 44px; bottom: 14px; display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  opacity: 0; transform: translateY(10px); transition: opacity .3s, transform .4s cubic-bezier(.3,1.5,.5,1);
}
.hp-ai-from { font-size: 13px; font-weight: 800; color: var(--hp-dark-kick); }
.hp-ai-a { padding: 12px 16px; border-radius: var(--r-tile) var(--r-tile) var(--r-tile) 6px; background: var(--white); color: var(--ink); font-size: 14px; line-height: 1.35; font-weight: 600; text-wrap: pretty; }
.is-asked .hp-ai-ask, .is-answered .hp-ai-reply { opacity: 1; transform: none; }
@media (min-width: 768px) {
  .hp-ai-ask { top: 24px; right: 24px; max-width: 360px; font-size: 16px; }
  .hp-ai-orb { top: 136px; }
  .hp-ai-status { top: 456px; }
  .hp-ai-reply { left: 24px; right: 54px; bottom: 24px; }
  .hp-ai-a { font-size: 16px; }
}

/* The sign-off: the whole crew along the bottom edge. */
.hp-end {
  position: relative; overflow: hidden; height: 340px; padding: 32px 20px 0; border-radius: var(--r-card);
  display: flex; flex-direction: column; align-items: center; gap: 16px; text-align: center;
  background: var(--surface-2);
}
.hp-end .hp-h2 { max-width: 900px; letter-spacing: -.03em; }
/* Each crew name over a highlighter mark in their colour (the orb's,
   crew-orb.js COLORS); hp-motion.js draws it left to right. */
.hp-name { position: relative; display: inline-block; z-index: 0; }
.hp-nm { position: relative; display: inline-block; }
.hp-mark {
  position: absolute; z-index: -1; left: -.04em; right: -.04em; bottom: .06em; height: .3em;
  border-radius: .08em; transform-origin: 0 50%; background: var(--hp-mk);
}
.hp-mark[data-c="volley"] { --hp-mk: var(--volley); }
.hp-mark[data-c="dink"] { --hp-mk: var(--dink); }
.hp-mark[data-c="smash"] { --hp-mk: var(--smash); }
.hp-mark[data-c="erne"] { --hp-mk: var(--erne); }
.hp-mark[data-c="lob"] { --hp-mk: var(--lob); }
.hp-mark[data-c="pop"] { --hp-mk: var(--pop); }
.hp-row { position: absolute; left: -20px; right: -20px; bottom: -34px; display: flex; justify-content: center; }
.hp-row-mate { flex: none; width: 120px; height: 120px; margin: 0 -24px; }
@media (min-width: 768px) {
  .hp-end { height: 420px; padding-top: 56px; gap: 24px; border-radius: var(--r-lg); }
  .hp-end .hp-h2 { font-size: 48px; line-height: 1; }
  .hp-row { left: 0; right: 0; bottom: -66px; }
  .hp-row-mate { width: 200px; height: 200px; margin: 0 -30px; }
}
@media (min-width: 1280px) {
  .hp-end { height: 460px; padding-top: 64px; }
  .hp-end .hp-h2 { font-size: 56px; letter-spacing: -.035em; }
  .hp-row { bottom: -90px; }
  .hp-row-mate { width: 275px; height: 275px; margin: 0 -41px; }
}

/* Footer: the links, then the credit (.footer-credit, as on every page). */
.hp-foot { display: flex; flex-direction: column; align-items: center; gap: 4px; padding-top: 20px; padding-bottom: calc(20px + var(--safe-b)); font-size: 14px; color: var(--ink-2); }
.hp-foot-name { font-weight: 700; color: var(--ink); }
.hp-foot-nav { display: flex; flex-wrap: wrap; justify-content: center; column-gap: 20px; }
.hp-foot-nav a { display: inline-flex; align-items: center; min-height: var(--tap); color: var(--ink-2); text-decoration: none; }
.hp-foot-nav a:hover { color: var(--ink); }
.hp-foot .footer-credit { padding: 0; }
@media (min-width: 768px) {
  .hp-foot { flex-direction: row; gap: 24px; padding-top: 28px; padding-bottom: calc(28px + var(--safe-b)); }
  .hp-foot-nav { justify-content: flex-start; column-gap: 24px; }
  .hp-foot .footer-credit { margin-left: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .hp-mate, .hp-who, .hp-flash, .hp-shutter span, .hp-ai-ask, .hp-ai-reply, .hp-ai-status { transition: none; }
  .hp-nav a, .hp .btn.hp-bb, .hp-qa, .hp-q-btn, .hp-q-ico, .hp-q-ico::before, .hp-q-ico::after, .hp-a, .hp-a p { transition: none; }
}
/* ══ end §13 ══ */
