/* ============================================================================
   app-states.css — the loading / empty / error triad, decided once.

   .interface-design/system.md has always required every data section to
   handle all three states. It was implemented on two pages (matchup,
   betting_stats) and nowhere else, so on the rest a slow fetch and a failed
   one produced the same screen: a blank one. Four of the six main
   destinations rendered nothing at all by default.

   This is that triad as one stylesheet, so a page gets it by using the class
   rather than by copying thirty lines. The class names are the ones the
   system doc already names — .loading-state / .loading-spinner /
   .error-state / .empty-state / .empty-icon, toggled with .active — so the
   two pages that already did it right keep working and everything written
   against the doc lands.

   Loaded from templates/_app_ios.html, i.e. last in <body>, for the same
   reason app-ios.css is: every page in this app declares its CSS in an
   inline <style> inside the document, so a sheet linked from <head> loses
   every tie on source order.

   Everything here is tokens. No literals — see betwizard-tokens.css.
   ========================================================================= */

/* ── 0 · Immunity from the app's global control reset ─────────────────────
   Several app pages ship `input, textarea, select, button { width: 100% }`.
   It is right for the forms it was written for and wrong for every control
   in this file: a chip that fills the line is not a chip, and a "Clear"
   button demanding 100% inside a flex header squeezes the heading beside it
   until it wraps mid-word.

   This sheet loads last, so stating the width is enough — no !important.
   Declared once here rather than repeated on each component below, and the
   one control that genuinely is full width (.bw-suggest-row) opts back in. */

.bw-state-btn,
.bw-chip,
.bw-suggest-clear {
  width: auto;
  min-width: 0;
}

.bw-suggest-row { width: 100%; }

/* ── 1 · Shared shell ─────────────────────────────────────────────────────
   The three states are the same box with a different voice, so they share
   their geometry and differ only in colour and content. Each is hidden
   until .active, which is what lets a section hold all three at once and
   show exactly one. */

.loading-state,
.empty-state,
.error-state {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  text-align: center;
  /* Centred in the space the section would have filled, so switching states
     does not jump the page. Sections that want a taller hold set
     --bw-state-min themselves. */
  min-height: var(--bw-state-min, 180px);
  padding: var(--sp-6) var(--sp-4);
  box-sizing: border-box;
  font-family: 'DM Sans', 'Inter', system-ui, sans-serif;
}

.loading-state.active,
.empty-state.active,
.error-state.active {
  display: flex;
}

/* A state inside an already-bordered panel should not draw a second border.
   Pages opt in by putting .bw-state-bare on the state element. */
.loading-state.bw-state-bare,
.empty-state.bw-state-bare,
.error-state.bw-state-bare {
  min-height: 0;
  padding: var(--sp-5) var(--sp-3);
}

/* Titles and body copy read the same in all three. --fs-body is the floor
   for the line someone actually reads; --fs-footnote for the line under it. */
.bw-state-title {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
  text-wrap: balance;
}

.bw-state-body {
  margin: 0;
  max-width: 34ch;
  font-size: var(--fs-footnote);
  line-height: 1.5;
  color: var(--muted);
  text-wrap: pretty;
}

/* ── 2 · Loading ──────────────────────────────────────────────────────────
   A spinner and, optionally, a line of text. The text is optional on
   purpose: a skeleton (§4) says "content is coming" better than the word
   "Loading", and this state is for the cases where there is no shape to
   skeleton yet. */

.loading-state {
  color: var(--on-canvas-muted);
  font-size: var(--fs-footnote);
}

.loading-spinner {
  width: 22px;
  height: 22px;
  border: 2px solid var(--glass-border2);
  border-top-color: var(--cyan);
  border-radius: 50%;
  animation: bw-state-spin 0.8s linear infinite;
}

@keyframes bw-state-spin {
  to { transform: rotate(360deg); }
}

/* ── 3 · Empty and error ──────────────────────────────────────────────────
   The difference that matters to a user: empty means "nothing to show, and
   that is the true answer"; error means "we could not find out". They must
   not look the same, because the second one is worth retrying and the first
   is not. */

.empty-state .empty-icon {
  font-size: var(--fs-title2);
  line-height: 1;
  color: var(--on-canvas-muted);
  opacity: 0.75;
}

.error-state {
  gap: var(--sp-3);
  border: 1px solid color-mix(in srgb, var(--red) 25%, transparent);
  border-radius: var(--r-card);
  background: var(--red-dim);
  color: var(--on-canvas-2);
}

.error-state .empty-icon,
.error-state .bw-state-icon {
  font-size: var(--fs-title3);
  line-height: 1;
  color: var(--on-canvas-red);
}

.error-state .bw-state-title { color: var(--on-canvas); }
.error-state .bw-state-body  { color: var(--on-canvas-2); }

/* ── 4 · Actions ──────────────────────────────────────────────────────────
   An empty state that only says "nothing here" wastes the screen. These are
   the way out of it — retry on an error, a route into the product on an
   empty. 44px of reach on a control that paints at --pill-h, the same
   expander pattern app-ios.css uses everywhere else. */

.bw-state-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  justify-content: center;
  margin-top: var(--sp-1);
}

.bw-state-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  height: var(--tap);
  padding: 0 var(--sp-4);
  border: 1px solid var(--glass-border2);
  border-radius: var(--r-control);
  background: var(--glass-bg2);
  color: var(--text);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: var(--fs-footnote);
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  transition: background-color 200ms ease, border-color 200ms ease, color 200ms ease;
}

.bw-state-btn:hover {
  border-color: var(--cyan-mid);
  color: var(--cyan);
}

.bw-state-btn:active { background: var(--cyan-dim); }

.bw-state-btn:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

/* The one action that is the point of the state gets the accent fill. */
.bw-state-btn.is-primary {
  border-color: transparent;
  background: var(--cyan);
  color: var(--on-accent);
}

.bw-state-btn.is-primary:hover {
  background: var(--cyan);
  color: var(--on-accent);
  filter: brightness(1.08);
}

.error-state .bw-state-btn {
  border-color: var(--cyan-mid);
  background: var(--cyan-dim);
  color: var(--on-canvas-accent);
}

/* ── 5 · Skeletons ────────────────────────────────────────────────────────
   For sections whose shape is known before the data is. A skeleton keeps
   the layout from jumping when content lands, which is the difference
   between a screen that fills in and a screen that lurches.

   The sweep is a background-position animation on a gradient — no layout,
   no paint of a moving element, so it composites. */

.bw-skeleton {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-control);
  background: var(--glass-bg2);
}

.bw-skeleton::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--text) 6%, transparent) 50%,
    transparent 100%
  );
  transform: translateX(-100%);
  animation: bw-skeleton-sweep 1.4s ease-in-out infinite;
}

@keyframes bw-skeleton-sweep {
  to { transform: translateX(100%); }
}

/* A stack of skeleton rows standing in for a list. */
.bw-skeleton-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.bw-skeleton-card {
  height: 84px;
  border-radius: var(--r-card);
}

.bw-skeleton-line {
  height: 12px;
  border-radius: var(--pill-radius);
}

.bw-skeleton-line.is-short { width: 40%; }
.bw-skeleton-line.is-half  { width: 60%; }

/* ── 6 · Suggestion chips ─────────────────────────────────────────────────
   What replaces a blank search screen: the things the user searched last
   and the things worth searching now. A chip is a real control — it runs
   the search — so it carries a real target, not a 26px one. */

.bw-suggest {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-5) 0 0;
}

.bw-suggest-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
}

.bw-suggest-title {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  font-family: 'DM Sans', 'Inter', system-ui, sans-serif;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--amber);
  /* A section label is one line. Letting it wrap puts "RECENTLY / VIEWED"
     over two lines beside a button, which reads as a layout fault. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A word of text is the design; 44px is the target. Real padding rather
   than a transparent ::after expander, and the padding grows *inward*:
   this control sits flush to the right edge of its row, and `body` is
   `overflow-x: clip` at phone widths (base.html), so anything hanging past
   the container edge is clipped out of hit-testing as well as out of paint
   — leaving the reach short on exactly the side a right thumb comes from.
   Padding on the start side only keeps the label optically flush while the
   box grows left into space that is actually hittable. */
.bw-suggest-clear {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  min-height: var(--tap);
  padding-inline-start: var(--sp-4);
  padding-inline-end: 0;
  border: 0;
  background: none;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}

.bw-suggest-clear:hover { color: var(--cyan); }

.bw-suggest-clear:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 3px;
  border-radius: var(--sp-1);
}

/* The chip rail scrolls sideways rather than wrapping into ragged rows.
   overscroll-behavior-x keeps the gesture off the page and off swipe-back. */
.bw-suggest-rail {
  display: flex;
  gap: var(--sp-2);
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  padding-bottom: var(--sp-1);
  /* Bleed to the screen edge so the rail reads as scrollable. */
  margin-inline: calc(var(--pad) * -1);
  padding-inline: var(--pad);
}

.bw-suggest-rail::-webkit-scrollbar { display: none; }

.bw-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 0 auto;
  min-height: var(--tap);
  padding: 0 var(--sp-4);
  border: 1px solid var(--glass-border);
  border-radius: var(--pill-radius);
  background: var(--glass-bg2);
  color: var(--text);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: var(--fs-footnote);
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  transition: border-color 200ms ease, background-color 200ms ease, color 200ms ease;
}

.bw-chip:hover {
  border-color: var(--cyan-mid);
  color: var(--cyan);
}

.bw-chip:active { transform: scale(0.97); }

.bw-chip:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

.bw-chip-crest {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  object-fit: contain;
}

.bw-chip-meta {
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* A ranked list rather than a rail — used for leaders, where the order is
   the information. */
.bw-suggest-list {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--glass-border);
  border-radius: var(--r-card);
  /* A nested panel, not a card: --bg3 stays a visible tint on a white card
     in light mode, where --glass-bg (86% white) vanished against it. */
  background: var(--bg3);
  overflow: hidden;
}

.bw-suggest-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: var(--tap);
  padding: var(--sp-3) var(--sp-4);
  border: 0;
  border-bottom: 1px solid var(--glass-border);
  background: none;
  color: var(--text);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: var(--fs-subhead);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 200ms ease;
}

.bw-suggest-row:last-child { border-bottom: 0; }
.bw-suggest-row:hover      { background: var(--glass-bg2); }
.bw-suggest-row:active     { background: var(--cyan-dim); }

.bw-suggest-row:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: -2px;
}

.bw-suggest-rank {
  flex: 0 0 auto;
  width: 18px;
  font-size: var(--fs-caption);
  font-weight: 700;
  color: var(--muted2);
  font-variant-numeric: tabular-nums;
}

.bw-suggest-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.bw-suggest-sub {
  display: block;
  font-size: var(--fs-caption);
  font-weight: 400;
  color: var(--muted);
}

.bw-suggest-stat {
  flex: 0 0 auto;
  font-family: 'Orbitron', 'Inter', system-ui, sans-serif;
  font-size: var(--fs-subhead);
  font-weight: 700;
  color: var(--cyan);
  font-variant-numeric: tabular-nums;
}

/* ── 7 · Non-destructive refresh failure ──────────────────────────────────
   A failed refresh must never take good content off the screen. This is the
   banner that says so, pinned above the list it could not update, while the
   list keeps showing what it already had. */

.bw-refresh-error {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid color-mix(in srgb, var(--red) 25%, transparent);
  border-radius: var(--r-control);
  background: var(--red-dim);
  color: var(--text2);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: var(--fs-footnote);
}

.bw-refresh-error .bw-refresh-msg { flex: 1 1 auto; min-width: 0; }

.bw-refresh-error .bw-state-btn {
  height: 32px;
  min-height: 32px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-caption);
}

/* 32px paints, 44px answers — the expander pattern from app-ios.css §2. */
.bw-refresh-error .bw-state-btn::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: var(--tap);
  min-width: var(--tap);
  transform: translate(-50%, -50%);
}

/* ── 8 · Reduced motion ───────────────────────────────────────────────────
   The spinner keeps turning — it is the only thing saying the app is still
   working, and a frozen spinner reads as a hang. Everything decorative
   stops. */

@media (prefers-reduced-motion: reduce) {
  .bw-skeleton::after {
    animation: none;
    opacity: 0;
  }
  .bw-state-btn,
  .bw-chip,
  .bw-suggest-row {
    transition: none;
  }
  .bw-chip:active { transform: none; }
  .loading-spinner { animation-duration: 1.6s; }
}

/* ── 9 · Bottom sheet ─────────────────────────────────────────────────────
   Paired with static/js/bw-sheet.js, which owns opening one, closing it and
   the flick that dismisses it.

   Here rather than in a page stylesheet because it was in two of them —
   dashboard-*.css and bestpicks-*.css carried near-identical copies, and the
   Picks copy was the older one: it drew `.bw-sheet-handle` but was missing
   `.bw-sheet.is-dragging`, so even once the shared controller was wired the
   sheet would have fought the finger with a 0.3s transition. Two copies of a
   component is how one of them ends up a version behind.

   Loaded from _app_ios.html at the end of <body>, so these land after a
   page's own stylesheet and a page that still declares .bw-sheet locally
   would be overridden rather than the other way round.

   Page-specific parts stay with their page: the Picks sheet's `.bw-sheet-foot`
   and `.sheet-group`, and the dashboard's `.bw-sheet-sub` and the filter row
   it nests. */

.bw-sheet-backdrop {
  position: fixed; inset: 0; z-index: 10040;
  background: var(--scrim);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  opacity: 0; visibility: hidden;
  transition: opacity 0.25s ease, visibility 0.25s ease;
}
.bw-sheet-backdrop.is-open { opacity: 1; visibility: visible; }

.bw-sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 10050;
  max-width: 540px; margin: 0 auto;
  max-height: min(82vh, 720px);
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--surface-border); border-bottom: none;
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  box-shadow: var(--shadow-pop);
  padding-bottom: var(--safe-bottom);
  transform: translateY(101%);
  visibility: hidden;
  transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1), visibility 0.3s;
}
.bw-sheet.is-open { transform: translateY(0); visibility: visible; }
/* The finger owns the position while a drag is live; a transition here would
   make the sheet lag behind it. */
.bw-sheet.is-dragging { transition: none; }

.bw-sheet-handle {
  width: 36px; height: 5px; border-radius: 3px; flex-shrink: 0;
  background: var(--glass-border2); margin: 8px auto 0;
}

/* Centred title, close button pinned to the corner — the same rule the
   section headings follow, so a sheet reads as part of the same page. */
.bw-sheet-head {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  gap: var(--sp-2);
  /* The head is the drag-dismiss surface; the browser gets no say. */
  touch-action: none;
  /* The gutter is reserved on BOTH sides — the close button is out of flow,
     so without it a long title centres across the full width and runs under
     the button. Symmetric, so the title stays centred. */
  padding: var(--sp-3) calc(var(--pad) + 34px + var(--sp-2)) var(--sp-2);
  flex-shrink: 0;
}
.bw-sheet-title {
  font-size: var(--fs-headline); font-weight: 700; color: var(--text-strong);
}
.bw-sheet-close {
  position: absolute; right: var(--pad); top: 50%;
  transform: translateY(-50%);
  /* min-width as well as width, so the circle stays a circle under any page
     that floors button widths on a phone (hpstyles.css does). */
  width: 34px; min-width: 34px; height: 34px; min-height: 34px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--glass-bg); border: 1px solid var(--glass-border);
  color: var(--muted2); cursor: pointer; font-size: 0.8rem; flex: 0 0 auto;
}
.bw-sheet-close:hover { color: var(--text-strong); }
/* 34px of paint, 44px of finger. */
.bw-sheet-close::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: var(--tap); height: var(--tap); transform: translate(-50%, -50%);
}

.bw-sheet-body {
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  /* Reaching the end of the table must not hand the drag to the page. */
  overscroll-behavior: contain;
  padding: 0 var(--pad) var(--sp-4);
}

@media (prefers-reduced-motion: reduce) {
  .bw-sheet { transition: visibility 0.2s; }
}

/* ── 10 · Toast ───────────────────────────────────────────────────────────
   Paired with BWToast in static/js/bw-states.js.

   One drawing, because there were four: fpShowToast's neutral disc
   bottom-right (copied into seven templates), #bp-toast's green box
   bottom-centre, showNotification's green banner top-centre, and admin's
   own. The Edge Analyzer and the Prop Lab are two tabs of one page and used
   two different ones, so adding a leg moved the confirmation to a different
   corner depending on which tab you were on.

   This is the Player Search banner's drawing — top-centre under the header,
   tinted by outcome, with room for an icon and a leg count. Above the
   sheets' backdrop is deliberate: a toast fired from inside an open sheet
   has to be readable without closing it. */

.bw-toast {
  position: fixed;
  top: calc(var(--id-h, 0px) + var(--safe-top) + 20px);
  left: 50%;
  z-index: 10070;
  display: flex; align-items: center; gap: 8px;
  max-width: 500px; width: 90%;
  padding: 12px 18px;
  border-radius: 12px;
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-pop);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  font-family: 'Inter', 'DM Sans', sans-serif;
  font-size: 0.88rem; font-weight: 500; letter-spacing: 0.3px;
  opacity: 0; visibility: hidden;
  transform: translateX(-50%) translateY(-20px);
  transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1),
              transform 0.4s cubic-bezier(0.4, 0, 0.2, 1),
              visibility 0.4s;
  pointer-events: none;
}
.bw-toast.is-open {
  opacity: 1; visibility: visible;
  transform: translateX(-50%) translateY(0);
}
.bw-toast.is-fading {
  opacity: 0;
  transform: translateX(-50%) translateY(-20px);
}

.bw-toast.success { background: var(--green-dim); border-color: var(--green-line); color: var(--green); }
.bw-toast.error   { background: var(--red-dim);   border-color: color-mix(in srgb, var(--red) 40%, transparent); color: var(--red); }
.bw-toast.warning { background: var(--amber-dim); border-color: var(--amber-line); color: var(--amber); }
.bw-toast.info    { background: var(--menu-bg);   border-color: var(--glass-border2); color: var(--text); }

.bw-toast-icon { flex: 0 0 auto; font-size: 1rem; }
.bw-toast-text { flex: 1 1 auto; }

@media (prefers-reduced-motion: reduce) {
  .bw-toast { transition: opacity 0.2s, visibility 0.2s; transform: translateX(-50%); }
  .bw-toast.is-open, .bw-toast.is-fading { transform: translateX(-50%); }
}

/* ── 11 · Segmented switch ────────────────────────────────────────────────
   The two-way tool switch: Player/Team on Search, Lab/Edge on Picks. A track
   that is the pill, a thumb that is the selected half, and two equal halves
   so neither side reads as the real one and the other as an extra.

   Here because it was written twice, and the copies had drifted in exactly
   the way tools/drift_audit.py exists to catch: the thumb's hairline was
   --cyan-mid on one page and --cyan-line on the other, and the option was
   44px tall on Search but 38px on Picks — under the 44px floor, with none of
   the ::after expander that .std-seg and .filter-pill use to answer 44 while
   painting less. Picks now paints its 38px and answers 44.

   This is also the animation the feedback asked to see more of ("animacion en
   todos los pill selectors... como el que tienes en player search"). The thumb
   moves with a transform, so it animates on the compositor and never reflows
   the row. NOT applied to the bottom navigation: that bar navigates to another
   page, so the highlight is painted by the page that loads, with nothing on
   screen for a thumb to slide from.

   Search's Player/Team switch left this component on 2026-09-23: it is two
   filter pills in the Search filter row now (§13). */

.bp-mode-switch {
  position: relative;
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 0;
  width: 100%; max-width: 360px;
  margin: 0 auto var(--sp-4);
  padding: 3px;
  background: var(--surface);
  border: 1px solid var(--surface-border);
  border-radius: var(--pill-radius);
  box-shadow: var(--shadow-elev);
  isolation: isolate;
}

.bp-mode-thumb {
  position: absolute; z-index: 0;
  top: 3px; bottom: 3px; left: 3px;
  width: calc(50% - 3px);
  border-radius: var(--pill-radius);
  background: var(--cyan-dim);
  /* --cyan-line: the token whose name is "accent hairline". Search used
     --cyan-mid, which is the fill weight. */
  border: 1px solid var(--cyan-line);
  transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}
.bp-mode-switch[data-mode="edge"] .bp-mode-thumb { transform: translateX(100%); }

.bp-mode-opt {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; min-width: 0;
  padding: 0 var(--sp-2);
  background: none; border: none; border-radius: var(--pill-radius);
  font-family: 'Inter', sans-serif;
  font-size: var(--fs-caption); font-weight: 600;
  letter-spacing: normal; text-transform: none;
  color: var(--text2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: color 0.18s ease;
}
/* Two paint heights, one reach. Search's switch is the page's main control
   and carries its full 44px; Picks' sits under a masthead and paints 38 —
   so it takes the expander instead, the same one .std-seg and .filter-pill
   use. */
.bp-mode-opt { height: 38px; min-height: 38px; }
.bp-mode-opt::after {
  content: ''; position: absolute; top: 50%; left: 0; right: 0;
  height: var(--tap); transform: translateY(-50%);
}

.bp-mode-opt i { font-size: 0.9em; opacity: 0.9; }
.bp-mode-opt[aria-selected="true"] { color: var(--cyan); font-weight: 700; }
.bp-mode-opt:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }

@media (max-width: 360px) {
  .bp-mode-opt span { font-size: var(--fs-micro); }
}

/* The selected half in light mode: a 12% tint of the deep light cyan is a
   whisper against the white track; the -mid step reads. This lived on Picks
   only, so the identical switch on Search had a thumb nobody could see in
   light mode — the same drift, in the other direction. */
html[data-theme="light"] .bp-mode-thumb { background: var(--cyan-mid); }

@media (prefers-reduced-motion: reduce) {
  .bp-mode-thumb { transition: none; }
}

/* ── 12 · Page intro ───────────────────────────────────────────────────────
   The one sentence at the top of a main destination that says what the page
   is for, before the first control. It began as Picks' masthead
   (.bp-masthead in the Picks page sheet); on the owner's ruling (2026-09-23)
   Search, Games and My Slips carry the same line, in the same face, size,
   weight and spacing — so it lives here, once, and every page that has one
   uses these two classes rather than a copy.

   The sentence is the page's <h1>. Bold explicitly rather than by the h1's
   UA default: this was once a bare h1 on one pane and a bare p on another,
   and the two rendered at different weights. Centred, capped at 38ch so a
   desktop column does not stretch it into one long line, on the canvas ink
   because no card sits under it. */
.page-intro {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  text-align: center;
  padding: 0 0 var(--sp-4);
}
/* display:flex outranks the [hidden] default. */
.page-intro[hidden] { display: none; }
.page-intro-text {
  margin: 0 auto;
  max-width: 38ch;
  /* Named rather than inherited: Games does not extend base.html and its
     body face is not Picks'. DM Sans is what Picks' line rendered in. */
  font-family: 'DM Sans', sans-serif;
  font-size: var(--fs-subhead);
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: normal;
  text-transform: none;
  color: var(--on-canvas-muted);
  text-wrap: pretty;
}

/* ── 13 · Search filter row ────────────────────────────────────────────────
   Player search · Team analysis · league, in one row — the Games page's
   filter row, same pill (owner ruling 2026-09-23). The league pill is the
   page's own .filter-dd-btn, already the Games .filter-pill in every value;
   the two tool buttons below are that pill again, with "selected" meaning
   the tool on screen, painted the way a pressed Games pill is.

   #pt-mode-switch keeps role="tablist" and its data-mode (apply() in
   search.html sets both), so it stays an element — a flex box with the gap
   of the row — rather than display:contents, which drops the tablist role
   in some WebKit builds.

   Each tool has its own league dropdown in the row; the one for the hidden
   tool is not drawn. Keyed off data-mode, so the switch script needs no
   change and the first paint is already right (the server renders it). */

.pt-filter-row {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: nowrap;
  gap: var(--sp-2);
  padding: 0 0 12px;
}

.pt-mode-switch {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 1 auto;
  min-width: 0;
}

.pt-mode-opt {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 6px;
  flex: 0 1 auto; width: auto; min-width: 0;
  height: 34px; min-height: 34px;
  padding: 0 var(--sp-3);
  border-radius: var(--pill-radius);
  background: var(--surface);
  border: 1px solid var(--surface-border);
  color: var(--text2);
  font-family: 'Inter', sans-serif;
  font-size: var(--fs-caption); font-weight: 600;
  letter-spacing: normal; text-transform: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-appearance: none; appearance: none;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.18s, border-color 0.18s, background 0.18s;
}
/* 34px of paint, 44px of finger. */
.pt-mode-opt::after {
  content: ''; position: absolute; left: 0; right: 0;
  top: 50%; height: var(--tap); transform: translateY(-50%);
}
.pt-mode-opt span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pt-mode-opt i { font-size: 0.9em; line-height: 1; opacity: 0.9; }
.pt-mode-opt:hover { color: var(--text-strong); border-color: var(--glass-border2); }
.pt-mode-opt[aria-selected="true"] {
  color: var(--cyan);
  border-color: var(--cyan-line);
  /* Over an opaque underlay, as on Games: straight onto the canvas the
     cyan-on-cyan-dim pair slips under AA in light mode. */
  background: linear-gradient(var(--cyan-dim), var(--cyan-dim)) var(--surface);
}
.pt-mode-opt:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }

/* The league pill beside them may give way too, on the narrowest phones. */
.pt-filter-row .pt-league-dd { flex: 0 1 auto; min-width: 0; }
.pt-filter-row .pt-league-dd > .filter-dd-btn { max-width: 100%; }
.pt-filter-row .pt-league-dd .filter-dd-label {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

#pt-mode-switch[data-mode="team"] ~ #league-filter-dd,
#pt-mode-switch:not([data-mode="team"]) ~ #team-sport-dd { display: none; }

/* On a touch screen the league pill grows to the 44px floor (app-ios.css
   §3); the tool pills grow with it so the row stays one height. */
@media (hover: none) and (pointer: coarse) {
  .pt-mode-opt { min-height: var(--tap); }
}
